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