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