sui_gql_client/queries/
object_args_and_content.rs

1use af_sui_types::{Address, ObjectArg, Version};
2use futures::TryStreamExt as _;
3use itertools::Itertools as _;
4use sui_gql_schema::{scalars, schema};
5
6use super::fragments::{MoveObjectContent, MoveValueRaw};
7use super::object_args::{ObjectOwner, build_oarg_set_mut};
8use super::objects_flat;
9use super::objects_flat::Variables;
10use crate::queries::fragments::ObjectFilterV2;
11use crate::queries::outputs::RawMoveStruct;
12use crate::{GraphQlClient, GraphQlErrors, GraphQlResponseExt as _};
13
14type Query = objects_flat::Query<Object>;
15
16// WARN: can't call this `Result`, otherwise it will mess with the code generated by the derive
17// macros in this module.
18type Res<T, C> = std::result::Result<T, Error<<C as GraphQlClient>::Error>>;
19
20#[derive(thiserror::Error, Debug)]
21pub enum Error<T> {
22    #[error(transparent)]
23    Client(T),
24    #[error(transparent)]
25    Server(#[from] GraphQlErrors),
26    #[error("No data in object args query response")]
27    NoData,
28    #[error("Missing data for object: {0}")]
29    MissingObject(Address),
30}
31
32/// Get a sequence of object args and contents corresponding to `object_ids`, but not
33/// necessarily in the same order.
34///
35/// The `mutable` argument controls whether we want to create mutable [`ObjectArg`]s, if they
36/// are of the [`ObjectArg::SharedObject`] variant.
37///
38/// Fails if any object in the response is missing data.
39pub async fn query<C: GraphQlClient>(
40    client: &C,
41    object_ids: impl IntoIterator<Item = Address> + Send,
42    mutable: bool,
43    page_size: Option<u32>,
44) -> Result<Vec<(ObjectArg, RawMoveStruct)>, Error<C::Error>> {
45    let object_ids = object_ids.into_iter().collect_vec();
46    let filter = ObjectFilterV2 {
47        object_ids: Some(&object_ids),
48        type_: None,
49        owner: None,
50    };
51    let vars = Variables {
52        filter: Some(filter),
53        after: None,
54        first: page_size.map(|n| n as i32),
55    };
56
57    let mut stream = std::pin::pin!(super::stream::forward(client, vars, request));
58
59    let mut result = vec![];
60    while let Some(Object {
61        object_id,
62        version,
63        digest,
64        owner,
65        as_move_object,
66    }) = stream.try_next().await?
67    {
68        let oarg = build_oarg_set_mut(object_id, version, owner, digest, mutable)
69            .ok_or_else(|| Error::MissingObject(object_id))?;
70        let content = as_move_object
71            .and_then(|c| c.into_content())
72            .ok_or_else(|| Error::MissingObject(object_id))?
73            .try_into()
74            .expect("Only Move structs can be top-level objects");
75        result.push((oarg, content));
76    }
77
78    Ok(result)
79}
80
81async fn request<C: GraphQlClient>(
82    client: &C,
83    vars: Variables<'_>,
84) -> Res<super::stream::Page<impl Iterator<Item = Res<Object, C>> + 'static + use<C>>, C> {
85    let objects = client
86        .query::<Query, _>(vars)
87        .await
88        .map_err(Error::Client)?
89        .try_into_data()?
90        .ok_or(Error::NoData)?
91        .objects;
92
93    Ok(super::stream::Page {
94        info: objects.page_info.into(),
95        data: objects.nodes.into_iter().map(Ok),
96    })
97}
98
99#[cfg(test)]
100#[allow(clippy::unwrap_used)]
101#[test]
102fn gql_output() {
103    use cynic::QueryBuilder as _;
104    let vars = Variables {
105        filter: None,
106        first: None,
107        after: None,
108    };
109    let operation = Query::build(vars);
110    insta::assert_snapshot!(operation.query, @r###"
111    query Query($filter: ObjectFilter, $after: String, $first: Int) {
112      objects(filter: $filter, first: $first, after: $after) {
113        nodes {
114          address
115          version
116          digest
117          owner {
118            __typename
119            ... on Immutable {
120              _
121            }
122            ... on Shared {
123              __typename
124              initialSharedVersion
125            }
126            ... on Parent {
127              __typename
128            }
129            ... on AddressOwner {
130              __typename
131            }
132          }
133          asMoveObject {
134            contents {
135              type {
136                repr
137              }
138              bcs
139            }
140          }
141        }
142        pageInfo {
143          hasNextPage
144          endCursor
145        }
146      }
147    }
148    "###);
149}
150
151// =============================================================================
152//  Inner query fragments
153// =============================================================================
154
155#[derive(cynic::QueryFragment, Debug)]
156struct Object {
157    #[cynic(rename = "address")]
158    object_id: Address,
159    version: Version,
160    digest: Option<scalars::Digest>,
161    owner: Option<ObjectOwner>,
162    as_move_object: Option<MoveObjectContent<MoveValueRaw>>,
163}