sui_rpc_api/grpc/v2/ledger_service/
get_object.rs

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