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