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