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