iota_rest_kv/routes/
kv_store.rs1use std::num::NonZeroUsize;
5
6use axum::{
7 Json,
8 body::Body,
9 extract::{Path, Query, State},
10 http::StatusCode,
11 response::IntoResponse,
12};
13use iota_kvstore::client::TransactionSequenceNumber;
14use iota_sdk_types::Address;
15use iota_storage::http_key_value_store::{ItemType, Key};
16use serde::Deserialize;
17
18use crate::{
19 bigtable::{ObjectRangeKeyBound, ObjectsBeforeVersionRequest},
20 errors::ApiError,
21 extractors::ExtractPath,
22 types::SharedRestServerAppState,
23};
24
25const BEFORE_VERSION_REQUIRES_OB_ERROR_MSG: &str =
26 "`before_version` query parameter is only valid for `ob` item types";
27
28#[derive(Deserialize, Debug)]
30pub(crate) struct MultiGetRequest {
31 pub(crate) keys: Vec<String>,
33}
34
35#[derive(Deserialize, Debug, Default)]
37pub(crate) struct BeforeVersion {
38 #[serde(default)]
39 pub(crate) before_version: bool,
40}
41
42pub async fn data_as_bytes(
61 State(app_state): State<SharedRestServerAppState>,
62 ExtractPath(key): ExtractPath,
63 Query(BeforeVersion { before_version }): Query<BeforeVersion>,
64) -> Result<impl IntoResponse, ApiError> {
65 tracing::debug!(?key, before_version, "get item");
66
67 if before_version {
68 let range = ObjectRangeKeyBound::try_from(key)
69 .map_err(|_| ApiError::BadRequest(BEFORE_VERSION_REQUIRES_OB_ERROR_MSG.into()))?;
70
71 let response = app_state
72 .kv_store_client
73 .object_before_version(range)
74 .await?;
75
76 return Ok(response.map_or_else(
77 || (StatusCode::NOT_FOUND, Body::empty()).into_response(),
78 |bytes| bytes.into_response(),
79 ));
80 }
81
82 app_state
83 .kv_store_client
84 .get(key)
85 .await
86 .map(|res| match res {
87 Some(bytes) => bytes.into_response(),
88 None => (StatusCode::NOT_FOUND, Body::empty()).into_response(),
89 })
90}
91
92pub async fn multi_get_data(
132 State(app_state): State<SharedRestServerAppState>,
133 Path(item_type): Path<ItemType>,
134 Query(BeforeVersion { before_version }): Query<BeforeVersion>,
135 Json(payload): Json<MultiGetRequest>,
136) -> Result<impl IntoResponse, ApiError> {
137 if payload.keys.is_empty() {
138 return Err(ApiError::BadRequest("no keys provided".into()));
139 }
140
141 if payload.keys.len() > app_state.multiget_max_items.get() {
142 return Err(ApiError::BadRequest(format!(
143 "too many keys: requested {}, maximum allowed is {}",
144 payload.keys.len(),
145 app_state.multiget_max_items
146 )));
147 }
148
149 tracing::debug!(
150 %item_type,
151 num_keys = payload.keys.len(),
152 before_version,
153 "multi-get items"
154 );
155
156 let item_type_str = item_type.to_string();
157 let keys = payload
158 .keys
159 .iter()
160 .map(|encoded_key| {
161 Key::new(item_type_str.as_str(), encoded_key.as_str())
162 .map_err(|err| ApiError::BadRequest(format!("invalid key '{encoded_key}': {err}")))
163 })
164 .collect::<Result<Vec<Key>, ApiError>>()?;
165
166 let results = if before_version {
167 let request = ObjectsBeforeVersionRequest::try_from(keys)
168 .map_err(|_| ApiError::BadRequest(BEFORE_VERSION_REQUIRES_OB_ERROR_MSG.into()))?;
169 app_state
170 .kv_store_client
171 .objects_before_version(request)
172 .await?
173 } else {
174 app_state.kv_store_client.get_items(keys).await?
175 };
176
177 let bcs_data = bcs::to_bytes(&results)
178 .map_err(Into::into)
179 .map_err(ApiError::InternalServerError)?;
180 Ok(bcs_data.into_response())
181}
182
183#[derive(Deserialize, Debug)]
184pub(crate) struct TransactionDigestsByAddressQuery {
185 pub(crate) cursor: Option<TransactionSequenceNumber>,
186 pub(crate) limit: Option<NonZeroUsize>,
187 #[serde(default)]
188 pub(crate) oldest_first: bool,
189}
190
191pub async fn transaction_digests_by_address(
220 State(app_state): State<SharedRestServerAppState>,
221 Path(address): Path<String>,
222 Query(query): Query<TransactionDigestsByAddressQuery>,
223) -> Result<impl IntoResponse, ApiError> {
224 let address = base64_url::decode(&address)
225 .map_err(|_| ApiError::BadRequest("address is not valid base64-url".into()))?;
226
227 let address = Address::from_bytes(&address)
228 .map_err(|_| ApiError::BadRequest("invalid address".into()))?;
229
230 let TransactionDigestsByAddressQuery {
231 cursor,
232 limit,
233 oldest_first,
234 } = query;
235
236 tracing::debug!(
237 %address,
238 ?cursor,
239 ?limit,
240 oldest_first,
241 "get transaction digests by address"
242 );
243
244 let max_limit = app_state.multiget_max_items.get();
245 let limit = limit.map_or(max_limit, |l| l.get());
246
247 if limit > max_limit {
248 return Err(ApiError::BadRequest(format!(
249 "limit too large: maximum allowed is {max_limit}",
250 )));
251 }
252
253 let transactions = app_state
254 .kv_store_client
255 .transactions_by_address(address, cursor, limit, oldest_first)
256 .await?;
257
258 let bcs_data = bcs::to_bytes(&transactions)
259 .map_err(Into::into)
260 .map_err(ApiError::InternalServerError)?;
261 Ok(bcs_data.into_response())
262}