sui_rpc_api/grpc/v2/ledger_service/
get_object.rs1use crate::ErrorReason;
5use crate::RpcError;
6use crate::RpcService;
7use crate::error::ObjectNotFoundError;
8use prost_types::FieldMask;
9use sui_rpc::field::FieldMaskTree;
10use sui_rpc::field::FieldMaskUtil;
11use sui_rpc::proto::google::rpc::bad_request::FieldViolation;
12use sui_rpc::proto::sui::rpc::v2::BatchGetObjectsRequest;
13use sui_rpc::proto::sui::rpc::v2::BatchGetObjectsResponse;
14use sui_rpc::proto::sui::rpc::v2::GetObjectRequest;
15use sui_rpc::proto::sui::rpc::v2::GetObjectResponse;
16use sui_rpc::proto::sui::rpc::v2::GetObjectResult;
17use sui_rpc::proto::sui::rpc::v2::Object;
18use sui_sdk_types::Address;
19use sui_types::full_checkpoint_content::ObjectSet;
20
21pub const MAX_BATCH_REQUESTS: usize = 1000;
22pub const READ_MASK_DEFAULT: &str = "object_id,version,digest";
23
24type ValidationResult = Result<(Vec<(Address, Option<u64>)>, FieldMaskTree), RpcError>;
25
26pub fn validate_get_object_requests(
27 requests: Vec<(Option<String>, Option<u64>)>,
28 read_mask: Option<FieldMask>,
29) -> ValidationResult {
30 let read_mask = {
31 let read_mask = read_mask.unwrap_or_else(|| FieldMask::from_str(READ_MASK_DEFAULT));
32 read_mask.validate::<Object>().map_err(|path| {
33 FieldViolation::new("read_mask")
34 .with_description(format!("invalid read_mask path: {path}"))
35 .with_reason(ErrorReason::FieldInvalid)
36 })?;
37 FieldMaskTree::from(read_mask)
38 };
39 let requests = requests
40 .into_iter()
41 .enumerate()
42 .map(|(idx, (object_id, version))| {
43 let object_id = object_id
44 .as_ref()
45 .ok_or_else(|| {
46 FieldViolation::new("object_id")
47 .with_reason(ErrorReason::FieldMissing)
48 .nested_at("requests", idx)
49 })?
50 .parse()
51 .map_err(|e| {
52 FieldViolation::new("object_id")
53 .with_description(format!("invalid object_id: {e}"))
54 .with_reason(ErrorReason::FieldInvalid)
55 .nested_at("requests", idx)
56 })?;
57 Ok((object_id, version))
58 })
59 .collect::<Result<_, RpcError>>()?;
60 Ok((requests, read_mask))
61}
62
63#[tracing::instrument(skip(service))]
64pub fn get_object(
65 service: &RpcService,
66 GetObjectRequest {
67 object_id,
68 version,
69 read_mask,
70 ..
71 }: GetObjectRequest,
72) -> Result<GetObjectResponse, RpcError> {
73 let (requests, read_mask) =
74 validate_get_object_requests(vec![(object_id, version)], read_mask)?;
75 let (object_id, version) = requests[0];
76 get_object_impl(service, object_id, version, &read_mask).map(GetObjectResponse::new)
77}
78
79#[tracing::instrument(skip(service))]
80pub fn batch_get_objects(
81 service: &RpcService,
82 BatchGetObjectsRequest {
83 requests,
84 read_mask,
85 ..
86 }: BatchGetObjectsRequest,
87) -> Result<BatchGetObjectsResponse, RpcError> {
88 if requests.len() > MAX_BATCH_REQUESTS {
89 return Err(RpcError::new(
90 tonic::Code::InvalidArgument,
91 format!("number of batch requests exceed limit of {MAX_BATCH_REQUESTS}"),
92 ));
93 }
94
95 let requests = requests
96 .into_iter()
97 .map(|req| (req.object_id, req.version))
98 .collect();
99 let (requests, read_mask) = validate_get_object_requests(requests, read_mask)?;
100 let objects = requests
101 .into_iter()
102 .map(|(object_id, version)| get_object_impl(service, object_id, version, &read_mask))
103 .map(|result| match result {
104 Ok(object) => GetObjectResult::new_object(object),
105 Err(error) => GetObjectResult::new_error(error.into_status_proto()),
106 })
107 .collect();
108 Ok(BatchGetObjectsResponse::new(objects))
109}
110
111#[tracing::instrument(skip(service))]
112fn get_object_impl(
113 service: &RpcService,
114 object_id: Address,
115 version: Option<u64>,
116 read_mask: &FieldMaskTree,
117) -> Result<Object, RpcError> {
118 let object = if let Some(version) = version {
119 service
120 .reader
121 .inner()
122 .get_object_by_key(&object_id.into(), version.into())
123 .ok_or_else(|| ObjectNotFoundError::new_with_version(object_id, version))?
124 } else {
125 service
126 .reader
127 .inner()
128 .get_object(&object_id.into())
129 .ok_or_else(|| ObjectNotFoundError::new(object_id))?
130 };
131
132 Ok(service.render_object_to_proto(&object, read_mask, &ObjectSet::default()))
133}