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_sdk2::types::{Address, ObjectId, StructTag, Version};
7use iota_types::iota_sdk2_conversions::struct_tag_core_to_sdk;
8use openapiv3::v3_1::Operation;
9use tap::Pipe;
10
11use crate::{
12    Page, 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 limit = parameters.limit();
56    let start = parameters.start();
57
58    let mut object_info = state
59        .inner()
60        .account_owned_objects_info_iter(address.into(), start)?
61        .take(limit + 1)
62        .map(|info| {
63            AccountOwnedObjectInfo {
64                owner: info.owner.into(),
65                object_id: info.object_id.into(),
66                version: info.version.into(),
67                type_: struct_tag_core_to_sdk(info.type_.into())?,
68            }
69            .pipe(Ok)
70        })
71        .collect::<Result<Vec<_>>>()?;
72
73    let cursor = if object_info.len() > limit {
74        // SAFETY: We've already verified that object_info is greater than limit, which
75        // is guaranteed to be >= 1.
76        object_info.pop().unwrap().object_id.pipe(Some)
77    } else {
78        None
79    };
80
81    object_info
82        .pipe(ResponseContent::Json)
83        .pipe(|entries| Page { entries, cursor })
84        .pipe(Ok)
85}
86
87#[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
88pub struct ListAccountOwnedObjectsQueryParameters {
89    pub limit: Option<u32>,
90    pub start: Option<ObjectId>,
91}
92
93impl ListAccountOwnedObjectsQueryParameters {
94    pub fn limit(&self) -> usize {
95        self.limit
96            .map(|l| (l as usize).clamp(1, crate::MAX_PAGE_SIZE))
97            .unwrap_or(crate::DEFAULT_PAGE_SIZE)
98    }
99
100    pub fn start(&self) -> Option<iota_types::base_types::ObjectID> {
101        self.start.map(Into::into)
102    }
103}
104
105#[serde_with::serde_as]
106#[derive(Debug, serde::Serialize, serde::Deserialize, schemars::JsonSchema)]
107pub struct AccountOwnedObjectInfo {
108    pub owner: Address,
109    pub object_id: ObjectId,
110    #[serde_as(as = "iota_types::iota_serde::BigInt<u64>")]
111    #[schemars(with = "crate::_schemars::U64")]
112    pub version: Version,
113    #[serde(rename = "type")]
114    pub type_: StructTag,
115}