sui_gql_client/queries/
full_objects.rs

1use std::collections::HashMap;
2
3use af_sui_types::{Object, ObjectId};
4use futures::{StreamExt as _, TryStreamExt as _};
5use graphql_extract::extract;
6use itertools::Itertools as _;
7use sui_gql_schema::scalars::Base64Bcs;
8
9use super::fragments::{ObjectFilterV2, PageInfo, PageInfoForward};
10use super::stream;
11use crate::queries::Error;
12use crate::{missing_data, schema, GraphQlClient, GraphQlResponseExt as _};
13
14pub(super) async fn query<C: GraphQlClient>(
15    client: &C,
16    objects: impl IntoIterator<Item = ObjectId> + Send,
17    page_size: Option<u32>,
18) -> Result<HashMap<ObjectId, Object>, Error<C::Error>> {
19    // To keep track of all ids requested.
20    let object_ids = objects.into_iter().collect_vec();
21
22    let filter = ObjectFilterV2 {
23        object_ids: Some(&object_ids),
24        ..Default::default()
25    };
26    let vars = Variables {
27        after: None,
28        first: page_size.map(|v| v.try_into().unwrap_or(i32::MAX)),
29        filter: Some(filter),
30    };
31
32    let raw_objs: HashMap<_, _> = stream::forward(client, vars, request)
33        .map(|r| -> super::Result<_, C> {
34            let (id, obj) = r?;
35            Ok((id, obj.ok_or_else(|| missing_data!("BCS for {id}"))?))
36        })
37        .try_collect()
38        .await?;
39
40    // Ensure all requested objects were returned
41    for id in object_ids {
42        raw_objs
43            .contains_key(&id)
44            .then_some(())
45            .ok_or(missing_data!("Object {id}"))?;
46    }
47
48    Ok(raw_objs)
49}
50
51async fn request<C: GraphQlClient>(
52    client: &C,
53    vars: Variables<'_>,
54) -> super::Result<
55    stream::Page<impl Iterator<Item = super::Result<(ObjectId, Option<Object>), C>> + 'static>,
56    C,
57> {
58    let data = client
59        .query::<Query, _>(vars)
60        .await
61        .map_err(Error::Client)?
62        .try_into_data()?;
63
64    extract!(data => {
65        objects {
66            nodes[] {
67                id
68                object
69            }
70            page_info
71        }
72    });
73    Ok(stream::Page::new(
74        page_info,
75        nodes.map(|r| -> super::Result<_, C> {
76            let (id, obj) = r?;
77            Ok((id, obj.map(Base64Bcs::into_inner)))
78        }),
79    ))
80}
81
82#[derive(cynic::QueryVariables, Clone, Debug)]
83struct Variables<'a> {
84    filter: Option<ObjectFilterV2<'a>>,
85    after: Option<String>,
86    first: Option<i32>,
87}
88
89impl stream::UpdatePageInfo for Variables<'_> {
90    fn update_page_info(&mut self, info: &PageInfo) {
91        self.after.clone_from(&info.end_cursor);
92    }
93}
94
95#[derive(cynic::QueryFragment, Clone, Debug)]
96#[cynic(variables = "Variables")]
97struct Query {
98    #[arguments(filter: $filter, first: $first, after: $after)]
99    objects: ObjectConnection,
100}
101
102// =============================================================================
103//  Inner query fragments
104// =============================================================================
105
106/// `ObjectConnection` where the `Object` fragment does take any parameters.
107#[derive(cynic::QueryFragment, Clone, Debug)]
108struct ObjectConnection {
109    nodes: Vec<ObjectGql>,
110    page_info: PageInfoForward,
111}
112
113#[derive(cynic::QueryFragment, Debug, Clone)]
114#[cynic(graphql_type = "Object")]
115struct ObjectGql {
116    #[cynic(rename = "address")]
117    id: ObjectId,
118    #[cynic(rename = "bcs")]
119    object: Option<Base64Bcs<Object>>,
120}
121
122#[cfg(test)]
123#[allow(clippy::unwrap_used)]
124#[test]
125fn gql_output() -> color_eyre::Result<()> {
126    use cynic::QueryBuilder as _;
127
128    // Variables don't matter, we just need it so taht `Query::build()` compiles
129    let vars = Variables {
130        filter: Some(Default::default()),
131        after: None,
132        first: None,
133    };
134
135    let operation = Query::build(vars);
136    insta::assert_snapshot!(operation.query, @r###"
137    query Query($filter: ObjectFilter, $after: String, $first: Int) {
138      objects(filter: $filter, first: $first, after: $after) {
139        nodes {
140          address
141          bcs
142        }
143        pageInfo {
144          hasNextPage
145          endCursor
146        }
147      }
148    }
149    "###);
150    Ok(())
151}