Skip to main content

sui_gql_client/queries/model/
fragments.rs

1use af_sui_types::{
2    Address as SuiAddress, Object as ObjectSdk, Transaction as TransactionSdk, TypeTag, Version,
3};
4use cynic::QueryFragment;
5use enum_as_inner::EnumAsInner;
6use sui_gql_schema::scalars::{self, BigInt};
7use sui_gql_schema::schema;
8
9// ====================================================================================================
10//  Query Input Fragments
11// ====================================================================================================
12
13/// This is only used in `Query.multiGetObjects` currently
14#[derive(cynic::InputObject, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct ObjectKey {
16    pub address: SuiAddress,
17    pub version: Option<Version>,
18    pub root_version: Option<Version>,
19    pub at_checkpoint: Option<u64>,
20}
21
22#[derive(cynic::InputObject, Clone, Debug, Default)]
23#[cynic(graphql_type = "ObjectFilter")]
24pub(crate) struct ObjectFilter {
25    /// Filter objects by their type's `package`, `package::module`, or their fully qualified type
26    /// name.
27    ///
28    /// Generic types can be queried by either the generic type name, e.g. `0x2::coin::Coin`, or by
29    /// the full type name, such as `0x2::coin::Coin<0x2::sui::SUI>`.
30    #[cynic(rename = "type")]
31    pub(crate) type_: Option<String>,
32    pub(crate) owner: Option<SuiAddress>,
33    pub(crate) owner_kind: Option<OwnerKind>,
34}
35
36#[derive(Clone, Debug, cynic::Enum)]
37pub enum OwnerKind {
38    Address,
39    Object,
40    Shared,
41    Immutable,
42}
43
44#[derive(cynic::InputObject, Clone, Debug)]
45pub struct DynamicFieldName {
46    /// The string type of the DynamicField's 'name' field.
47    /// A string representation of a Move primitive like 'u64', or a struct type like '0x2::kiosk::Listing'
48    #[cynic(rename = "type")]
49    pub type_: scalars::TypeTag,
50    /// The Base64 encoded bcs serialization of the DynamicField's 'name' field.
51    pub bcs: scalars::Base64<Vec<u8>>,
52}
53
54impl<T: af_move_type::MoveType> TryFrom<af_move_type::MoveInstance<T>> for DynamicFieldName {
55    type Error = bcs::Error;
56
57    fn try_from(value: af_move_type::MoveInstance<T>) -> Result<Self, Self::Error> {
58        let af_move_type::MoveInstance { type_, value } = value;
59        Ok(Self {
60            type_: scalars::TypeTag(type_.into()),
61            bcs: scalars::Base64::new(bcs::to_bytes(&value)?),
62        })
63    }
64}
65
66#[derive(cynic::InputObject, Clone, Debug, Default)]
67pub struct TransactionFilter {
68    pub function: Option<String>,
69    pub kind: Option<TransactionKindInput>,
70    pub after_checkpoint: Option<Version>,
71    pub at_checkpoint: Option<Version>,
72    pub before_checkpoint: Option<Version>,
73    pub affected_address: Option<SuiAddress>,
74    pub sent_address: Option<SuiAddress>,
75    pub affected_object: Option<SuiAddress>,
76}
77
78#[derive(cynic::Enum, Clone, Debug)]
79pub enum TransactionKindInput {
80    SystemTx,
81    ProgrammableTx,
82}
83
84// ====================================================================================================
85//  PageInfo Fragments
86// ====================================================================================================
87
88#[derive(cynic::QueryFragment, Clone, Debug, Default)]
89pub struct PageInfo {
90    pub has_next_page: bool,
91    pub end_cursor: Option<String>,
92    pub has_previous_page: bool,
93    pub start_cursor: Option<String>,
94}
95
96impl From<PageInfoForward> for PageInfo {
97    fn from(
98        PageInfoForward {
99            has_next_page,
100            end_cursor,
101        }: PageInfoForward,
102    ) -> Self {
103        Self {
104            has_next_page,
105            end_cursor,
106            ..Default::default()
107        }
108    }
109}
110
111impl From<PageInfoBackward> for PageInfo {
112    fn from(
113        PageInfoBackward {
114            has_previous_page,
115            start_cursor,
116        }: PageInfoBackward,
117    ) -> Self {
118        Self {
119            has_previous_page,
120            start_cursor,
121            ..Default::default()
122        }
123    }
124}
125
126#[derive(cynic::QueryFragment, Clone, Debug)]
127#[cynic(graphql_type = "PageInfo")]
128pub struct PageInfoForward {
129    pub has_next_page: bool,
130    pub end_cursor: Option<String>,
131}
132
133#[derive(cynic::QueryFragment, Clone, Debug)]
134#[cynic(graphql_type = "PageInfo")]
135pub struct PageInfoBackward {
136    pub has_previous_page: bool,
137    pub start_cursor: Option<String>,
138}
139
140// =============================================================================
141//  Inner Fragments
142// =============================================================================
143
144#[derive(cynic::QueryFragment, Clone, Debug)]
145#[cynic(graphql_type = "MoveValue")]
146pub struct MoveValueGql {
147    #[cynic(rename = "type")]
148    pub type_: Option<MoveTypeGql>,
149    pub bcs: Option<scalars::Base64<Vec<u8>>>,
150}
151
152/// `ObjectConnection` where the `Object` fragment does take any parameters.
153#[derive(cynic::QueryFragment, Clone, Debug)]
154pub struct ObjectConnection {
155    pub nodes: Vec<ObjectGql>,
156    pub page_info: PageInfoForward,
157}
158
159#[derive(cynic::QueryFragment, Debug, Clone)]
160#[cynic(graphql_type = "Object")]
161pub struct ObjectGql {
162    #[cynic(rename = "address")]
163    pub id: SuiAddress,
164    #[cynic(rename = "objectBcs")]
165    pub object: Option<scalars::Base64Bcs<ObjectSdk>>,
166}
167
168impl TryFrom<MoveValueGql> for super::outputs::RawMoveValue {
169    type Error = TryFromMoveValue;
170    fn try_from(MoveValueGql { type_, bcs }: MoveValueGql) -> Result<Self, Self::Error> {
171        let (Some(type_), Some(bcs)) = (type_, bcs) else {
172            return Err(TryFromMoveValue::MissingData);
173        };
174
175        Ok(Self {
176            type_: type_.into(),
177            bcs: bcs.into_inner(),
178        })
179    }
180}
181
182impl TryFrom<MoveValueGql> for super::outputs::RawMoveStruct {
183    type Error = TryFromMoveValue;
184    fn try_from(MoveValueGql { type_, bcs }: MoveValueGql) -> Result<Self, Self::Error> {
185        let (Some(type_), Some(bcs)) = (type_, bcs) else {
186            return Err(TryFromMoveValue::MissingData);
187        };
188        let tag: TypeTag = type_.into();
189        let TypeTag::Struct(stag) = tag else {
190            return Err(TryFromMoveValue::NotMoveStructError);
191        };
192
193        Ok(Self {
194            type_: *stag,
195            bcs: bcs.into_inner(),
196        })
197    }
198}
199
200impl<T> TryFrom<MoveValueGql> for af_move_type::MoveInstance<T>
201where
202    T: af_move_type::MoveType,
203{
204    type Error = ToMoveInstanceError;
205    fn try_from(MoveValueGql { bcs, type_ }: MoveValueGql) -> Result<Self, Self::Error> {
206        let (Some(type_), Some(bcs)) = (type_, bcs) else {
207            return Err(TryFromMoveValue::MissingData.into());
208        };
209        // Fail early if type tag is not expected
210        let type_ = TypeTag::from(type_).try_into()?;
211        let value = bcs::from_bytes(bcs.as_ref())?;
212        Ok(Self { type_, value })
213    }
214}
215
216/// Helper to extract a strongly typed [`TypeTag`] from the `MoveType` GQL type.
217#[derive(cynic::QueryFragment, Clone)]
218#[cynic(graphql_type = "MoveType")]
219pub struct MoveTypeGql {
220    /// Keep this private so that we can change where we get the [TypeTag] from.
221    repr: scalars::TypeTag,
222}
223
224impl std::fmt::Debug for MoveTypeGql {
225    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
226        write!(f, "MoveTypeTag({})", self.repr.0)
227    }
228}
229
230impl From<MoveTypeGql> for TypeTag {
231    fn from(value: MoveTypeGql) -> Self {
232        value.repr.0
233    }
234}
235
236#[derive(cynic::QueryFragment, Debug)]
237#[cynic(graphql_type = "DynamicField")]
238pub struct DynamicFieldByName {
239    pub value: Option<DynamicFieldValue>,
240}
241
242#[derive(cynic::QueryFragment, Debug)]
243pub struct DynamicFieldConnection {
244    pub nodes: Vec<DynamicField>,
245    pub page_info: PageInfo,
246}
247
248#[derive(cynic::QueryFragment, Debug)]
249pub struct DynamicField {
250    pub name: Option<MoveValueGql>,
251    pub value: Option<DynamicFieldValue>,
252}
253
254#[derive(cynic::InlineFragments, Debug, EnumAsInner)]
255pub enum DynamicFieldValue {
256    MoveObject(MoveObject),
257    MoveValue(MoveValueGql),
258    #[cynic(fallback)]
259    Unknown,
260}
261
262#[derive(cynic::QueryFragment, Debug)]
263pub struct MoveObject {
264    pub address: SuiAddress,
265    pub version: Option<Version>,
266    pub contents: Option<MoveValueGql>,
267}
268
269#[derive(cynic::QueryFragment, Debug, Clone)]
270pub struct Checkpoint {
271    pub sequence_number: af_sui_types::Version,
272}
273
274#[derive(QueryFragment, Clone, Debug)]
275pub struct Epoch {
276    pub epoch_id: Version,
277    pub reference_gas_price: Option<BigInt<u64>>,
278}
279
280#[derive(cynic::QueryFragment, Debug, Clone)]
281#[cynic(graphql_type = "Transaction")]
282pub struct TransactionGql {
283    pub digest: String,
284    #[cynic(rename = "transactionBcs")]
285    pub bcs: Option<scalars::Base64Bcs<TransactionSdk>>,
286    pub effects: Option<TransactionEffects>,
287}
288
289#[derive(cynic::QueryFragment, Debug, Clone)]
290pub struct TransactionEffects {
291    pub status: Option<ExecutionStatus>,
292}
293
294#[derive(cynic::Enum, Clone, Copy, Debug)]
295pub enum ExecutionStatus {
296    Success,
297    Failure,
298}
299
300impl From<ExecutionStatus> for bool {
301    fn from(value: ExecutionStatus) -> Self {
302        match value {
303            ExecutionStatus::Success => true,
304            ExecutionStatus::Failure => false,
305        }
306    }
307}
308
309// ====================================================================================================
310//  Events
311// ====================================================================================================
312
313#[derive(cynic::InputObject, Debug, Clone)]
314pub struct EventFilter {
315    pub sender: Option<SuiAddress>,
316    pub after_checkpoint: Option<u64>,
317    pub before_checkpoint: Option<u64>,
318    pub at_checkpoint: Option<u64>,
319    #[cynic(rename = "type")]
320    pub type_: Option<String>,
321    pub module: Option<String>,
322}
323
324#[derive(cynic::QueryFragment, Debug, Clone)]
325pub struct EventEdge {
326    pub node: Event,
327    pub cursor: String,
328}
329
330#[derive(cynic::QueryFragment, Debug, Clone)]
331pub struct Event {
332    pub timestamp: Option<scalars::DateTime>,
333    pub contents: Option<MoveValueGql>,
334}
335
336// ====================================================================================================
337//  Packages
338// ====================================================================================================
339
340#[derive(cynic::QueryFragment, Debug)]
341pub struct MovePackageConnection {
342    pub nodes: Vec<MovePackage>,
343    pub page_info: PageInfoForward,
344}
345
346#[derive(cynic::QueryFragment, Debug)]
347pub struct MovePackage {
348    pub address: SuiAddress,
349    pub version: Option<Version>,
350}
351
352// ====================================================================================================
353//  Errors
354// ====================================================================================================
355
356#[derive(thiserror::Error, Debug)]
357pub enum ToMoveInstanceError {
358    #[error("Mismatched types: {0}")]
359    TypeTag(#[from] af_move_type::TypeTagError),
360    #[error("Deserializing value: {0}")]
361    Bcs(#[from] bcs::Error),
362    #[error("Deserializing value: {0}")]
363    TryFromMoveValue(#[from] TryFromMoveValue),
364}
365
366#[derive(thiserror::Error, Debug)]
367pub enum TryFromMoveValue {
368    #[error("Either type or bcs are missing")]
369    MissingData,
370
371    #[error("TypeTag is not a Struct variant")]
372    NotMoveStructError,
373}