wlan_fidl_ext/
try_unpack.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use itertools::Itertools;
6use paste::paste;
7use std::fmt::Display;
8
9#[derive(Clone, Copy, Debug)]
10pub struct NamedField<T, A> {
11    pub value: T,
12    pub name: A,
13}
14
15pub trait WithName<T>: Sized {
16    fn with_name<A>(self, name: A) -> NamedField<T, A>;
17}
18
19impl<T> WithName<Option<T>> for Option<T> {
20    fn with_name<A>(self, name: A) -> NamedField<Option<T>, A> {
21        NamedField { value: self, name }
22    }
23}
24
25/// A trait for attempting to unpack the values of one type into another.
26///
27/// # Examples
28///
29/// Basic usage:
30///
31/// ```
32/// impl<T, U> TryUnpack for (Option<T>, Option<U>) {
33///     type Unpacked = (T, U);
34///     type Error = ();
35///
36///     fn try_unpack(self) -> Result<Self::Unpacked, Self::Error> {
37///         match self {
38///             (Some(t), Some(u)) => Ok((t, u)),
39///             _ => Err(()),
40///         }
41///     }
42/// }
43///
44/// assert_eq!(Ok((1i8, 2u8)), (Some(1i8), Some(2u8)).try_unpack());
45/// assert_eq!(Err(()), (Some(1i8), None).try_unpack());
46/// ```
47pub trait TryUnpack {
48    type Unpacked;
49    type Error;
50
51    /// Tries to unpack value(s) from `self` and returns a `Self::Error` if the operation fails.
52    fn try_unpack(self) -> Result<Self::Unpacked, Self::Error>;
53}
54
55#[doc(hidden)]
56#[macro_export]
57macro_rules! one {
58    ($_t:tt) => {
59        1
60    };
61}
62
63macro_rules! impl_try_unpack_named_fields {
64    (($t:ident, $a:ident)) => {
65        impl<$t, $a: Display> TryUnpack
66            for NamedField<Option<$t>, $a>
67        {
68            type Unpacked = $t;
69            type Error = anyhow::Error;
70
71            /// Tries to unpack the value a `NamedField<Option<T>, A: Display>`.
72            ///
73            /// If the `Option<T>` contains a value, then value is unwrapped from the `Option<T>`
74            /// and returned, discarding the name.  Otherwise, the returned value is a `Self::Error`
75            /// is an array containing the name. An array is returned so that `Self::Error` is an
76            /// iterable like in the `TryUnpack` implementation for tuples.
77            fn try_unpack(self) -> Result<Self::Unpacked, Self::Error> {
78                let NamedField { value, name } = self;
79                value.ok_or_else(|| anyhow::format_err!(
80                    "Unable to unpack empty field: {}", name
81                ))
82            }
83        }
84    };
85
86    ($(($t:ident, $a:ident)),+) => {
87        paste!{
88            impl<$($t, $a: Display),+> TryUnpack
89                for ($(NamedField<Option<$t>, $a>),+)
90            {
91                type Unpacked = ($($t),+);
92                type Error = anyhow::Error;
93
94                /// Tries to unpack values from a tuple of *n* fields each with type
95                /// `NamedField<Option<Tᵢ>, Aᵢ: Display>`.
96                ///
97                /// If every `Option<Tᵢ>` contains a value, then the returned value has the type
98                /// `(T₁, T₂, …, Tₙ)`, i.e., every value is unwrapped, and every name is
99                /// discarded. Otherwise, the returned value is a `Self::Error` containing name of
100                /// each field missing a value.
101                fn try_unpack(self) -> Result<Self::Unpacked, Self::Error> {
102                    // Fill `names` with the name of each missing field. The variable `arity`
103                    // is the arity of the tuple this trait is implemented for.
104                    let mut names = None;
105                    let names = &mut names;
106                    let arity =  0usize $(+ one!($t))+;
107
108                    // Deconstruct the tuple `self` into variables `t1`, `t2`, ...
109                    let ($([<$t:lower>]),+) = self;
110
111                    // Rebind each `tn` to an `Option<Tn>` that contains a value if `tn` contains
112                    // a success value. If `tn` contains an error value, then append the error
113                    // value (name) to `names`.
114                    let ($([<$t:lower>]),+) = (
115                        $({
116                            let NamedField { value, name } = [<$t:lower>];
117                            value.or_else(|| {
118                                names
119                                    .get_or_insert_with(|| Vec::with_capacity(arity))
120                                    .push(name.to_string());
121                                None
122                            })
123                        }),+
124                    );
125
126                    // If `names` contains a value, then one of the `tn` variables must have
127                    // contained an error value. In that case, this function returns the error value
128                    // `names`. Otherwise, each `tn` variable is returned without its name.
129                    match names.take() {
130                        Some(names) => Err(anyhow::format_err!(
131                            "Unable to unpack empty field(s): {}",
132                            names.into_iter().join(", "),
133                        )),
134                        _ => Ok(($([<$t:lower>].unwrap()),+)),
135                    }
136                }
137            }
138        }
139    };
140}
141
142impl_try_unpack_named_fields!((T1, A1));
143impl_try_unpack_named_fields!((T1, A1), (T2, A2));
144impl_try_unpack_named_fields!((T1, A1), (T2, A2), (T3, A3));
145impl_try_unpack_named_fields!((T1, A1), (T2, A2), (T3, A3), (T4, A4));
146impl_try_unpack_named_fields!((T1, A1), (T2, A2), (T3, A3), (T4, A4), (T5, A5));