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