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));