iota_grpc_client/api/ledger/objects.rs
1// Copyright (c) 2026 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4//! High-level API for object queries.
5
6use iota_grpc_types::v1::{
7 ledger_service::{GetObjectsRequest, ObjectRequest, ObjectRequests},
8 object::Object,
9 types::ObjectReference,
10};
11use iota_sdk_types::{ObjectId, Version};
12
13use crate::{
14 Client,
15 api::{
16 Error, GET_OBJECTS_READ_MASK, MetadataEnvelope, ProtoResult, Result, collect_stream,
17 field_mask_with_default, proto_object_id, saturating_usize_to_u32,
18 },
19};
20
21impl Client {
22 /// Get objects by their IDs and optional versions.
23 ///
24 /// Returns proto `Object` types. Use `obj.object()` to convert to SDK
25 /// type, or use `obj.object_reference()` to get the object reference.
26 ///
27 /// Results are returned in the same order as the input refs.
28 /// If an object is not found, an error is returned.
29 ///
30 /// # Errors
31 ///
32 /// Returns [`Error::EmptyRequest`] if `refs` is empty.
33 ///
34 /// # Available Read Mask Fields
35 ///
36 /// The optional `read_mask` parameter controls which fields the server
37 /// returns. If `None`, uses [`GET_OBJECTS_READ_MASK`].
38 ///
39 /// ## Reference Fields
40 /// - `reference` - includes all reference fields
41 /// - `reference.object_id` - the ID of the object to fetch
42 /// - `reference.version` - the version of the object, which can be used
43 /// to fetch a specific historical version or the latest version if not
44 /// provided
45 /// - `reference.digest` - the digest of the object contents, which can be
46 /// used for integrity verification
47 ///
48 /// ## Data Fields
49 /// - `bcs` - the full BCS-encoded object
50 ///
51 /// # Example
52 ///
53 /// ```no_run
54 /// # use iota_grpc_client::Client;
55 /// # use iota_sdk_types::ObjectId;
56 /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
57 /// let client = Client::connect("http://localhost:9000").await?;
58 /// let object_id: ObjectId = "0x2".parse()?;
59 ///
60 /// // Get proto objects
61 /// let objs = client.get_objects(&[(object_id, None)], None).await?;
62 ///
63 /// for obj in objs.body() {
64 /// // Convert proto object to SDK type
65 /// let sdk_obj = obj.object()?;
66 /// println!("Got object ID: {:?}", sdk_obj.object_id());
67 /// let obj_ref = obj.object_reference()?;
68 /// println!("Object version: {:?}", obj_ref.version());
69 /// }
70 /// # Ok(())
71 /// # }
72 /// ```
73 pub async fn get_objects(
74 &self,
75 refs: &[(ObjectId, Option<Version>)],
76 read_mask: Option<&str>,
77 ) -> Result<MetadataEnvelope<Vec<Object>>> {
78 if refs.is_empty() {
79 return Err(Error::EmptyRequest);
80 }
81
82 let requests = ObjectRequests::default().with_requests(
83 refs.iter()
84 .map(|(id, version)| {
85 let mut object_ref =
86 ObjectReference::default().with_object_id(proto_object_id(*id));
87
88 if let Some(v) = version {
89 object_ref = object_ref.with_version(*v);
90 }
91
92 ObjectRequest::default().with_object_ref(object_ref)
93 })
94 .collect(),
95 );
96
97 let mut request = GetObjectsRequest::default()
98 .with_requests(requests)
99 .with_read_mask(field_mask_with_default(read_mask, GET_OBJECTS_READ_MASK));
100
101 if let Some(max_size) = self.max_decoding_message_size() {
102 request = request.with_max_message_size_bytes(saturating_usize_to_u32(max_size));
103 }
104
105 let mut client = self.ledger_service_client();
106
107 let response = client.get_objects(request).await?;
108 let (stream, metadata) = MetadataEnvelope::from(response).into_parts();
109
110 // Server guarantees results are returned in request order
111 collect_stream(stream, metadata, |msg| {
112 let items = msg
113 .objects
114 .into_iter()
115 .map(|r| r.into_result())
116 .collect::<Result<Vec<_>>>()?;
117 Ok((msg.has_next, items))
118 })
119 .await
120 }
121}