sui_gql_client/queries/
object_args_and_content.rs

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