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