sui_gql_client/extract.rs
1//! Defines [`extract!`](crate::extract!) and its [`Error`].
2
3#[cfg(feature = "queries")]
4pub(crate) type Result<T> = std::result::Result<T, Error>;
5
6/// Error for [`extract!`](crate::extract!).
7#[derive(thiserror::Error, Debug)]
8#[error("Missing data from response: {0}")]
9pub struct Error(pub(crate) String);
10
11impl Error {
12 pub fn new(s: impl Into<String>) -> Self {
13 Self(s.into())
14 }
15}
16
17/// Helper for extracting data from GraphQL responses.
18///
19/// Designed specially to deal with the multitude of nested [`Option`]s commonly found in GQL
20/// responses. The macro will generate an error string with the full path to the missing attribute.
21///
22/// # Example
23///
24/// ```no_run
25/// # use af_sui_types::Address;
26/// # use sui_gql_schema::{schema, scalars};
27/// #
28/// # #[derive(cynic::QueryVariables, Debug)]
29/// # struct QueryVariables<'a> {
30/// # ch: Address,
31/// # vault: DynamicFieldName<'a>,
32/// # }
33/// #
34/// # #[derive(cynic::InputObject, Debug)]
35/// # struct DynamicFieldName<'a> {
36/// # #[cynic(rename = "type")]
37/// # type_: &'a str,
38/// # bcs: scalars::Base64<Vec<u8>>,
39/// # }
40/// #
41/// #[derive(cynic::QueryFragment, Debug)]
42/// #[cynic(variables = "QueryVariables")]
43/// struct Query {
44/// #[arguments(address: $ch)]
45/// object: Option<Object>,
46/// }
47///
48/// #[derive(cynic::QueryFragment, Debug)]
49/// #[cynic(variables = "QueryVariables")]
50/// struct Object {
51/// #[arguments(name: $vault)]
52/// dynamic_field: Option<DynamicField>,
53/// }
54///
55/// #[derive(cynic::QueryFragment, Debug)]
56/// struct DynamicField {
57/// value: Option<DynamicFieldValue>,
58/// }
59///
60/// #[derive(cynic::InlineFragments, Debug)]
61/// enum DynamicFieldValue {
62/// MoveValue(MoveValue),
63/// #[cynic(fallback)]
64/// Unknown
65/// }
66///
67/// #[derive(cynic::QueryFragment, Debug)]
68/// struct MoveValue {
69/// #[cynic(rename = "type")]
70/// type_: MoveType,
71/// bcs: scalars::Base64<Vec<u8>>,
72/// __typename: String,
73/// }
74///
75/// #[derive(cynic::QueryFragment, Debug)]
76/// struct MoveType {
77/// repr: String,
78/// }
79///
80/// use sui_gql_client::extract;
81///
82/// // Could be obtained in practice from `sui_gql_client::GraphQlResponseExt::try_into_data`
83/// let data: Option<Query> = None;
84/// let df_value: MoveValue = extract!(
85/// data?.object?.dynamic_field?.value?.as_variant(DynamicFieldValue::MoveValue)
86/// );
87/// # color_eyre::eyre::Ok(())
88/// ```
89#[macro_export]
90macro_rules! extract {
91 ($ident:ident $($tt:tt)*) => {{
92 $crate::extract!( @attributes [$ident][$($tt)*] -> {
93 $ident
94 } )
95 }};
96
97 (@attributes [$($path:tt)*][] -> { $($expr:tt)* } ) => { $($expr)* };
98
99 (@attributes [$($path:tt)*][.as_variant($($var:tt)+) $($tt:tt)*] -> { $($expr:tt)* } ) => {
100 $crate::extract!(@attributes
101 [$($path)*.as_variant($($var)*)][$($tt)*] -> {{
102 let value = $($expr)*;
103 let $($var)+(var) = value else {
104 let msg = stringify!($($path)*.as_variant($($var)*));
105 return Err($crate::extract::Error::new(msg).into());
106 };
107 var
108 }}
109 )
110 };
111
112 (@attributes [$($path:tt)*][? $($tt:tt)*] -> { $($expr:tt)* } ) => {
113 $crate::extract!(@attributes
114 [$($path)*][$($tt)*] -> {
115 $($expr)*
116 .ok_or_else(|| {
117 let msg = stringify!($($path)*);
118 $crate::extract::Error::new(msg)
119 })?
120 }
121 )
122 };
123
124 (@attributes [$($path:tt)*][.$ident:ident $($tt:tt)*] -> { $($expr:tt)* } ) => {
125 $crate::extract!(@attributes
126 [$($path)*.$ident][$($tt)*] -> {
127 $($expr)*
128 .$ident
129 }
130 )
131 };
132}