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