sui_gql_client/extract.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
//! Defines [`extract!`](crate::extract!) and its [`Error`].
#[cfg(feature = "queries")]
pub(crate) type Result<T> = std::result::Result<T, Error>;
/// Error for [`extract!`](crate::extract!).
#[derive(thiserror::Error, Debug)]
#[error("Missing data from response: {0}")]
pub struct Error(pub(crate) String);
impl Error {
pub fn new(s: impl Into<String>) -> Self {
Self(s.into())
}
}
/// Helper for extracting data from GraphQL responses.
///
/// Designed specially to deal with the multitude of nested [`Option`]s commonly found in GQL
/// responses. The macro will generate an error string with the full path to the missing attribute.
///
/// # Example
///
/// ```no_run
/// # use af_sui_types::Address;
/// # use sui_gql_schema::{schema, scalars};
/// #
/// # #[derive(cynic::QueryVariables, Debug)]
/// # struct QueryVariables<'a> {
/// # ch: Address,
/// # vault: DynamicFieldName<'a>,
/// # }
/// #
/// # #[derive(cynic::InputObject, Debug)]
/// # struct DynamicFieldName<'a> {
/// # #[cynic(rename = "type")]
/// # type_: &'a str,
/// # bcs: scalars::Base64<Vec<u8>>,
/// # }
/// #
/// #[derive(cynic::QueryFragment, Debug)]
/// #[cynic(variables = "QueryVariables")]
/// struct Query {
/// #[arguments(address: $ch)]
/// object: Option<Object>,
/// }
///
/// #[derive(cynic::QueryFragment, Debug)]
/// #[cynic(variables = "QueryVariables")]
/// struct Object {
/// #[arguments(name: $vault)]
/// dynamic_field: Option<DynamicField>,
/// }
///
/// #[derive(cynic::QueryFragment, Debug)]
/// struct DynamicField {
/// value: Option<DynamicFieldValue>,
/// }
///
/// #[derive(cynic::InlineFragments, Debug)]
/// enum DynamicFieldValue {
/// MoveValue(MoveValue),
/// #[cynic(fallback)]
/// Unknown
/// }
///
/// #[derive(cynic::QueryFragment, Debug)]
/// struct MoveValue {
/// #[cynic(rename = "type")]
/// type_: MoveType,
/// bcs: scalars::Base64<Vec<u8>>,
/// __typename: String,
/// }
///
/// #[derive(cynic::QueryFragment, Debug)]
/// struct MoveType {
/// repr: String,
/// }
///
/// use sui_gql_client::extract;
///
/// // Could be obtained in practice from `sui_gql_client::GraphQlResponseExt::try_into_data`
/// let data: Option<Query> = None;
/// let df_value: MoveValue = extract!(
/// data?.object?.dynamic_field?.value?.as_variant(DynamicFieldValue::MoveValue)
/// );
/// # color_eyre::eyre::Ok(())
/// ```
#[macro_export]
macro_rules! extract {
($ident:ident $($tt:tt)*) => {{
$crate::extract!( @attributes [$ident][$($tt)*] -> {
$ident
} )
}};
(@attributes [$($path:tt)*][] -> { $($expr:tt)* } ) => { $($expr)* };
(@attributes [$($path:tt)*][.as_variant($($var:tt)+) $($tt:tt)*] -> { $($expr:tt)* } ) => {
$crate::extract!(@attributes
[$($path)*.as_variant($($var)*)][$($tt)*] -> {{
let value = $($expr)*;
let $($var)+(var) = value else {
let msg = stringify!($($path)*.as_variant($($var)*));
return Err($crate::extract::Error::new(msg).into());
};
var
}}
)
};
(@attributes [$($path:tt)*][? $($tt:tt)*] -> { $($expr:tt)* } ) => {
$crate::extract!(@attributes
[$($path)*][$($tt)*] -> {
$($expr)*
.ok_or_else(|| {
let msg = stringify!($($path)*);
$crate::extract::Error::new(msg)
})?
}
)
};
(@attributes [$($path:tt)*][.$ident:ident $($tt:tt)*] -> { $($expr:tt)* } ) => {
$crate::extract!(@attributes
[$($path)*.$ident][$($tt)*] -> {
$($expr)*
.$ident
}
)
};
}