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