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            object_type,
175            owner,
176            previous_transaction,
177            ..
178        } = value.into_object()?;
179
180        Ok(ObjectInfo {
181            object_id,
182            version,
183            digest,
184            object_type: object_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 object_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 object_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            object_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.object_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.object_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 object_type = if let Some(tag) = &self.object_type {
365            tag.to_string()
366        } else {
367            "Unknown Type".into()
368        };
369        let mut writer = String::new();
370        writeln!(
371            writer,
372            "{}",
373            format!(
374                "----- {object_type} ({}[{}]) -----",
375                self.object_id, self.version
376            )
377            .bold()
378        )?;
379        if let Some(owner) = self.owner {
380            writeln!(writer, "{}: {owner}", "Owner".bold().bright_black())?;
381        }
382
383        writeln!(
384            writer,
385            "{}: {}",
386            "Version".bold().bright_black(),
387            self.version
388        )?;
389        if let Some(storage_rebate) = self.storage_rebate {
390            writeln!(
391                writer,
392                "{}: {storage_rebate}",
393                "Storage Rebate".bold().bright_black(),
394            )?;
395        }
396
397        if let Some(previous_transaction) = self.previous_transaction {
398            writeln!(
399                writer,
400                "{}: {previous_transaction:?}",
401                "Previous Transaction".bold().bright_black(),
402            )?;
403        }
404        if let Some(content) = self.content.as_ref() {
405            writeln!(writer, "{}", "----- Data -----".bold())?;
406            write!(writer, "{content}")?;
407        }
408
409        write!(f, "{writer}")
410    }
411}
412
413impl TryFrom<&IotaObjectData> for GasCoin {
414    type Error = anyhow::Error;
415    fn try_from(object: &IotaObjectData) -> Result<Self, Self::Error> {
416        match &object
417            .content
418            .as_ref()
419            .ok_or_else(|| anyhow!("Expect object content to not be empty"))?
420        {
421            IotaParsedData::MoveObject(o) => {
422                if o.struct_tag.is_gas_coin() {
423                    return GasCoin::try_from(&o.fields);
424                }
425            }
426            IotaParsedData::Package(_) => {}
427        }
428
429        bail!(
430            "Gas object type is not a gas coin: {:?}",
431            object.object_type
432        )
433    }
434}
435
436impl TryFrom<&IotaMoveStruct> for GasCoin {
437    type Error = anyhow::Error;
438    fn try_from(move_struct: &IotaMoveStruct) -> Result<Self, Self::Error> {
439        match move_struct {
440            IotaMoveStruct::WithFields(fields)
441            | IotaMoveStruct::WithTypes {
442                struct_tag: _,
443                fields,
444            } => {
445                if let Some(IotaMoveValue::String(balance)) = fields.get("balance") {
446                    if let Ok(balance) = balance.parse::<u64>() {
447                        if let Some(IotaMoveValue::UID { id }) = fields.get("id") {
448                            return Ok(GasCoin::new(*id, balance));
449                        }
450                    }
451                }
452            }
453            _ => {}
454        }
455        bail!("Struct is not a gas coin: {move_struct:?}")
456    }
457}
458
459#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Eq, PartialEq, Default)]
460#[serde(rename_all = "camelCase", rename = "ObjectDataOptions", default)]
461pub struct IotaObjectDataOptions {
462    /// Whether to show the type of the object. Default to be False
463    pub show_type: bool,
464    /// Whether to show the owner of the object. Default to be False
465    pub show_owner: bool,
466    /// Whether to show the previous transaction digest of the object. Default
467    /// to be False
468    pub show_previous_transaction: bool,
469    /// Whether to show the Display metadata of the object for frontend
470    /// rendering. Default to be False
471    pub show_display: bool,
472    /// Whether to show the content(i.e., package content or Move struct
473    /// content) of the object. Default to be False
474    pub show_content: bool,
475    /// Whether to show the content in BCS format. Default to be False
476    pub show_bcs: bool,
477    /// Whether to show the storage rebate of the object. Default to be False
478    pub show_storage_rebate: bool,
479}
480
481impl IotaObjectDataOptions {
482    pub fn new() -> Self {
483        Self::default()
484    }
485
486    /// return BCS data and all other metadata such as storage rebate
487    pub fn bcs_lossless() -> Self {
488        Self {
489            show_bcs: true,
490            show_type: true,
491            show_owner: true,
492            show_previous_transaction: true,
493            show_display: false,
494            show_content: false,
495            show_storage_rebate: true,
496        }
497    }
498
499    /// return full content except bcs
500    pub fn full_content() -> Self {
501        Self {
502            show_bcs: false,
503            show_type: true,
504            show_owner: true,
505            show_previous_transaction: true,
506            show_display: false,
507            show_content: true,
508            show_storage_rebate: true,
509        }
510    }
511
512    pub fn with_content(mut self) -> Self {
513        self.show_content = true;
514        self
515    }
516
517    pub fn with_owner(mut self) -> Self {
518        self.show_owner = true;
519        self
520    }
521
522    pub fn with_type(mut self) -> Self {
523        self.show_type = true;
524        self
525    }
526
527    pub fn with_display(mut self) -> Self {
528        self.show_display = true;
529        self
530    }
531
532    pub fn with_bcs(mut self) -> Self {
533        self.show_bcs = true;
534        self
535    }
536
537    pub fn with_previous_transaction(mut self) -> Self {
538        self.show_previous_transaction = true;
539        self
540    }
541
542    pub fn is_not_in_object_info(&self) -> bool {
543        self.show_bcs || self.show_content || self.show_display || self.show_storage_rebate
544    }
545}
546
547impl TryFrom<(ObjectRead, IotaObjectDataOptions)> for IotaObjectResponse {
548    type Error = anyhow::Error;
549
550    fn try_from(
551        (object_read, options): (ObjectRead, IotaObjectDataOptions),
552    ) -> Result<Self, Self::Error> {
553        Self::try_from_object_read_and_options(object_read, &options)
554    }
555}
556
557impl TryFrom<(ObjectInfo, IotaObjectDataOptions)> for IotaObjectResponse {
558    type Error = anyhow::Error;
559
560    fn try_from(
561        (object_info, options): (ObjectInfo, IotaObjectDataOptions),
562    ) -> Result<Self, Self::Error> {
563        let IotaObjectDataOptions {
564            show_type,
565            show_owner,
566            show_previous_transaction,
567            ..
568        } = options;
569
570        Ok(Self::new_with_data(IotaObjectData {
571            object_id: object_info.object_id,
572            version: object_info.version,
573            digest: object_info.digest,
574            object_type: show_type.then_some(object_info.object_type),
575            owner: show_owner.then_some(object_info.owner),
576            previous_transaction: show_previous_transaction
577                .then_some(object_info.previous_transaction),
578            storage_rebate: None,
579            display: None,
580            content: None,
581            bcs: None,
582        }))
583    }
584}
585
586impl IotaObjectResponse {
587    /// Returns a reference to the object if there is any, otherwise an Err if
588    /// the object does not exist or is deleted.
589    pub fn object(&self) -> Result<&IotaObjectData, IotaObjectResponseError> {
590        if let Some(data) = &self.data {
591            Ok(data)
592        } else if let Some(error) = &self.error {
593            Err(error.clone())
594        } else {
595            // We really shouldn't reach this code block since either data, or error field
596            // should always be filled.
597            Err(IotaObjectResponseError::Unknown)
598        }
599    }
600
601    /// Returns the object value if there is any, otherwise an Err if
602    /// the object does not exist or is deleted.
603    pub fn into_object(self) -> Result<IotaObjectData, IotaObjectResponseError> {
604        match self.object() {
605            Ok(data) => Ok(data.clone()),
606            Err(error) => Err(error),
607        }
608    }
609}
610
611impl TryInto<Object> for IotaObjectData {
612    type Error = anyhow::Error;
613
614    fn try_into(self) -> Result<Object, Self::Error> {
615        let protocol_config = ProtocolConfig::get_for_min_version();
616        let data = match self.bcs {
617            Some(IotaRawData::MoveObject(o)) => ObjectData::Struct({
618                MoveStruct::new_from_execution(
619                    o.struct_tag().clone(),
620                    o.version.into(),
621                    o.bcs_bytes,
622                    &protocol_config,
623                    // system_mutation
624                    false,
625                )?
626            }),
627            Some(IotaRawData::Package(p)) => ObjectData::Package(MovePackage::new(
628                p.id,
629                self.version,
630                p.module_map
631                    .iter()
632                    .map(|(k, v)| (Identifier::new_unchecked(k), v.clone()))
633                    .collect(),
634                protocol_config.max_move_package_size(),
635                p.type_origin_table.into_iter().collect(),
636                p.linkage_table
637                    .into_iter()
638                    .map(|(k, v)| (k, v.into()))
639                    .collect(),
640            )?),
641            _ => Err(anyhow!(
642                "BCS data is required to convert IotaObjectData to Object"
643            ))?,
644        };
645        Ok(ObjectInner {
646            data,
647            owner: self
648                .owner
649                .ok_or_else(|| anyhow!("Owner is required to convert IotaObjectData to Object"))?,
650            previous_transaction: self.previous_transaction.ok_or_else(|| {
651                anyhow!("previous_transaction is required to convert IotaObjectData to Object")
652            })?,
653            storage_rebate: self.storage_rebate.ok_or_else(|| {
654                anyhow!("storage_rebate is required to convert IotaObjectData to Object")
655            })?,
656        }
657        .into())
658    }
659}
660
661#[serde_as]
662#[derive(Deserialize, Serialize, JsonSchema)]
663#[serde(rename_all = "camelCase", rename = "ObjectRef")]
664pub struct ObjectRefSchema {
665    /// Hex code as string representing the object id
666    #[serde_as(as = "ObjectIdSchema")]
667    #[schemars(with = "ObjectIdSchema")]
668    pub object_id: ObjectId,
669    /// Object version.
670    pub version: SequenceNumberU64,
671    /// Base64 string representing the object digest
672    #[serde_as(as = "Base58Schema")]
673    #[schemars(with = "Base58Schema")]
674    pub digest: ObjectDigest,
675}
676
677impl SerializeAs<ObjectReference> for ObjectRefSchema {
678    fn serialize_as<S>(source: &ObjectReference, serializer: S) -> Result<S::Ok, S::Error>
679    where
680        S: serde::Serializer,
681    {
682        let iota_object_ref: ObjectRefSchema = (*source).into();
683        iota_object_ref.serialize(serializer)
684    }
685}
686
687impl<'de> DeserializeAs<'de, ObjectReference> for ObjectRefSchema {
688    fn deserialize_as<D>(deserializer: D) -> Result<ObjectReference, D::Error>
689    where
690        D: serde::Deserializer<'de>,
691    {
692        let iota_object_ref = ObjectRefSchema::deserialize(deserializer)?;
693        Ok(iota_object_ref.into())
694    }
695}
696
697impl From<ObjectReference> for ObjectRefSchema {
698    fn from(oref: ObjectReference) -> Self {
699        Self {
700            object_id: oref.object_id,
701            version: oref.version.into(),
702            digest: oref.digest,
703        }
704    }
705}
706
707impl From<ObjectRefSchema> for ObjectReference {
708    fn from(oref: ObjectRefSchema) -> Self {
709        ObjectReference::new(oref.object_id, oref.version.into(), oref.digest)
710    }
711}
712
713pub trait IotaData: Sized {
714    type ObjectType;
715    type PackageType;
716    fn try_from_object(object: MoveStruct, layout: MoveStructLayout)
717    -> Result<Self, anyhow::Error>;
718    fn try_from_package(package: MovePackage) -> Result<Self, anyhow::Error>;
719    fn try_as_move(&self) -> Option<&Self::ObjectType>;
720    fn try_into_move(self) -> Option<Self::ObjectType>;
721    fn try_as_package(&self) -> Option<&Self::PackageType>;
722    fn struct_tag(&self) -> Option<&StructTag>;
723}
724
725#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
726#[serde(tag = "dataType", rename_all = "camelCase", rename = "RawData")]
727pub enum IotaRawData {
728    // Manually handle generic schema generation
729    MoveObject(IotaRawMoveObject),
730    Package(IotaRawMovePackage),
731}
732
733impl IotaData for IotaRawData {
734    type ObjectType = IotaRawMoveObject;
735    type PackageType = IotaRawMovePackage;
736
737    fn try_from_object(object: MoveStruct, _: MoveStructLayout) -> Result<Self, anyhow::Error> {
738        Ok(Self::MoveObject(object.into()))
739    }
740
741    fn try_from_package(package: MovePackage) -> Result<Self, anyhow::Error> {
742        Ok(Self::Package(package.into()))
743    }
744
745    fn try_as_move(&self) -> Option<&Self::ObjectType> {
746        match self {
747            Self::MoveObject(o) => Some(o),
748            Self::Package(_) => None,
749        }
750    }
751
752    fn try_into_move(self) -> Option<Self::ObjectType> {
753        match self {
754            Self::MoveObject(o) => Some(o),
755            Self::Package(_) => None,
756        }
757    }
758
759    fn try_as_package(&self) -> Option<&Self::PackageType> {
760        match self {
761            Self::MoveObject(_) => None,
762            Self::Package(p) => Some(p),
763        }
764    }
765
766    fn struct_tag(&self) -> Option<&StructTag> {
767        match self {
768            Self::MoveObject(o) => Some(&o.struct_tag),
769            Self::Package(_) => None,
770        }
771    }
772}
773
774#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
775#[serde(tag = "dataType", rename_all = "camelCase", rename = "Data")]
776pub enum IotaParsedData {
777    // Manually handle generic schema generation
778    MoveObject(Box<IotaParsedMoveObject>),
779    Package(IotaMovePackage),
780}
781
782impl IotaData for IotaParsedData {
783    type ObjectType = IotaParsedMoveObject;
784    type PackageType = IotaMovePackage;
785
786    fn try_from_object(
787        object: MoveStruct,
788        layout: MoveStructLayout,
789    ) -> Result<Self, anyhow::Error> {
790        Ok(Self::MoveObject(Box::new(
791            IotaParsedMoveObject::try_from_layout(object, layout)?,
792        )))
793    }
794
795    fn try_from_package(package: MovePackage) -> Result<Self, anyhow::Error> {
796        let mut disassembled = BTreeMap::new();
797        for bytecode in package.serialized_module_map().values() {
798            // this function is only from JSON RPC - it is OK to deserialize with max Move
799            // binary version
800            let module = move_binary_format::CompiledModule::deserialize_with_defaults(bytecode)
801                .map_err(|error| IotaError::ModuleDeserializationFailure {
802                    error: error.to_string(),
803                })?;
804            let d = move_disassembler::disassembler::Disassembler::from_module(
805                &module,
806                move_ir_types::location::Spanned::unsafe_no_loc(()).loc,
807            )
808            .map_err(|e| IotaError::ObjectSerialization {
809                error: e.to_string(),
810            })?;
811            let bytecode_str = d
812                .disassemble()
813                .map_err(|e| IotaError::ObjectSerialization {
814                    error: e.to_string(),
815                })?;
816            disassembled.insert(module.name().to_string(), Value::String(bytecode_str));
817        }
818
819        Ok(Self::Package(IotaMovePackage { disassembled }))
820    }
821
822    fn try_as_move(&self) -> Option<&Self::ObjectType> {
823        match self {
824            Self::MoveObject(o) => Some(o),
825            Self::Package(_) => None,
826        }
827    }
828
829    fn try_into_move(self) -> Option<Self::ObjectType> {
830        match self {
831            Self::MoveObject(o) => Some(*o),
832            Self::Package(_) => None,
833        }
834    }
835
836    fn try_as_package(&self) -> Option<&Self::PackageType> {
837        match self {
838            Self::MoveObject(_) => None,
839            Self::Package(p) => Some(p),
840        }
841    }
842
843    fn struct_tag(&self) -> Option<&StructTag> {
844        match self {
845            Self::MoveObject(o) => Some(&o.struct_tag),
846            Self::Package(_) => None,
847        }
848    }
849}
850
851impl Display for IotaParsedData {
852    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
853        let mut writer = String::new();
854        match self {
855            IotaParsedData::MoveObject(o) => {
856                writeln!(writer, "{}: {}", "type".bold().bright_black(), o.struct_tag)?;
857                write!(writer, "{}", o.fields)?;
858            }
859            IotaParsedData::Package(p) => {
860                write!(
861                    writer,
862                    "{}: {:?}",
863                    "Modules".bold().bright_black(),
864                    p.disassembled.keys()
865                )?;
866            }
867        }
868        write!(f, "{writer}")
869    }
870}
871
872impl IotaParsedData {
873    pub fn try_from_object_read(object_read: ObjectRead) -> Result<Self, anyhow::Error> {
874        match object_read {
875            ObjectRead::NotExists(id) => Err(anyhow::anyhow!("Object {id} does not exist")),
876            ObjectRead::Exists(_object_ref, o, layout) => {
877                let data = match o.into_inner().data {
878                    ObjectData::Struct(m) => {
879                        let layout = layout.ok_or_else(|| {
880                            anyhow!("Layout is required to convert Move object to json")
881                        })?;
882                        IotaParsedData::try_from_object(m, layout)?
883                    }
884                    ObjectData::Package(p) => IotaParsedData::try_from_package(p)?,
885                };
886                Ok(data)
887            }
888            ObjectRead::Deleted(object_ref) => Err(anyhow::anyhow!(
889                "Object {} was deleted at version {} with digest {}",
890                object_ref.object_id,
891                object_ref.version,
892                object_ref.digest
893            )),
894        }
895    }
896}
897
898pub trait IotaMoveObject: Sized {
899    fn try_from_layout(object: MoveStruct, layout: MoveStructLayout)
900    -> Result<Self, anyhow::Error>;
901
902    fn try_from(o: MoveStruct, resolver: &impl GetModule) -> Result<Self, anyhow::Error> {
903        let layout = o.get_layout(resolver)?;
904        Self::try_from_layout(o, layout)
905    }
906
907    fn struct_tag(&self) -> &StructTag;
908}
909
910#[serde_as]
911#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
912#[serde(rename = "MoveObject", rename_all = "camelCase")]
913pub struct IotaParsedMoveObject {
914    #[serde(rename = "type")]
915    #[schemars(with = "StructTagSchema")]
916    #[serde_as(as = "StructTagSchema")]
917    pub struct_tag: StructTag,
918    pub fields: IotaMoveStruct,
919}
920
921impl IotaMoveObject for IotaParsedMoveObject {
922    fn try_from_layout(
923        object: MoveStruct,
924        layout: MoveStructLayout,
925    ) -> Result<Self, anyhow::Error> {
926        let move_struct = object.to_move_struct(&layout)?.into();
927
928        Ok(
929            if let IotaMoveStruct::WithTypes {
930                struct_tag: tag,
931                fields,
932            } = move_struct
933            {
934                IotaParsedMoveObject {
935                    struct_tag: tag,
936                    fields: IotaMoveStruct::WithFields(fields),
937                }
938            } else {
939                IotaParsedMoveObject {
940                    struct_tag: object.struct_tag().clone(),
941                    fields: move_struct,
942                }
943            },
944        )
945    }
946
947    fn struct_tag(&self) -> &StructTag {
948        &self.struct_tag
949    }
950}
951
952impl IotaParsedMoveObject {
953    pub fn try_from_object_read(object_read: ObjectRead) -> Result<Self, anyhow::Error> {
954        let parsed_data = IotaParsedData::try_from_object_read(object_read)?;
955        match parsed_data {
956            IotaParsedData::MoveObject(o) => Ok(*o),
957            IotaParsedData::Package(_) => Err(anyhow::anyhow!("Object is not a Move object")),
958        }
959    }
960
961    pub fn read_dynamic_field_value(&self, field_name: &str) -> Option<IotaMoveValue> {
962        match &self.fields {
963            IotaMoveStruct::WithFields(fields) => fields.get(field_name).cloned(),
964            IotaMoveStruct::WithTypes { fields, .. } => fields.get(field_name).cloned(),
965            _ => None,
966        }
967    }
968}
969
970pub fn type_and_fields_from_move_event_data(
971    event_data: MoveValue,
972) -> IotaResult<(StructTag, serde_json::Value)> {
973    match event_data.into() {
974        IotaMoveValue::Struct(move_struct) => match &move_struct {
975            IotaMoveStruct::WithTypes {
976                struct_tag: tag, ..
977            } => Ok((tag.clone(), move_struct.clone().to_json_value())),
978            _ => Err(IotaError::ObjectDeserialization {
979                error: "Found non-type IotaMoveStruct in MoveValue event".to_string(),
980            }),
981        },
982        IotaMoveValue::Variant(v) => Ok((v.struct_tag.clone(), v.to_json_value())),
983        IotaMoveValue::Vector(_)
984        | IotaMoveValue::Number(_)
985        | IotaMoveValue::Bool(_)
986        | IotaMoveValue::Address(_)
987        | IotaMoveValue::String(_)
988        | IotaMoveValue::UID { .. }
989        | IotaMoveValue::Option(_) => Err(IotaError::ObjectDeserialization {
990            error: "Invalid MoveValue event type -- this should not be possible".to_string(),
991        }),
992    }
993}
994
995#[serde_as]
996#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
997#[serde(rename = "RawMoveObject", rename_all = "camelCase")]
998pub struct IotaRawMoveObject {
999    #[serde(rename = "type")]
1000    #[schemars(with = "StructTagSchema")]
1001    #[serde_as(as = "StructTagSchema")]
1002    pub struct_tag: StructTag,
1003    pub version: SequenceNumberU64,
1004    #[serde_as(as = "Base64")]
1005    #[schemars(with = "Base64Schema")]
1006    pub bcs_bytes: Vec<u8>,
1007}
1008
1009impl From<MoveStruct> for IotaRawMoveObject {
1010    fn from(o: MoveStruct) -> Self {
1011        Self {
1012            struct_tag: o.struct_tag().clone(),
1013            version: o.version().into(),
1014            bcs_bytes: o.into_contents(),
1015        }
1016    }
1017}
1018
1019impl IotaMoveObject for IotaRawMoveObject {
1020    fn try_from_layout(
1021        object: MoveStruct,
1022        _layout: MoveStructLayout,
1023    ) -> Result<Self, anyhow::Error> {
1024        Ok(Self {
1025            struct_tag: object.struct_tag().clone(),
1026            version: object.version().into(),
1027            bcs_bytes: object.into_contents(),
1028        })
1029    }
1030
1031    fn struct_tag(&self) -> &StructTag {
1032        &self.struct_tag
1033    }
1034}
1035
1036impl IotaRawMoveObject {
1037    pub fn deserialize<'a, T: Deserialize<'a>>(&'a self) -> Result<T, anyhow::Error> {
1038        Ok(bcs::from_bytes(self.bcs_bytes.as_slice())?)
1039    }
1040}
1041
1042/// Store the origin of a data type where it first appeared in the version
1043/// chain.
1044///
1045/// A data type is identified by the name of the module and the name of the
1046/// struct/enum in combination.
1047///
1048/// # Undefined behavior
1049///
1050/// Directly modifying any field is undefined behavior. The fields are only
1051/// public for read-only access.
1052#[serde_as]
1053#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, JsonSchema)]
1054#[schemars(rename = "TypeOrigin")]
1055pub struct IotaTypeOrigin {
1056    /// The name of the module the data type resides in.
1057    #[schemars(with = "IdentifierSchema")]
1058    pub module_name: Identifier,
1059    /// The name of the data type.
1060    ///
1061    /// Here this either refers to an enum or a struct identifier.
1062    // `struct_name` alias to support backwards compatibility with the old name
1063    #[serde(alias = "struct_name")]
1064    #[schemars(with = "IdentifierSchema")]
1065    pub datatype_name: Identifier,
1066    /// `Storage ID` of the package, where the given type first appeared.
1067    #[schemars(with = "ObjectIdSchema")]
1068    pub package: ObjectId,
1069}
1070
1071impl From<TypeOrigin> for IotaTypeOrigin {
1072    fn from(origin: TypeOrigin) -> Self {
1073        Self {
1074            module_name: origin.module_name,
1075            datatype_name: origin.datatype_name,
1076            package: origin.package,
1077        }
1078    }
1079}
1080
1081impl From<IotaTypeOrigin> for TypeOrigin {
1082    fn from(origin: IotaTypeOrigin) -> Self {
1083        Self {
1084            module_name: origin.module_name,
1085            datatype_name: origin.datatype_name,
1086            package: origin.package,
1087        }
1088    }
1089}
1090
1091/// Value for the [MovePackage]'s linkage_table.
1092///
1093/// # Undefined behavior
1094///
1095/// Directly modifying any field is undefined behavior. The fields are only
1096/// public for read-only access.
1097#[serde_as]
1098#[derive(JsonSchema, Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
1099#[schemars(rename = "UpgradeInfo")]
1100pub struct IotaUpgradeInfo {
1101    /// `Storage ID`/`Package ID` of the referred package.
1102    #[schemars(with = "ObjectIdSchema")]
1103    pub upgraded_id: ObjectId,
1104    /// The version of the package at `upgraded_id`.
1105    pub upgraded_version: SequenceNumberU64,
1106}
1107
1108impl From<UpgradeInfo> for IotaUpgradeInfo {
1109    fn from(info: UpgradeInfo) -> Self {
1110        Self {
1111            upgraded_id: info.upgraded_id,
1112            upgraded_version: info.upgraded_version.into(),
1113        }
1114    }
1115}
1116
1117impl From<IotaUpgradeInfo> for UpgradeInfo {
1118    fn from(info: IotaUpgradeInfo) -> Self {
1119        Self {
1120            upgraded_id: info.upgraded_id,
1121            upgraded_version: info.upgraded_version.into(),
1122        }
1123    }
1124}
1125
1126#[serde_as]
1127#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
1128#[serde(rename = "RawMovePackage", rename_all = "camelCase")]
1129pub struct IotaRawMovePackage {
1130    #[serde_as(as = "ObjectIdSchema")]
1131    #[schemars(with = "ObjectIdSchema")]
1132    pub id: ObjectId,
1133    pub version: SequenceNumberU64,
1134    #[schemars(with = "BTreeMap<String, Base64Schema>")]
1135    #[serde_as(as = "BTreeMap<_, Base64>")]
1136    pub module_map: BTreeMap<String, Vec<u8>>,
1137    #[schemars(with = "Vec<IotaTypeOrigin>")]
1138    pub type_origin_table: Vec<TypeOrigin>,
1139    #[serde_as(as = "BTreeMap<ObjectIdSchema, _>")]
1140    #[schemars(with = "BTreeMap<ObjectIdSchema, IotaUpgradeInfo>")]
1141    pub linkage_table: BTreeMap<ObjectId, IotaUpgradeInfo>,
1142}
1143
1144impl From<MovePackage> for IotaRawMovePackage {
1145    fn from(p: MovePackage) -> Self {
1146        Self {
1147            id: p.id(),
1148            version: p.version().into(),
1149            module_map: p
1150                .modules
1151                .into_iter()
1152                .map(|(k, v)| (k.to_string(), v))
1153                .collect(),
1154            type_origin_table: p.type_origin_table,
1155            linkage_table: p
1156                .linkage_table
1157                .into_iter()
1158                .map(|(k, v)| (k, v.into()))
1159                .collect(),
1160        }
1161    }
1162}
1163
1164impl IotaRawMovePackage {
1165    pub fn to_move_package(
1166        &self,
1167        max_move_package_size: u64,
1168    ) -> Result<MovePackage, ExecutionError> {
1169        Ok(MovePackage::new(
1170            self.id,
1171            self.version.into(),
1172            self.module_map
1173                .iter()
1174                .map(|(k, v)| (Identifier::new_unchecked(k), v.clone()))
1175                .collect(),
1176            max_move_package_size,
1177            self.type_origin_table.clone(),
1178            self.linkage_table
1179                .clone()
1180                .into_iter()
1181                .map(|(k, v)| (k, v.into()))
1182                .collect(),
1183        )?)
1184    }
1185}
1186
1187#[serde_as]
1188#[derive(Serialize, Deserialize, Debug, JsonSchema, Clone, PartialEq, Eq)]
1189#[serde(tag = "status", content = "details", rename = "ObjectRead")]
1190#[expect(clippy::large_enum_variant)]
1191pub enum IotaPastObjectResponse {
1192    /// The object exists and is found with this version
1193    VersionFound(IotaObjectData),
1194    /// The object does not exist
1195    ObjectNotExists(
1196        #[serde_as(as = "ObjectIdSchema")]
1197        #[schemars(with = "ObjectIdSchema")]
1198        ObjectId,
1199    ),
1200    /// The object is found to be deleted with this version
1201    ObjectDeleted(
1202        #[schemars(with = "ObjectRefSchema")]
1203        #[serde_as(as = "ObjectRefSchema")]
1204        ObjectReference,
1205    ),
1206    /// The object exists but not found with this version
1207    VersionNotFound(
1208        #[serde_as(as = "ObjectIdSchema")]
1209        #[schemars(with = "ObjectIdSchema")]
1210        ObjectId,
1211        SequenceNumberU64,
1212    ),
1213    /// The asked object version is higher than the latest
1214    VersionTooHigh {
1215        #[serde_as(as = "ObjectIdSchema")]
1216        #[schemars(with = "ObjectIdSchema")]
1217        object_id: ObjectId,
1218        asked_version: SequenceNumberU64,
1219        latest_version: SequenceNumberU64,
1220    },
1221}
1222
1223impl IotaPastObjectResponse {
1224    /// Returns a reference to the object if there is any, otherwise an Err
1225    pub fn object(&self) -> UserInputResult<&IotaObjectData> {
1226        match &self {
1227            Self::ObjectDeleted(oref) => Err(UserInputError::ObjectDeleted { object_ref: *oref }),
1228            Self::ObjectNotExists(id) => Err(UserInputError::ObjectNotFound {
1229                object_id: *id,
1230                version: None,
1231            }),
1232            Self::VersionFound(o) => Ok(o),
1233            Self::VersionNotFound(id, seq_num) => Err(UserInputError::ObjectNotFound {
1234                object_id: *id,
1235                version: Some((*seq_num).into()),
1236            }),
1237            Self::VersionTooHigh {
1238                object_id,
1239                asked_version,
1240                latest_version,
1241            } => Err(UserInputError::ObjectSequenceNumberTooHigh {
1242                object_id: *object_id,
1243                asked_version: (*asked_version).into(),
1244                latest_version: (*latest_version).into(),
1245            }),
1246        }
1247    }
1248
1249    /// Returns the object value if there is any, otherwise an Err
1250    pub fn into_object(self) -> UserInputResult<IotaObjectData> {
1251        match self {
1252            Self::ObjectDeleted(oref) => Err(UserInputError::ObjectDeleted { object_ref: oref }),
1253            Self::ObjectNotExists(id) => Err(UserInputError::ObjectNotFound {
1254                object_id: id,
1255                version: None,
1256            }),
1257            Self::VersionFound(o) => Ok(o),
1258            Self::VersionNotFound(object_id, version) => Err(UserInputError::ObjectNotFound {
1259                object_id,
1260                version: Some(version.into()),
1261            }),
1262            Self::VersionTooHigh {
1263                object_id,
1264                asked_version,
1265                latest_version,
1266            } => Err(UserInputError::ObjectSequenceNumberTooHigh {
1267                object_id,
1268                asked_version: asked_version.into(),
1269                latest_version: latest_version.into(),
1270            }),
1271        }
1272    }
1273}
1274
1275#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
1276#[serde(rename = "MovePackage", rename_all = "camelCase")]
1277pub struct IotaMovePackage {
1278    pub disassembled: BTreeMap<String, Value>,
1279}
1280
1281pub type QueryObjectsPage = Page<IotaObjectResponse, CheckpointedObjectID>;
1282pub type ObjectsPage = Page<IotaObjectResponse, ObjectId>;
1283
1284#[serde_as]
1285#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Copy, Eq, PartialEq)]
1286#[serde(rename_all = "camelCase")]
1287pub struct CheckpointedObjectID {
1288    #[serde_as(as = "ObjectIdSchema")]
1289    #[schemars(with = "ObjectIdSchema")]
1290    pub object_id: ObjectId,
1291    #[schemars(with = "Option<String>")]
1292    #[serde_as(as = "Option<DisplayFromStr>")]
1293    #[serde(skip_serializing_if = "Option::is_none")]
1294    pub at_checkpoint: Option<CheckpointSequenceNumber>,
1295}
1296
1297#[serde_as]
1298#[derive(Debug, Deserialize, Serialize, JsonSchema, Clone, Eq, PartialEq)]
1299#[serde(rename = "GetPastObjectRequest", rename_all = "camelCase")]
1300pub struct IotaGetPastObjectRequest {
1301    /// the ID of the queried object
1302    #[serde_as(as = "ObjectIdSchema")]
1303    #[schemars(with = "ObjectIdSchema")]
1304    pub object_id: ObjectId,
1305    /// the version of the queried object.
1306    #[schemars(with = "SequenceNumberStringSchema")]
1307    #[serde_as(as = "SequenceNumberStringSchema")]
1308    pub version: Version,
1309}
1310
1311#[serde_as]
1312#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
1313pub enum IotaObjectDataFilter {
1314    MatchAll(Vec<IotaObjectDataFilter>),
1315    MatchAny(Vec<IotaObjectDataFilter>),
1316    MatchNone(Vec<IotaObjectDataFilter>),
1317    /// Query by type a specified Package.
1318    Package(
1319        #[serde_as(as = "ObjectIdSchema")]
1320        #[schemars(with = "ObjectIdSchema")]
1321        ObjectId,
1322    ),
1323    /// Query by type a specified Move module.
1324    MoveModule {
1325        /// the Move package ID
1326        #[serde_as(as = "ObjectIdSchema")]
1327        #[schemars(with = "ObjectIdSchema")]
1328        package: ObjectId,
1329        /// the module name
1330        #[serde_as(as = "IdentifierSchema")]
1331        #[schemars(with = "IdentifierSchema")]
1332        module: Identifier,
1333    },
1334    /// Query by type
1335    StructType(
1336        #[schemars(with = "StructTagSchema")]
1337        #[serde_as(as = "StructTagSchema")]
1338        StructTag,
1339    ),
1340    AddressOwner(
1341        #[serde_as(as = "AddressSchema")]
1342        #[schemars(with = "AddressSchema")]
1343        Address,
1344    ),
1345    ObjectOwner(
1346        #[serde_as(as = "ObjectIdSchema")]
1347        #[schemars(with = "ObjectIdSchema")]
1348        ObjectId,
1349    ),
1350    ObjectId(
1351        #[serde_as(as = "ObjectIdSchema")]
1352        #[schemars(with = "ObjectIdSchema")]
1353        ObjectId,
1354    ),
1355    // allow querying for multiple object ids
1356    ObjectIds(
1357        #[serde_as(as = "Vec<ObjectIdSchema>")]
1358        #[schemars(with = "Vec<ObjectIdSchema>")]
1359        Vec<ObjectId>,
1360    ),
1361    Version(
1362        #[serde_as(as = "DisplayFromStr")]
1363        #[schemars(with = "String")]
1364        u64,
1365    ),
1366}
1367
1368impl IotaObjectDataFilter {
1369    pub fn gas_coin() -> Self {
1370        Self::StructType(StructTag::new_gas_coin())
1371    }
1372
1373    pub fn and(self, other: Self) -> Self {
1374        Self::MatchAll(vec![self, other])
1375    }
1376    pub fn or(self, other: Self) -> Self {
1377        Self::MatchAny(vec![self, other])
1378    }
1379    pub fn not(self, other: Self) -> Self {
1380        Self::MatchNone(vec![self, other])
1381    }
1382
1383    pub fn matches(&self, object: &ObjectInfo) -> bool {
1384        match self {
1385            IotaObjectDataFilter::MatchAll(filters) => !filters.iter().any(|f| !f.matches(object)),
1386            IotaObjectDataFilter::MatchAny(filters) => filters.iter().any(|f| f.matches(object)),
1387            IotaObjectDataFilter::MatchNone(filters) => !filters.iter().any(|f| f.matches(object)),
1388            IotaObjectDataFilter::StructType(s) => {
1389                let obj_tag: StructTag = match &object.object_type {
1390                    ObjectType::Package => return false,
1391                    ObjectType::Struct(s) => s.clone().into(),
1392                };
1393                // If people do not provide type_params, we will match all type_params
1394                // e.g. `0x2::coin::Coin` can match `0x2::coin::Coin<0x2::iota::IOTA>`
1395                if !s.type_params().is_empty() && s.type_params() != obj_tag.type_params() {
1396                    false
1397                } else {
1398                    obj_tag.address() == s.address()
1399                        && obj_tag.module() == s.module()
1400                        && obj_tag.name() == s.name()
1401                }
1402            }
1403            IotaObjectDataFilter::MoveModule { package, module } => {
1404                matches!(&object.object_type, ObjectType::Struct(s) if &ObjectId::from(s.address()) == package
1405                        && s.module() == module)
1406            }
1407            IotaObjectDataFilter::Package(p) => {
1408                matches!(&object.object_type, ObjectType::Struct(s) if &ObjectId::from(s.address()) == p)
1409            }
1410            IotaObjectDataFilter::AddressOwner(a) => {
1411                matches!(object.owner, Owner::Address(addr) if &addr == a)
1412            }
1413            IotaObjectDataFilter::ObjectOwner(o) => {
1414                matches!(object.owner, Owner::Object(addr) if &addr == o)
1415            }
1416            IotaObjectDataFilter::ObjectId(id) => &object.object_id == id,
1417            IotaObjectDataFilter::ObjectIds(ids) => ids.contains(&object.object_id),
1418            IotaObjectDataFilter::Version(v) => object.version == *v,
1419        }
1420    }
1421}
1422
1423#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Default)]
1424#[serde(rename_all = "camelCase", rename = "ObjectResponseQuery", default)]
1425pub struct IotaObjectResponseQuery {
1426    /// If None, no filter will be applied
1427    pub filter: Option<IotaObjectDataFilter>,
1428    /// config which fields to include in the response, by default only digest
1429    /// is included
1430    pub options: Option<IotaObjectDataOptions>,
1431}
1432
1433impl IotaObjectResponseQuery {
1434    pub fn new(
1435        filter: Option<IotaObjectDataFilter>,
1436        options: Option<IotaObjectDataOptions>,
1437    ) -> Self {
1438        Self { filter, options }
1439    }
1440
1441    pub fn new_with_filter(filter: IotaObjectDataFilter) -> Self {
1442        Self {
1443            filter: Some(filter),
1444            options: None,
1445        }
1446    }
1447
1448    pub fn new_with_options(options: IotaObjectDataOptions) -> Self {
1449        Self {
1450            filter: None,
1451            options: Some(options),
1452        }
1453    }
1454}