iota_rest_api/
accounts.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use axum::extract::{Path, Query, State};
6use iota_sdk_types::{Address, ObjectId, StructTag, Version};
7use iota_types::iota_sdk_types_conversions::struct_tag_core_to_sdk;
8use openapiv3::v3_1::Operation;
9use tap::Pipe;
10
11use crate::{
12    Page, RestError, RestService, Result,
13    openapi::{ApiEndpoint, OperationBuilder, ResponseBuilder, RouteHandler},
14    reader::StateReader,
15    response::ResponseContent,
16};
17
18pub struct ListAccountObjects;
19
20impl ApiEndpoint<RestService> for ListAccountObjects {
21    fn method(&self) -> axum::http::Method {
22        axum::http::Method::GET
23    }
24
25    fn path(&self) -> &'static str {
26        "/accounts/{account}/objects"
27    }
28
29    fn operation(&self, generator: &mut schemars::gen::SchemaGenerator) -> Operation {
30        OperationBuilder::new()
31            .tag("Account")
32            .operation_id("ListAccountObjects")
33            .path_parameter::<Address>("account", generator)
34            .query_parameters::<ListAccountOwnedObjectsQueryParameters>(generator)
35            .response(
36                200,
37                ResponseBuilder::new()
38                    .json_content::<Vec<AccountOwnedObjectInfo>>(generator)
39                    .header::<String>(crate::types::X_IOTA_CURSOR, generator)
40                    .build(),
41            )
42            .build()
43    }
44
45    fn handler(&self) -> crate::openapi::RouteHandler<RestService> {
46        RouteHandler::new(self.method(), list_account_objects)
47    }
48}
49
50async fn list_account_objects(
51    Path(address): Path<Address>,
52    Query(parameters): Query<ListAccountOwnedObjectsQueryParameters>,
53    State(state): State<StateReader>,
54) -> Result<Page<AccountOwnedObjectInfo, ObjectId>> {
55    let indexes = state.inner().indexes().ok_or_else(RestError::not_found)?;
56    let limit = parameters.limit();
57    let start = parameters.start();
58
59    let mut object_info = indexes
60        .account_owned_objects_info_iter(address.into(), start)?
61        .take(limit + 1)
62        .map(|result| {
63            result
64                .map_err(|err| {
65                    RestError::new(
66                        axum::http::StatusCode::INTERNAL_SERVER_ERROR,
67                        err.to_string(),
68                    )
69                })
70                .and_then(|info| {
71                    AccountOwnedObjectInfo {
72                        owner: info.owner.into(),
73                        object_id: info.object_id.into(),
74                        version: info.version.into(),
75                        type_: struct_tag_core_to_sdk(info.type_.into())?,
76                    }
77                    .pipe(Ok)
78                })
79        })
80        .collect::<Result<Vec<_>, _>>()?;
81
82    let cursor = if object_info.len() > limit {
83        // SAFETY: We've already verified that object_info is greater than limit, which
84        // is guaranteed to be >= 1.
85        object_info.pop().unwrap().object_id.pipe(Some)
86    } else {
87        None
88    };
89
90    object_info
91        .pipe(ResponseContent::Json)
92        .pipe(|entries| Page { entries, cursor })
93        .pipe(Ok)
94}
95
96#[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
97pub struct ListAccountOwnedObjectsQueryParameters {
98    pub limit: Option<u32>,
99    pub start: Option<ObjectId>,
100}
101
102impl ListAccountOwnedObjectsQueryParameters {
103    pub fn limit(&self) -> usize {
104        self.limit
105            .map(|l| (l as usize).clamp(1, crate::MAX_PAGE_SIZE))
106            .unwrap_or(crate::DEFAULT_PAGE_SIZE)
107    }
108
109    pub fn start(&self) -> Option<iota_types::base_types::ObjectID> {
110        self.start.map(Into::into)
111    }
112}
113
114#[serde_with::serde_as]
115#[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
116pub struct AccountOwnedObjectInfo {
117    pub owner: Address,
118    pub object_id: ObjectId,
119    #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
120    #[schemars(with = "crate::_schemars::U64")]
121    pub version: Version,
122    #[serde(rename = "type")]
123    pub type_: StructTag,
124}