Skip to main content

iota_json_rpc_types/
iota_object.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{
6    cmp::Ordering,
7    collections::BTreeMap,
8    fmt,
9    fmt::{Display, Formatter, Write},
10};
11
12use anyhow::{anyhow, bail};
13use colored::Colorize;
14use fastcrypto::encoding::Base64;
15use iota_protocol_config::ProtocolConfig;
16use iota_sdk_types::{
17    Address, Identifier, MovePackage, MoveStruct, ObjectData, ObjectDigest, ObjectId,
18    ObjectReference, Owner, StructTag, TransactionDigest, TypeOrigin, UpgradeInfo, Version,
19};
20use iota_types::{
21    base_types::{ObjectInfo, ObjectType},
22    error::{IotaError, IotaResult, UserInputError, UserInputResult},
23    gas_coin::GasCoin,
24    messages_checkpoint::CheckpointSequenceNumber,
25    object::{MoveStructExt, Object, ObjectInner, ObjectRead},
26};
27use move_bytecode_utils::module_cache::GetModule;
28use move_core_types::annotated_value::{MoveStructLayout, MoveValue};
29use schemars::JsonSchema;
30use serde::{Deserialize, Serialize};
31use serde_json::Value;
32use serde_with::{DeserializeAs, DisplayFromStr, SerializeAs, serde_as};
33
34use crate::{
35    IotaMoveStruct, IotaMoveValue, IotaObjectResponseError, Page,
36    iota_owner::OwnerSchema,
37    iota_primitives::{
38        Address as AddressSchema, Base58 as Base58Schema, Base64 as Base64Schema,
39        Identifier as IdentifierSchema, ObjectId as ObjectIdSchema,
40        SequenceNumberString as SequenceNumberStringSchema, SequenceNumberU64,
41        StructTag as StructTagSchema,
42    },
43};
44
45#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone, PartialEq, Eq)]
46pub struct IotaObjectResponse {
47    #[serde(skip_serializing_if = "Option::is_none")]
48    pub data: Option<IotaObjectData>,
49    #[serde(skip_serializing_if = "Option::is_none")]
50    pub error: Option<IotaObjectResponseError>,
51}
52
53impl IotaObjectResponse {
54    pub fn new(data: Option<IotaObjectData>, error: Option<IotaObjectResponseError>) -> Self {
55        Self { data, error }
56    }
57
58    pub fn new_with_data(data: IotaObjectData) -> Self {
59        Self {
60            data: Some(data),
61            error: None,
62        }
63    }
64
65    pub fn new_with_error(error: IotaObjectResponseError) -> Self {
66        Self {
67            data: None,
68            error: Some(error),
69        }
70    }
71
72    pub fn try_from_object_read_and_options(
73        object_read: ObjectRead,
74        options: &IotaObjectDataOptions,
75    ) -> anyhow::Result<Self> {
76        match object_read {
77            ObjectRead::NotExists(id) => Ok(IotaObjectResponse::new_with_error(
78                IotaObjectResponseError::NotExists { object_id: id },
79            )),
80            ObjectRead::Exists(object_ref, o, layout) => Ok(IotaObjectResponse::new_with_data(
81                IotaObjectData::new(object_ref, o, layout, options, None)?,
82            )),
83            ObjectRead::Deleted(object_ref) => Ok(IotaObjectResponse::new_with_error(
84                IotaObjectResponseError::Deleted {
85                    object_id: object_ref.object_id,
86                    version: object_ref.version.into(),
87                    digest: object_ref.digest,
88                },
89            )),
90        }
91    }
92}
93
94impl Ord for IotaObjectResponse {
95    fn cmp(&self, other: &Self) -> Ordering {
96        match (&self.data, &other.data) {
97            (Some(data), Some(data_2)) => {
98                if data.object_id.cmp(&data_2.object_id).eq(&Ordering::Greater) {
99                    return Ordering::Greater;
100                } else if data.object_id.cmp(&data_2.object_id).eq(&Ordering::Less) {
101                    return Ordering::Less;
102                }
103                Ordering::Equal
104            }
105            // In this ordering those with data will come before IotaObjectResponses that are
106            // errors.
107            (Some(_), None) => Ordering::Less,
108            (None, Some(_)) => Ordering::Greater,
109            // IotaObjectResponses that are errors are just considered equal.
110            _ => Ordering::Equal,
111        }
112    }
113}
114
115impl PartialOrd for IotaObjectResponse {
116    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
117        Some(self.cmp(other))
118    }
119}
120
121impl IotaObjectResponse {
122    pub fn move_object_bcs(&self) -> Option<&Vec<u8>> {
123        match &self.data {
124            Some(IotaObjectData {
125                bcs: Some(IotaRawData::MoveObject(obj)),
126                ..
127            }) => Some(&obj.bcs_bytes),
128            _ => None,
129        }
130    }
131
132    pub fn owner(&self) -> Option<Owner> {
133        if let Some(data) = &self.data {
134            return data.owner;
135        }
136        None
137    }
138
139    pub fn object_id(&self) -> Result<ObjectId, anyhow::Error> {
140        Ok(match (&self.data, &self.error) {
141            (Some(obj_data), None) => obj_data.object_id,
142            (None, Some(IotaObjectResponseError::NotExists { object_id })) => *object_id,
143            (
144                None,
145                Some(IotaObjectResponseError::Deleted {
146                    object_id,
147                    version: _,
148                    digest: _,
149                }),
150            ) => *object_id,
151            _ => bail!(
152                "Could not get object_id, something went wrong with IotaObjectResponse construction."
153            ),
154        })
155    }
156
157    pub fn object_ref_if_exists(&self) -> Option<ObjectReference> {
158        match (&self.data, &self.error) {
159            (Some(obj_data), None) => Some(obj_data.object_ref()),
160            _ => None,
161        }
162    }
163}
164
165impl TryFrom<IotaObjectResponse> for ObjectInfo {
166    type Error = anyhow::Error;
167
168    fn try_from(value: IotaObjectResponse) -> Result<Self, Self::Error> {
169        let IotaObjectData {
170            object_id,
171            version,
172            digest,
173            object_type,
174            owner,
175            previous_transaction,
176            ..
177        } = value.into_object()?;
178
179        Ok(ObjectInfo {
180            object_id,
181            version,
182            digest,
183            object_type: object_type.ok_or_else(|| anyhow!("Object type not found for object."))?,
184            owner: owner.ok_or_else(|| anyhow!("Owner not found for object."))?,
185            previous_transaction: previous_transaction
186                .ok_or_else(|| anyhow!("Transaction digest not found for object."))?,
187        })
188    }
189}
190
191#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Eq, PartialEq)]
192pub struct DisplayFieldsResponse {
193    pub data: Option<BTreeMap<String, String>>,
194    pub error: Option<IotaObjectResponseError>,
195}
196
197#[serde_as]
198#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Eq, PartialEq)]
199#[serde(rename_all = "camelCase", rename = "ObjectData")]
200pub struct IotaObjectData {
201    #[serde_as(as = "ObjectIdSchema")]
202    #[schemars(with = "ObjectIdSchema")]
203    pub object_id: ObjectId,
204    /// Object version.
205    #[serde_as(as = "SequenceNumberStringSchema")]
206    #[schemars(with = "SequenceNumberStringSchema")]
207    pub version: Version,
208    /// Base64 string representing the object digest
209    #[serde_as(as = "Base58Schema")]
210    #[schemars(with = "Base58Schema")]
211    pub digest: ObjectDigest,
212    /// The type of the object. Default to be None unless
213    /// IotaObjectDataOptions.showType is set to true
214    #[schemars(with = "Option<String>")]
215    #[serde_as(as = "Option<DisplayFromStr>")]
216    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
217    pub object_type: Option<ObjectType>,
218    // Default to be None because otherwise it will be repeated for the getOwnedObjects endpoint
219    /// The owner of this object. Default to be None unless
220    /// IotaObjectDataOptions.showOwner is set to true
221    #[serde(skip_serializing_if = "Option::is_none")]
222    #[schemars(with = "Option<OwnerSchema>")]
223    #[serde_as(as = "Option<OwnerSchema>")]
224    pub owner: Option<Owner>,
225    /// The digest of the transaction that created or last mutated this object.
226    /// Default to be None unless IotaObjectDataOptions.
227    /// showPreviousTransaction is set to true
228    #[serde(skip_serializing_if = "Option::is_none")]
229    #[serde_as(as = "Option<Base58Schema>")]
230    #[schemars(with = "Option<Base58Schema>")]
231    pub previous_transaction: Option<TransactionDigest>,
232    /// The amount of IOTA we would rebate if this object gets deleted.
233    /// This number is re-calculated each time the object is mutated based on
234    /// the present storage gas price.
235    #[schemars(with = "Option<String>")]
236    #[serde_as(as = "Option<DisplayFromStr>")]
237    #[serde(skip_serializing_if = "Option::is_none")]
238    pub storage_rebate: Option<u64>,
239    /// The Display metadata for frontend UI rendering, default to be None
240    /// unless IotaObjectDataOptions.showContent is set to true This can also
241    /// be None if the struct type does not have Display defined
242    #[serde(skip_serializing_if = "Option::is_none")]
243    pub display: Option<DisplayFieldsResponse>,
244    /// Move object content or package content, default to be None unless
245    /// IotaObjectDataOptions.showContent is set to true
246    #[serde(skip_serializing_if = "Option::is_none")]
247    pub content: Option<IotaParsedData>,
248    /// Move object content or package content in BCS, default to be None unless
249    /// IotaObjectDataOptions.showBcs is set to true
250    #[serde(skip_serializing_if = "Option::is_none")]
251    pub bcs: Option<IotaRawData>,
252}
253
254impl IotaObjectData {
255    pub fn new(
256        object_ref: ObjectReference,
257        obj: Object,
258        layout: impl Into<Option<MoveStructLayout>>,
259        options: &IotaObjectDataOptions,
260        display_fields: impl Into<Option<DisplayFieldsResponse>>,
261    ) -> anyhow::Result<Self> {
262        let layout = layout.into();
263        let display_fields = display_fields.into();
264        let show_display = options.show_display;
265        let IotaObjectDataOptions {
266            show_type,
267            show_owner,
268            show_previous_transaction,
269            show_content,
270            show_bcs,
271            show_storage_rebate,
272            ..
273        } = options;
274
275        let ObjectReference {
276            object_id,
277            version,
278            digest,
279        } = object_ref;
280        let object_type = if *show_type {
281            Some(Into::<ObjectType>::into(&obj))
282        } else {
283            None
284        };
285
286        let bcs: Option<IotaRawData> = if *show_bcs {
287            let data = match obj.data.clone() {
288                ObjectData::Struct(m) => {
289                    let layout = layout.clone().ok_or_else(|| {
290                        anyhow!("Layout is required to convert Move object to json")
291                    })?;
292                    IotaRawData::try_from_object(m, layout)?
293                }
294                ObjectData::Package(p) => IotaRawData::try_from_package(p)
295                    .map_err(|e| anyhow!("Error getting raw data from package: {e:#?}"))?,
296                _ => bail!("unknown ObjectData variant"),
297            };
298            Some(data)
299        } else {
300            None
301        };
302
303        let obj = obj.into_inner();
304
305        let content: Option<IotaParsedData> = if *show_content {
306            let data = match obj.data {
307                ObjectData::Struct(m) => {
308                    let layout = layout.ok_or_else(|| {
309                        anyhow!("Layout is required to convert Move object to json")
310                    })?;
311                    IotaParsedData::try_from_object(m, layout)?
312                }
313                ObjectData::Package(p) => IotaParsedData::try_from_package(p)?,
314                _ => bail!("unknown ObjectData variant"),
315            };
316            Some(data)
317        } else {
318            None
319        };
320
321        Ok(IotaObjectData {
322            object_id,
323            version,
324            digest,
325            object_type,
326            owner: if *show_owner { Some(obj.owner) } else { None },
327            storage_rebate: if *show_storage_rebate {
328                Some(obj.storage_rebate)
329            } else {
330                None
331            },
332            previous_transaction: if *show_previous_transaction {
333                Some(obj.previous_transaction)
334            } else {
335                None
336            },
337            content,
338            bcs,
339            display: if show_display { display_fields } else { None },
340        })
341    }
342
343    pub fn object_ref(&self) -> ObjectReference {
344        ObjectReference::new(self.object_id, self.version, self.digest)
345    }
346
347    pub fn object_type(&self) -> anyhow::Result<ObjectType> {
348        self.object_type
349            .as_ref()
350            .ok_or_else(|| anyhow!("type is missing for object {}", self.object_id))
351            .cloned()
352    }
353
354    pub fn is_gas_coin(&self) -> bool {
355        match self.object_type.as_ref() {
356            Some(ObjectType::Struct(ty)) if ty.is_gas_coin() => true,
357            Some(_) => false,
358            None => false,
359        }
360    }
361}
362
363impl Display for IotaObjectData {
364    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
365        let object_type = if let Some(tag) = &self.object_type {
366            tag.to_string()
367        } else {
368            "Unknown Type".into()
369        };
370        let mut writer = String::new();
371        writeln!(
372            writer,
373            "{}",
374            format!(
375                "----- {object_type} ({}[{}]) -----",
376                self.object_id, self.version
377            )
378            .bold()
379        )?;
380        if let Some(owner) = self.owner {
381            writeln!(writer, "{}: {owner}", "Owner".bold().bright_black())?;
382        }
383
384        writeln!(
385            writer,
386            "{}: {}",
387            "Version".bold().bright_black(),
388            self.version
389        )?;
390        if let Some(storage_rebate) = self.storage_rebate {
391            writeln!(
392                writer,
393                "{}: {storage_rebate}",
394                "Storage Rebate".bold().bright_black(),
395            )?;
396        }
397
398        if let Some(previous_transaction) = self.previous_transaction {
399            writeln!(
400                writer,
401                "{}: {previous_transaction:?}",
402                "Previous Transaction".bold().bright_black(),
403            )?;
404        }
405        if let Some(content) = self.content.as_ref() {
406            writeln!(writer, "{}", "----- Data -----".bold())?;
407            write!(writer, "{content}")?;
408        }
409
410        write!(f, "{writer}")
411    }
412}
413
414impl TryFrom<&IotaObjectData> for GasCoin {
415    type Error = anyhow::Error;
416    fn try_from(object: &IotaObjectData) -> Result<Self, Self::Error> {
417        match &object
418            .content
419            .as_ref()
420            .ok_or_else(|| anyhow!("Expect object content to not be empty"))?
421        {
422            IotaParsedData::MoveObject(o) => {
423                if o.struct_tag.is_gas_coin() {
424                    return GasCoin::try_from(&o.fields);
425                }
426            }
427            IotaParsedData::Package(_) => {}
428        }
429
430        bail!(
431            "Gas object type is not a gas coin: {:?}",
432            object.object_type
433        )
434    }
435}
436
437impl TryFrom<&IotaMoveStruct> for GasCoin {
438    type Error = anyhow::Error;
439    fn try_from(move_struct: &IotaMoveStruct) -> Result<Self, Self::Error> {
440        match move_struct {
441            IotaMoveStruct::WithFields(fields)
442            | IotaMoveStruct::WithTypes {
443                struct_tag: _,
444                fields,
445            } => {
446                if let Some(IotaMoveValue::String(balance)) = fields.get("balance") {
447                    if let Ok(balance) = balance.parse::<u64>() {
448                        if let Some(IotaMoveValue::UID { id }) = fields.get("id") {
449                            return Ok(GasCoin::new(*id, balance));
450                        }
451                    }
452                }
453            }
454            _ => {}
455        }
456        bail!("Struct is not a gas coin: {move_struct:?}")
457    }
458}
459
460#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Eq, PartialEq, Default)]
461#[serde(rename_all = "camelCase", rename = "ObjectDataOptions", default)]
462pub struct IotaObjectDataOptions {
463    /// Whether to show the type of the object. Default to be False
464    pub show_type: bool,
465    /// Whether to show the owner of the object. Default to be False
466    pub show_owner: bool,
467    /// Whether to show the previous transaction digest of the object. Default
468    /// to be False
469    pub show_previous_transaction: bool,
470    /// Whether to show the Display metadata of the object for frontend
471    /// rendering. Default to be False
472    pub show_display: bool,
473    /// Whether to show the content(i.e., package content or Move struct
474    /// content) of the object. Default to be False
475    pub show_content: bool,
476    /// Whether to show the content in BCS format. Default to be False
477    pub show_bcs: bool,
478    /// Whether to show the storage rebate of the object. Default to be False
479    pub show_storage_rebate: bool,
480}
481
482impl IotaObjectDataOptions {
483    pub fn new() -> Self {
484        Self::default()
485    }
486
487    /// return BCS data and all other metadata such as storage rebate
488    pub fn bcs_lossless() -> Self {
489        Self {
490            show_bcs: true,
491            show_type: true,
492            show_owner: true,
493            show_previous_transaction: true,
494            show_display: false,
495            show_content: false,
496            show_storage_rebate: true,
497        }
498    }
499
500    /// return full content except bcs
501    pub fn full_content() -> Self {
502        Self {
503            show_bcs: false,
504            show_type: true,
505            show_owner: true,
506            show_previous_transaction: true,
507            show_display: false,
508            show_content: true,
509            show_storage_rebate: true,
510        }
511    }
512
513    pub fn with_content(mut self) -> Self {
514        self.show_content = true;
515        self
516    }
517
518    pub fn with_owner(mut self) -> Self {
519        self.show_owner = true;
520        self
521    }
522
523    pub fn with_type(mut self) -> Self {
524        self.show_type = true;
525        self
526    }
527
528    pub fn with_display(mut self) -> Self {
529        self.show_display = true;
530        self
531    }
532
533    pub fn with_bcs(mut self) -> Self {
534        self.show_bcs = true;
535        self
536    }
537
538    pub fn with_previous_transaction(mut self) -> Self {
539        self.show_previous_transaction = true;
540        self
541    }
542
543    pub fn is_not_in_object_info(&self) -> bool {
544        self.show_bcs || self.show_content || self.show_display || self.show_storage_rebate
545    }
546}
547
548impl TryFrom<(ObjectRead, IotaObjectDataOptions)> for IotaObjectResponse {
549    type Error = anyhow::Error;
550
551    fn try_from(
552        (object_read, options): (ObjectRead, IotaObjectDataOptions),
553    ) -> Result<Self, Self::Error> {
554        Self::try_from_object_read_and_options(object_read, &options)
555    }
556}
557
558impl TryFrom<(ObjectInfo, IotaObjectDataOptions)> for IotaObjectResponse {
559    type Error = anyhow::Error;
560
561    fn try_from(
562        (object_info, options): (ObjectInfo, IotaObjectDataOptions),
563    ) -> Result<Self, Self::Error> {
564        let IotaObjectDataOptions {
565            show_type,
566            show_owner,
567            show_previous_transaction,
568            ..
569        } = options;
570
571        Ok(Self::new_with_data(IotaObjectData {
572            object_id: object_info.object_id,
573            version: object_info.version,
574            digest: object_info.digest,
575            object_type: show_type.then_some(object_info.object_type),
576            owner: show_owner.then_some(object_info.owner),
577            previous_transaction: show_previous_transaction
578                .then_some(object_info.previous_transaction),
579            storage_rebate: None,
580            display: None,
581            content: None,
582            bcs: None,
583        }))
584    }
585}
586
587impl IotaObjectResponse {
588    /// Returns a reference to the object if there is any, otherwise an Err if
589    /// the object does not exist or is deleted.
590    pub fn object(&self) -> Result<&IotaObjectData, IotaObjectResponseError> {
591        if let Some(data) = &self.data {
592            Ok(data)
593        } else if let Some(error) = &self.error {
594            Err(error.clone())
595        } else {
596            // We really shouldn't reach this code block since either data, or error field
597            // should always be filled.
598            Err(IotaObjectResponseError::Unknown)
599        }
600    }
601
602    /// Returns the object value if there is any, otherwise an Err if
603    /// the object does not exist or is deleted.
604    pub fn into_object(self) -> Result<IotaObjectData, IotaObjectResponseError> {
605        match self.object() {
606            Ok(data) => Ok(data.clone()),
607            Err(error) => Err(error),
608        }
609    }
610}
611
612impl TryInto<Object> for IotaObjectData {
613    type Error = anyhow::Error;
614
615    fn try_into(self) -> Result<Object, Self::Error> {
616        let protocol_config = ProtocolConfig::get_for_min_version();
617        let data = match self.bcs {
618            Some(IotaRawData::MoveObject(o)) => ObjectData::Struct({
619                MoveStruct::new_from_execution(
620                    o.struct_tag().clone(),
621                    o.version.into(),
622                    o.bcs_bytes,
623                    &protocol_config,
624                    // system_mutation
625                    false,
626                )?
627            }),
628            // Not size-checked: this package is already on-chain, and the limit
629            // the network held it to is not knowable from here.
630            Some(IotaRawData::Package(p)) => ObjectData::Package(MovePackage::new(
631                p.id,
632                self.version,
633                p.module_map
634                    .iter()
635                    .map(|(k, v)| (Identifier::new_unchecked(k), v.clone()))
636                    .collect(),
637                p.type_origin_table.into_iter().collect(),
638                p.linkage_table
639                    .into_iter()
640                    .map(|(k, v)| (k, v.into()))
641                    .collect(),
642            )),
643            _ => Err(anyhow!(
644                "BCS data is required to convert IotaObjectData to Object"
645            ))?,
646        };
647        Ok(ObjectInner {
648            data,
649            owner: self
650                .owner
651                .ok_or_else(|| anyhow!("Owner is required to convert IotaObjectData to Object"))?,
652            previous_transaction: self.previous_transaction.ok_or_else(|| {
653                anyhow!("previous_transaction is required to convert IotaObjectData to Object")
654            })?,
655            storage_rebate: self.storage_rebate.ok_or_else(|| {
656                anyhow!("storage_rebate is required to convert IotaObjectData to Object")
657            })?,
658        }
659        .into())
660    }
661}
662
663#[serde_as]
664#[derive(Deserialize, Serialize, JsonSchema)]
665#[serde(rename_all = "camelCase", rename = "ObjectRef")]
666pub struct ObjectRefSchema {
667    /// Hex code as string representing the object id
668    #[serde_as(as = "ObjectIdSchema")]
669    #[schemars(with = "ObjectIdSchema")]
670    pub object_id: ObjectId,
671    /// Object version.
672    pub version: SequenceNumberU64,
673    /// Base64 string representing the object digest
674    #[serde_as(as = "Base58Schema")]
675    #[schemars(with = "Base58Schema")]
676    pub digest: ObjectDigest,
677}
678
679impl SerializeAs<ObjectReference> for ObjectRefSchema {
680    fn serialize_as<S>(source: &ObjectReference, serializer: S) -> Result<S::Ok, S::Error>
681    where
682        S: serde::Serializer,
683    {
684        let iota_object_ref: ObjectRefSchema = (*source).into();
685        iota_object_ref.serialize(serializer)
686    }
687}
688
689impl<'de> DeserializeAs<'de, ObjectReference> for ObjectRefSchema {
690    fn deserialize_as<D>(deserializer: D) -> Result<ObjectReference, D::Error>
691    where
692        D: serde::Deserializer<'de>,
693    {
694        let iota_object_ref = ObjectRefSchema::deserialize(deserializer)?;
695        Ok(iota_object_ref.into())
696    }
697}
698
699impl From<ObjectReference> for ObjectRefSchema {
700    fn from(oref: ObjectReference) -> Self {
701        Self {
702            object_id: oref.object_id,
703            version: oref.version.into(),
704            digest: oref.digest,
705        }
706    }
707}
708
709impl From<ObjectRefSchema> for ObjectReference {
710    fn from(oref: ObjectRefSchema) -> Self {
711        ObjectReference::new(oref.object_id, oref.version.into(), oref.digest)
712    }
713}
714
715pub trait IotaData: Sized {
716    type ObjectType;
717    type PackageType;
718    fn try_from_object(object: MoveStruct, layout: MoveStructLayout)
719    -> Result<Self, anyhow::Error>;
720    fn try_from_package(package: MovePackage) -> Result<Self, anyhow::Error>;
721    fn try_as_move(&self) -> Option<&Self::ObjectType>;
722    fn try_into_move(self) -> Option<Self::ObjectType>;
723    fn try_as_package(&self) -> Option<&Self::PackageType>;
724    fn struct_tag(&self) -> Option<&StructTag>;
725}
726
727#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
728#[serde(tag = "dataType", rename_all = "camelCase", rename = "RawData")]
729pub enum IotaRawData {
730    // Manually handle generic schema generation
731    MoveObject(IotaRawMoveObject),
732    Package(IotaRawMovePackage),
733}
734
735impl IotaData for IotaRawData {
736    type ObjectType = IotaRawMoveObject;
737    type PackageType = IotaRawMovePackage;
738
739    fn try_from_object(object: MoveStruct, _: MoveStructLayout) -> Result<Self, anyhow::Error> {
740        Ok(Self::MoveObject(object.into()))
741    }
742
743    fn try_from_package(package: MovePackage) -> Result<Self, anyhow::Error> {
744        Ok(Self::Package(package.into()))
745    }
746
747    fn try_as_move(&self) -> Option<&Self::ObjectType> {
748        match self {
749            Self::MoveObject(o) => Some(o),
750            Self::Package(_) => None,
751        }
752    }
753
754    fn try_into_move(self) -> Option<Self::ObjectType> {
755        match self {
756            Self::MoveObject(o) => Some(o),
757            Self::Package(_) => None,
758        }
759    }
760
761    fn try_as_package(&self) -> Option<&Self::PackageType> {
762        match self {
763            Self::MoveObject(_) => None,
764            Self::Package(p) => Some(p),
765        }
766    }
767
768    fn struct_tag(&self) -> Option<&StructTag> {
769        match self {
770            Self::MoveObject(o) => Some(&o.struct_tag),
771            Self::Package(_) => None,
772        }
773    }
774}
775
776#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
777#[serde(tag = "dataType", rename_all = "camelCase", rename = "Data")]
778pub enum IotaParsedData {
779    // Manually handle generic schema generation
780    MoveObject(Box<IotaParsedMoveObject>),
781    Package(IotaMovePackage),
782}
783
784impl IotaData for IotaParsedData {
785    type ObjectType = IotaParsedMoveObject;
786    type PackageType = IotaMovePackage;
787
788    fn try_from_object(
789        object: MoveStruct,
790        layout: MoveStructLayout,
791    ) -> Result<Self, anyhow::Error> {
792        Ok(Self::MoveObject(Box::new(
793            IotaParsedMoveObject::try_from_layout(object, layout)?,
794        )))
795    }
796
797    fn try_from_package(package: MovePackage) -> Result<Self, anyhow::Error> {
798        let mut disassembled = BTreeMap::new();
799        for bytecode in package.serialized_module_map().values() {
800            // this function is only from JSON RPC - it is OK to deserialize with max Move
801            // binary version
802            let module = move_binary_format::CompiledModule::deserialize_with_defaults(bytecode)
803                .map_err(|error| IotaError::ModuleDeserializationFailure {
804                    error: error.to_string(),
805                })?;
806            let d = move_disassembler::disassembler::Disassembler::from_module(
807                &module,
808                move_ir_types::location::Spanned::unsafe_no_loc(()).loc,
809            )
810            .map_err(|e| IotaError::ObjectSerialization {
811                error: e.to_string(),
812            })?;
813            let bytecode_str = d
814                .disassemble()
815                .map_err(|e| IotaError::ObjectSerialization {
816                    error: e.to_string(),
817                })?;
818            disassembled.insert(module.name().to_string(), Value::String(bytecode_str));
819        }
820
821        Ok(Self::Package(IotaMovePackage { disassembled }))
822    }
823
824    fn try_as_move(&self) -> Option<&Self::ObjectType> {
825        match self {
826            Self::MoveObject(o) => Some(o),
827            Self::Package(_) => None,
828        }
829    }
830
831    fn try_into_move(self) -> Option<Self::ObjectType> {
832        match self {
833            Self::MoveObject(o) => Some(*o),
834            Self::Package(_) => None,
835        }
836    }
837
838    fn try_as_package(&self) -> Option<&Self::PackageType> {
839        match self {
840            Self::MoveObject(_) => None,
841            Self::Package(p) => Some(p),
842        }
843    }
844
845    fn struct_tag(&self) -> Option<&StructTag> {
846        match self {
847            Self::MoveObject(o) => Some(&o.struct_tag),
848            Self::Package(_) => None,
849        }
850    }
851}
852
853impl Display for IotaParsedData {
854    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
855        let mut writer = String::new();
856        match self {
857            IotaParsedData::MoveObject(o) => {
858                writeln!(writer, "{}: {}", "type".bold().bright_black(), o.struct_tag)?;
859                write!(writer, "{}", o.fields)?;
860            }
861            IotaParsedData::Package(p) => {
862                write!(
863                    writer,
864                    "{}: {:?}",
865                    "Modules".bold().bright_black(),
866                    p.disassembled.keys()
867                )?;
868            }
869        }
870        write!(f, "{writer}")
871    }
872}
873
874impl IotaParsedData {
875    pub fn try_from_object_read(object_read: ObjectRead) -> Result<Self, anyhow::Error> {
876        match object_read {
877            ObjectRead::NotExists(id) => Err(anyhow::anyhow!("Object {id} does not exist")),
878            ObjectRead::Exists(_object_ref, o, layout) => {
879                let data = match o.into_inner().data {
880                    ObjectData::Struct(m) => {
881                        let layout = layout.ok_or_else(|| {
882                            anyhow!("Layout is required to convert Move object to json")
883                        })?;
884                        IotaParsedData::try_from_object(m, layout)?
885                    }
886                    ObjectData::Package(p) => IotaParsedData::try_from_package(p)?,
887                    _ => bail!("unknown ObjectData variant"),
888                };
889                Ok(data)
890            }
891            ObjectRead::Deleted(object_ref) => Err(anyhow::anyhow!(
892                "Object {} was deleted at version {} with digest {}",
893                object_ref.object_id,
894                object_ref.version,
895                object_ref.digest
896            )),
897        }
898    }
899}
900
901pub trait IotaMoveObject: Sized {
902    fn try_from_layout(object: MoveStruct, layout: MoveStructLayout)
903    -> Result<Self, anyhow::Error>;
904
905    fn try_from(o: MoveStruct, resolver: &impl GetModule) -> Result<Self, anyhow::Error> {
906        let layout = o.get_layout(resolver)?;
907        Self::try_from_layout(o, layout)
908    }
909
910    fn struct_tag(&self) -> &StructTag;
911}
912
913#[serde_as]
914#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
915#[serde(rename = "MoveObject", rename_all = "camelCase")]
916pub struct IotaParsedMoveObject {
917    #[serde(rename = "type")]
918    #[schemars(with = "StructTagSchema")]
919    #[serde_as(as = "StructTagSchema")]
920    pub struct_tag: StructTag,
921    pub fields: IotaMoveStruct,
922}
923
924impl IotaMoveObject for IotaParsedMoveObject {
925    fn try_from_layout(
926        object: MoveStruct,
927        layout: MoveStructLayout,
928    ) -> Result<Self, anyhow::Error> {
929        let move_struct = object.to_move_struct(&layout)?.into();
930
931        Ok(
932            if let IotaMoveStruct::WithTypes {
933                struct_tag: tag,
934                fields,
935            } = move_struct
936            {
937                IotaParsedMoveObject {
938                    struct_tag: tag,
939                    fields: IotaMoveStruct::WithFields(fields),
940                }
941            } else {
942                IotaParsedMoveObject {
943                    struct_tag: object.struct_tag().clone(),
944                    fields: move_struct,
945                }
946            },
947        )
948    }
949
950    fn struct_tag(&self) -> &StructTag {
951        &self.struct_tag
952    }
953}
954
955impl IotaParsedMoveObject {
956    pub fn try_from_object_read(object_read: ObjectRead) -> Result<Self, anyhow::Error> {
957        let parsed_data = IotaParsedData::try_from_object_read(object_read)?;
958        match parsed_data {
959            IotaParsedData::MoveObject(o) => Ok(*o),
960            IotaParsedData::Package(_) => Err(anyhow::anyhow!("Object is not a Move object")),
961        }
962    }
963
964    pub fn read_dynamic_field_value(&self, field_name: &str) -> Option<IotaMoveValue> {
965        match &self.fields {
966            IotaMoveStruct::WithFields(fields) => fields.get(field_name).cloned(),
967            IotaMoveStruct::WithTypes { fields, .. } => fields.get(field_name).cloned(),
968            _ => None,
969        }
970    }
971}
972
973pub fn type_and_fields_from_move_event_data(
974    event_data: MoveValue,
975) -> IotaResult<(StructTag, serde_json::Value)> {
976    match event_data.into() {
977        IotaMoveValue::Struct(move_struct) => match &move_struct {
978            IotaMoveStruct::WithTypes {
979                struct_tag: tag, ..
980            } => Ok((tag.clone(), move_struct.clone().to_json_value())),
981            _ => Err(IotaError::ObjectDeserialization {
982                error: "Found non-type IotaMoveStruct in MoveValue event".to_string(),
983            }),
984        },
985        IotaMoveValue::Variant(v) => Ok((v.struct_tag.clone(), v.to_json_value())),
986        IotaMoveValue::Vector(_)
987        | IotaMoveValue::Number(_)
988        | IotaMoveValue::Bool(_)
989        | IotaMoveValue::Address(_)
990        | IotaMoveValue::String(_)
991        | IotaMoveValue::UID { .. }
992        | IotaMoveValue::Option(_) => Err(IotaError::ObjectDeserialization {
993            error: "Invalid MoveValue event type -- this should not be possible".to_string(),
994        }),
995    }
996}
997
998#[serde_as]
999#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
1000#[serde(rename = "RawMoveObject", rename_all = "camelCase")]
1001pub struct IotaRawMoveObject {
1002    #[serde(rename = "type")]
1003    #[schemars(with = "StructTagSchema")]
1004    #[serde_as(as = "StructTagSchema")]
1005    pub struct_tag: StructTag,
1006    pub version: SequenceNumberU64,
1007    #[serde_as(as = "Base64")]
1008    #[schemars(with = "Base64Schema")]
1009    pub bcs_bytes: Vec<u8>,
1010}
1011
1012impl From<MoveStruct> for IotaRawMoveObject {
1013    fn from(o: MoveStruct) -> Self {
1014        Self {
1015            struct_tag: o.struct_tag().clone(),
1016            version: o.version().into(),
1017            bcs_bytes: o.into_contents(),
1018        }
1019    }
1020}
1021
1022impl IotaMoveObject for IotaRawMoveObject {
1023    fn try_from_layout(
1024        object: MoveStruct,
1025        _layout: MoveStructLayout,
1026    ) -> Result<Self, anyhow::Error> {
1027        Ok(Self {
1028            struct_tag: object.struct_tag().clone(),
1029            version: object.version().into(),
1030            bcs_bytes: object.into_contents(),
1031        })
1032    }
1033
1034    fn struct_tag(&self) -> &StructTag {
1035        &self.struct_tag
1036    }
1037}
1038
1039impl IotaRawMoveObject {
1040    pub fn deserialize<'a, T: Deserialize<'a>>(&'a self) -> Result<T, anyhow::Error> {
1041        Ok(bcs::from_bytes(self.bcs_bytes.as_slice())?)
1042    }
1043}
1044
1045/// Store the origin of a data type where it first appeared in the version
1046/// chain.
1047///
1048/// A data type is identified by the name of the module and the name of the
1049/// struct/enum in combination.
1050///
1051/// # Undefined behavior
1052///
1053/// Directly modifying any field is undefined behavior. The fields are only
1054/// public for read-only access.
1055#[serde_as]
1056#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, JsonSchema)]
1057#[schemars(rename = "TypeOrigin")]
1058pub struct IotaTypeOrigin {
1059    /// The name of the module the data type resides in.
1060    #[schemars(with = "IdentifierSchema")]
1061    pub module_name: Identifier,
1062    /// The name of the data type.
1063    ///
1064    /// Here this either refers to an enum or a struct identifier.
1065    // `struct_name` alias to support backwards compatibility with the old name
1066    #[serde(alias = "struct_name")]
1067    #[schemars(with = "IdentifierSchema")]
1068    pub datatype_name: Identifier,
1069    /// `Storage ID` of the package, where the given type first appeared.
1070    #[schemars(with = "ObjectIdSchema")]
1071    pub package: ObjectId,
1072}
1073
1074impl From<TypeOrigin> for IotaTypeOrigin {
1075    fn from(origin: TypeOrigin) -> Self {
1076        Self {
1077            module_name: origin.module_name,
1078            datatype_name: origin.datatype_name,
1079            package: origin.package,
1080        }
1081    }
1082}
1083
1084impl From<IotaTypeOrigin> for TypeOrigin {
1085    fn from(origin: IotaTypeOrigin) -> Self {
1086        Self {
1087            module_name: origin.module_name,
1088            datatype_name: origin.datatype_name,
1089            package: origin.package,
1090        }
1091    }
1092}
1093
1094/// Value for the [MovePackage]'s linkage_table.
1095///
1096/// # Undefined behavior
1097///
1098/// Directly modifying any field is undefined behavior. The fields are only
1099/// public for read-only access.
1100#[serde_as]
1101#[derive(JsonSchema, Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
1102#[schemars(rename = "UpgradeInfo")]
1103pub struct IotaUpgradeInfo {
1104    /// `Storage ID`/`Package ID` of the referred package.
1105    #[schemars(with = "ObjectIdSchema")]
1106    pub upgraded_id: ObjectId,
1107    /// The version of the package at `upgraded_id`.
1108    pub upgraded_version: SequenceNumberU64,
1109}
1110
1111impl From<UpgradeInfo> for IotaUpgradeInfo {
1112    fn from(info: UpgradeInfo) -> Self {
1113        Self {
1114            upgraded_id: info.upgraded_id,
1115            upgraded_version: info.upgraded_version.into(),
1116        }
1117    }
1118}
1119
1120impl From<IotaUpgradeInfo> for UpgradeInfo {
1121    fn from(info: IotaUpgradeInfo) -> Self {
1122        Self {
1123            upgraded_id: info.upgraded_id,
1124            upgraded_version: info.upgraded_version.into(),
1125        }
1126    }
1127}
1128
1129#[serde_as]
1130#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
1131#[serde(rename = "RawMovePackage", rename_all = "camelCase")]
1132pub struct IotaRawMovePackage {
1133    #[serde_as(as = "ObjectIdSchema")]
1134    #[schemars(with = "ObjectIdSchema")]
1135    pub id: ObjectId,
1136    pub version: SequenceNumberU64,
1137    #[schemars(with = "BTreeMap<String, Base64Schema>")]
1138    #[serde_as(as = "BTreeMap<_, Base64>")]
1139    pub module_map: BTreeMap<String, Vec<u8>>,
1140    #[schemars(with = "Vec<IotaTypeOrigin>")]
1141    pub type_origin_table: Vec<TypeOrigin>,
1142    #[serde_as(as = "BTreeMap<ObjectIdSchema, _>")]
1143    #[schemars(with = "BTreeMap<ObjectIdSchema, IotaUpgradeInfo>")]
1144    pub linkage_table: BTreeMap<ObjectId, IotaUpgradeInfo>,
1145}
1146
1147impl From<MovePackage> for IotaRawMovePackage {
1148    fn from(p: MovePackage) -> Self {
1149        Self {
1150            id: p.id(),
1151            version: p.version().into(),
1152            module_map: p
1153                .modules
1154                .into_iter()
1155                .map(|(k, v)| (k.to_string(), v))
1156                .collect(),
1157            type_origin_table: p.type_origin_table,
1158            linkage_table: p
1159                .linkage_table
1160                .into_iter()
1161                .map(|(k, v)| (k, v.into()))
1162                .collect(),
1163        }
1164    }
1165}
1166
1167impl IotaRawMovePackage {
1168    /// The package this describes, not size-checked: it is already on-chain,
1169    /// and the limit the network held it to is not knowable from here.
1170    pub fn to_move_package(&self) -> MovePackage {
1171        MovePackage::new(
1172            self.id,
1173            self.version.into(),
1174            self.module_map
1175                .iter()
1176                .map(|(k, v)| (Identifier::new_unchecked(k), v.clone()))
1177                .collect(),
1178            self.type_origin_table.clone(),
1179            self.linkage_table
1180                .clone()
1181                .into_iter()
1182                .map(|(k, v)| (k, v.into()))
1183                .collect(),
1184        )
1185    }
1186}
1187
1188#[serde_as]
1189#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone, PartialEq, Eq)]
1190#[serde(tag = "status", content = "details", rename = "ObjectRead")]
1191#[expect(clippy::large_enum_variant)]
1192pub enum IotaPastObjectResponse {
1193    /// The object exists and is found with this version
1194    VersionFound(IotaObjectData),
1195    /// The object does not exist
1196    ObjectNotExists(
1197        #[serde_as(as = "ObjectIdSchema")]
1198        #[schemars(with = "ObjectIdSchema")]
1199        ObjectId,
1200    ),
1201    /// The object is found to be deleted with this version
1202    ObjectDeleted(
1203        #[schemars(with = "ObjectRefSchema")]
1204        #[serde_as(as = "ObjectRefSchema")]
1205        ObjectReference,
1206    ),
1207    /// The object exists but not found with this version
1208    VersionNotFound(
1209        #[serde_as(as = "ObjectIdSchema")]
1210        #[schemars(with = "ObjectIdSchema")]
1211        ObjectId,
1212        SequenceNumberU64,
1213    ),
1214    /// The asked object version is higher than the latest
1215    VersionTooHigh {
1216        #[serde_as(as = "ObjectIdSchema")]
1217        #[schemars(with = "ObjectIdSchema")]
1218        object_id: ObjectId,
1219        asked_version: SequenceNumberU64,
1220        latest_version: SequenceNumberU64,
1221    },
1222}
1223
1224impl IotaPastObjectResponse {
1225    /// Returns a reference to the object if there is any, otherwise an Err
1226    pub fn object(&self) -> UserInputResult<&IotaObjectData> {
1227        match &self {
1228            Self::ObjectDeleted(oref) => Err(UserInputError::ObjectDeleted { object_ref: *oref }),
1229            Self::ObjectNotExists(id) => Err(UserInputError::ObjectNotFound {
1230                object_id: *id,
1231                version: None,
1232            }),
1233            Self::VersionFound(o) => Ok(o),
1234            Self::VersionNotFound(id, seq_num) => Err(UserInputError::ObjectNotFound {
1235                object_id: *id,
1236                version: Some((*seq_num).into()),
1237            }),
1238            Self::VersionTooHigh {
1239                object_id,
1240                asked_version,
1241                latest_version,
1242            } => Err(UserInputError::ObjectSequenceNumberTooHigh {
1243                object_id: *object_id,
1244                asked_version: (*asked_version).into(),
1245                latest_version: (*latest_version).into(),
1246            }),
1247        }
1248    }
1249
1250    /// Returns the object value if there is any, otherwise an Err
1251    pub fn into_object(self) -> UserInputResult<IotaObjectData> {
1252        match self {
1253            Self::ObjectDeleted(oref) => Err(UserInputError::ObjectDeleted { object_ref: oref }),
1254            Self::ObjectNotExists(id) => Err(UserInputError::ObjectNotFound {
1255                object_id: id,
1256                version: None,
1257            }),
1258            Self::VersionFound(o) => Ok(o),
1259            Self::VersionNotFound(object_id, version) => Err(UserInputError::ObjectNotFound {
1260                object_id,
1261                version: Some(version.into()),
1262            }),
1263            Self::VersionTooHigh {
1264                object_id,
1265                asked_version,
1266                latest_version,
1267            } => Err(UserInputError::ObjectSequenceNumberTooHigh {
1268                object_id,
1269                asked_version: asked_version.into(),
1270                latest_version: latest_version.into(),
1271            }),
1272        }
1273    }
1274}
1275
1276#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
1277#[serde(rename = "MovePackage", rename_all = "camelCase")]
1278pub struct IotaMovePackage {
1279    pub disassembled: BTreeMap<String, Value>,
1280}
1281
1282pub type QueryObjectsPage = Page<IotaObjectResponse, CheckpointedObjectID>;
1283pub type ObjectsPage = Page<IotaObjectResponse, ObjectId>;
1284
1285#[serde_as]
1286#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Copy, Eq, PartialEq)]
1287#[serde(rename_all = "camelCase")]
1288pub struct CheckpointedObjectID {
1289    #[serde_as(as = "ObjectIdSchema")]
1290    #[schemars(with = "ObjectIdSchema")]
1291    pub object_id: ObjectId,
1292    #[schemars(with = "Option<String>")]
1293    #[serde_as(as = "Option<DisplayFromStr>")]
1294    #[serde(skip_serializing_if = "Option::is_none")]
1295    pub at_checkpoint: Option<CheckpointSequenceNumber>,
1296}
1297
1298#[serde_as]
1299#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
1300#[serde(rename = "GetPastObjectRequest", rename_all = "camelCase")]
1301pub struct IotaGetPastObjectRequest {
1302    /// the ID of the queried object
1303    #[serde_as(as = "ObjectIdSchema")]
1304    #[schemars(with = "ObjectIdSchema")]
1305    pub object_id: ObjectId,
1306    /// the version of the queried object.
1307    #[schemars(with = "SequenceNumberStringSchema")]
1308    #[serde_as(as = "SequenceNumberStringSchema")]
1309    pub version: Version,
1310}
1311
1312#[serde_as]
1313#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
1314pub enum IotaObjectDataFilter {
1315    MatchAll(Vec<IotaObjectDataFilter>),
1316    MatchAny(Vec<IotaObjectDataFilter>),
1317    MatchNone(Vec<IotaObjectDataFilter>),
1318    /// Query by type a specified Package.
1319    Package(
1320        #[serde_as(as = "ObjectIdSchema")]
1321        #[schemars(with = "ObjectIdSchema")]
1322        ObjectId,
1323    ),
1324    /// Query by type a specified Move module.
1325    MoveModule {
1326        /// the Move package ID
1327        #[serde_as(as = "ObjectIdSchema")]
1328        #[schemars(with = "ObjectIdSchema")]
1329        package: ObjectId,
1330        /// the module name
1331        #[serde_as(as = "IdentifierSchema")]
1332        #[schemars(with = "IdentifierSchema")]
1333        module: Identifier,
1334    },
1335    /// Query by type
1336    StructType(
1337        #[schemars(with = "StructTagSchema")]
1338        #[serde_as(as = "StructTagSchema")]
1339        StructTag,
1340    ),
1341    AddressOwner(
1342        #[serde_as(as = "AddressSchema")]
1343        #[schemars(with = "AddressSchema")]
1344        Address,
1345    ),
1346    ObjectOwner(
1347        #[serde_as(as = "ObjectIdSchema")]
1348        #[schemars(with = "ObjectIdSchema")]
1349        ObjectId,
1350    ),
1351    ObjectId(
1352        #[serde_as(as = "ObjectIdSchema")]
1353        #[schemars(with = "ObjectIdSchema")]
1354        ObjectId,
1355    ),
1356    // allow querying for multiple object ids
1357    ObjectIds(
1358        #[serde_as(as = "Vec<ObjectIdSchema>")]
1359        #[schemars(with = "Vec<ObjectIdSchema>")]
1360        Vec<ObjectId>,
1361    ),
1362    Version(
1363        #[serde_as(as = "DisplayFromStr")]
1364        #[schemars(with = "String")]
1365        u64,
1366    ),
1367}
1368
1369impl IotaObjectDataFilter {
1370    pub fn gas_coin() -> Self {
1371        Self::StructType(StructTag::new_gas_coin())
1372    }
1373
1374    pub fn and(self, other: Self) -> Self {
1375        Self::MatchAll(vec![self, other])
1376    }
1377    pub fn or(self, other: Self) -> Self {
1378        Self::MatchAny(vec![self, other])
1379    }
1380    pub fn not(self, other: Self) -> Self {
1381        Self::MatchNone(vec![self, other])
1382    }
1383
1384    pub fn matches(&self, object: &ObjectInfo) -> bool {
1385        match self {
1386            IotaObjectDataFilter::MatchAll(filters) => !filters.iter().any(|f| !f.matches(object)),
1387            IotaObjectDataFilter::MatchAny(filters) => filters.iter().any(|f| f.matches(object)),
1388            IotaObjectDataFilter::MatchNone(filters) => !filters.iter().any(|f| f.matches(object)),
1389            IotaObjectDataFilter::StructType(s) => {
1390                let obj_tag: StructTag = match &object.object_type {
1391                    ObjectType::Package => return false,
1392                    ObjectType::Struct(s) => s.clone().into(),
1393                };
1394                // If people do not provide type_params, we will match all type_params
1395                // e.g. `0x2::coin::Coin` can match `0x2::coin::Coin<0x2::iota::IOTA>`
1396                if !s.type_params().is_empty() && s.type_params() != obj_tag.type_params() {
1397                    false
1398                } else {
1399                    obj_tag.address() == s.address()
1400                        && obj_tag.module() == s.module()
1401                        && obj_tag.name() == s.name()
1402                }
1403            }
1404            IotaObjectDataFilter::MoveModule { package, module } => {
1405                matches!(&object.object_type, ObjectType::Struct(s) if &ObjectId::from(s.address()) == package
1406                        && s.module() == module)
1407            }
1408            IotaObjectDataFilter::Package(p) => {
1409                matches!(&object.object_type, ObjectType::Struct(s) if &ObjectId::from(s.address()) == p)
1410            }
1411            IotaObjectDataFilter::AddressOwner(a) => {
1412                matches!(object.owner, Owner::Address(addr) if &addr == a)
1413            }
1414            IotaObjectDataFilter::ObjectOwner(o) => {
1415                matches!(object.owner, Owner::Object(addr) if &addr == o)
1416            }
1417            IotaObjectDataFilter::ObjectId(id) => &object.object_id == id,
1418            IotaObjectDataFilter::ObjectIds(ids) => ids.contains(&object.object_id),
1419            IotaObjectDataFilter::Version(v) => object.version == *v,
1420        }
1421    }
1422}
1423
1424#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Default)]
1425#[serde(rename_all = "camelCase", rename = "ObjectResponseQuery", default)]
1426pub struct IotaObjectResponseQuery {
1427    /// If None, no filter will be applied
1428    pub filter: Option<IotaObjectDataFilter>,
1429    /// config which fields to include in the response, by default only digest
1430    /// is included
1431    pub options: Option<IotaObjectDataOptions>,
1432}
1433
1434impl IotaObjectResponseQuery {
1435    pub fn new(
1436        filter: Option<IotaObjectDataFilter>,
1437        options: Option<IotaObjectDataOptions>,
1438    ) -> Self {
1439        Self { filter, options }
1440    }
1441
1442    pub fn new_with_filter(filter: IotaObjectDataFilter) -> Self {
1443        Self {
1444            filter: Some(filter),
1445            options: None,
1446        }
1447    }
1448
1449    pub fn new_with_options(options: IotaObjectDataOptions) -> Self {
1450        Self {
1451            filter: None,
1452            options: Some(options),
1453        }
1454    }
1455}