Skip to main content

sui_gql_client/queries/
object_dfs.rs

1use af_sui_types::Address as SuiAddress;
2use futures_core::Stream;
3
4use super::model::fragments;
5use super::model::outputs::{DynamicField as OutputDf, RawMoveValue};
6use super::{Error, stream};
7use crate::queries::model::fragments::{
8    DynamicField, DynamicFieldConnection, DynamicFieldValue, MoveObject, ObjectKey,
9};
10use crate::{GraphQlClient, GraphQlResponseExt as _, missing_data, schema};
11
12#[derive(cynic::QueryVariables, Debug, Clone)]
13struct Variables {
14    address: SuiAddress,
15    at_checkpoint: Option<u64>,
16    after: Option<String>,
17    first: Option<i32>,
18}
19
20#[derive(cynic::QueryFragment, Debug)]
21#[cynic(variables = "Variables")]
22struct Query {
23    #[arguments(address: $address, atCheckpoint: $at_checkpoint)]
24    pub object: Option<ObjectDfs>,
25}
26
27#[derive(cynic::QueryFragment, Debug)]
28#[cynic(graphql_type = "Object", variables = "Variables")]
29struct ObjectDfs {
30    #[arguments(first: $first, after: $after)]
31    pub dynamic_fields: Option<DynamicFieldConnection>,
32}
33
34pub async fn query<C: GraphQlClient>(
35    client: &C,
36    address: SuiAddress,
37    at_checkpoint: Option<u64>,
38    page_size: Option<i32>,
39) -> impl Stream<Item = super::Result<(RawMoveValue, OutputDf), C>> + '_ {
40    let vars = Variables {
41        address,
42        at_checkpoint,
43        first: page_size,
44        after: None,
45    };
46
47    stream::forward(client, vars, request)
48}
49
50async fn request<C: GraphQlClient>(
51    client: &C,
52    vars: Variables,
53) -> super::Result<
54    stream::Page<
55        impl Iterator<Item = super::Result<(RawMoveValue, OutputDf), C>> + 'static + use<C>,
56    >,
57    C,
58> {
59    let at_checkpoint = vars.at_checkpoint;
60    let data = client
61        .query::<Query, _>(vars)
62        .await
63        .map_err(Error::Client)?
64        .try_into_data()?
65        .ok_or(missing_data!("Response empty"))?;
66
67    let DynamicFieldConnection { nodes, page_info } = data
68        .object
69        .ok_or(missing_data!("No parent object found"))?
70        .dynamic_fields
71        .ok_or(missing_data!("No dynamic fields found"))?;
72
73    let data = nodes.into_iter().map(move |DynamicField { name, value }| {
74        let name = name
75            .ok_or(missing_data!("Dynamic field found but with no name"))?
76            .try_into()
77            .map_err(|e| missing_data!("Dynamic field name content empty. Error: {e}"))?;
78        let instance = value.ok_or(missing_data!("Dynamic field found but with no value"))?;
79        let out = match instance {
80            DynamicFieldValue::MoveObject(MoveObject {
81                address,
82                version,
83                contents,
84            }) => {
85                let struct_ = contents
86                    .ok_or(missing_data!("No contents for DF"))?
87                    .try_into()
88                    .expect("Only Move structs can be top-level objects");
89                OutputDf::Object(
90                    ObjectKey {
91                        version,
92                        address,
93                        root_version: None,
94                        at_checkpoint,
95                    },
96                    struct_,
97                )
98            }
99            DynamicFieldValue::MoveValue(value) => OutputDf::Field(
100                value
101                    .try_into()
102                    .map_err(|e| missing_data!("Dynamic field name content empty. Error: {e}"))?,
103            ),
104            DynamicFieldValue::Unknown => return Err(missing_data!("Unknown dynamic field type")),
105        };
106        Ok((name, out))
107    });
108
109    Ok(stream::Page {
110        info: page_info,
111        data,
112    })
113}
114
115#[cfg(test)]
116#[allow(clippy::unwrap_used)]
117#[test]
118fn gql_output() {
119    use cynic::QueryBuilder as _;
120    let vars = Variables {
121        address: SuiAddress::new(rand::random()),
122        at_checkpoint: None,
123        first: None,
124        after: None,
125    };
126    let operation = Query::build(vars);
127    insta::assert_snapshot!(operation.query, @r"
128    query Query($address: SuiAddress!, $atCheckpoint: UInt53, $after: String, $first: Int) {
129      object(address: $address, atCheckpoint: $atCheckpoint) {
130        dynamicFields(first: $first, after: $after) {
131          nodes {
132            name {
133              type {
134                repr
135              }
136              bcs
137            }
138            value {
139              __typename
140              ... on MoveObject {
141                address
142                version
143                contents {
144                  type {
145                    repr
146                  }
147                  bcs
148                }
149              }
150              ... on MoveValue {
151                type {
152                  repr
153                }
154                bcs
155              }
156            }
157          }
158          pageInfo {
159            hasNextPage
160            endCursor
161            hasPreviousPage
162            startCursor
163          }
164        }
165      }
166    }
167    ");
168}
169
170impl stream::UpdatePageInfo for Variables {
171    fn update_page_info(&mut self, info: &fragments::PageInfo) {
172        self.after.clone_from(&info.end_cursor)
173    }
174}