iota_rest_kv/routes/kv_store.rs
1// Copyright (c) 2025 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3use axum::{
4 Json,
5 body::Body,
6 extract::{Path, State},
7 http::StatusCode,
8 response::IntoResponse,
9};
10use iota_storage::http_key_value_store::{ItemType, Key};
11use serde::Deserialize;
12
13use crate::{errors::ApiError, extractors::ExtractPath, types::SharedRestServerAppState};
14
15/// Request payload for multi_get_objects_post containing list of keys.
16#[derive(Deserialize, Debug)]
17pub struct MultiGetRequest {
18 /// List of base64url-encoded keys to retrieve.
19 pub keys: Vec<String>,
20}
21
22/// Retrieves data associated with a given key from the KV store as raw
23/// [`Bytes`](bytes::Bytes).
24///
25/// # Returns
26///
27/// * If the key exists, the data is returned as a [`Bytes`](bytes::Bytes)
28/// stream with a `200 OK` status code.
29/// * If the key does not exist, a `204 No Content` status code is returned with
30/// an empty body.
31/// * If an error occurs while interacting with the KV store, an `500 internal
32/// server error` is returned.
33pub async fn data_as_bytes(
34 ExtractPath(key): ExtractPath,
35 State(app_state): State<SharedRestServerAppState>,
36) -> Result<impl IntoResponse, ApiError> {
37 app_state
38 .kv_store_client
39 .get(key)
40 .await
41 .map(|res| match res {
42 Some(bytes) => bytes.into_response(),
43 None => (StatusCode::NOT_FOUND, Body::empty()).into_response(),
44 })
45}
46
47/// Retrieves multiple objects via POST request with JSON payload.
48///
49/// # Path Parameters
50///
51/// - `item_type`: The type of items to get (e.g., "cs", "cc", "tx")
52///
53/// # Request Body
54///
55/// JSON object with `keys` field:
56///
57/// ```json
58/// {
59/// "keys": ["AAEAAAAAAAAA", "AAIAAAAAAAAA", "AAMAAAAAAAAA"]
60/// }
61/// ```
62///
63/// Where:
64/// - `keys`: Array of base64url-encoded keys for given `item_type`. The same
65/// kind of key and encoding user would use in single item GET request.
66///
67/// # Returns
68///
69/// * If successful, returns a BCS-serialized
70/// [`Vec`]<[`Option`]<[`Bytes`](bytes::Bytes)>> with a `200 OK` status code.
71/// The vector has the same length and order as the `keys` list in the request
72/// body. Each entry is `Some(bytes)` if the key was found, or `None` if the
73/// key was not found.
74/// * If no keys are provided or the number of keys exceeds the configured
75/// `multiget_max_items` limit, a `400 bad request error` is returned.
76/// * If the keys cannot be parsed, a `400 bad request error` is returned.
77/// * If heterogenous key types are requested (e.g. checkpoints by sequence id
78/// and by digest), a `400 bad request error` is returned.
79/// * If an error occurs while interacting with the KV store, an `500 internal
80/// server error` is returned.
81pub async fn multi_get_data(
82 Path(item_type): Path<ItemType>,
83 State(app_state): State<SharedRestServerAppState>,
84 Json(payload): Json<MultiGetRequest>,
85) -> Result<impl IntoResponse, ApiError> {
86 if payload.keys.is_empty() {
87 return Err(ApiError::BadRequest("no keys provided".to_string()));
88 }
89
90 if payload.keys.len() > app_state.multiget_max_items.get() {
91 return Err(ApiError::BadRequest(format!(
92 "too many keys: requested {}, maximum allowed is {}",
93 payload.keys.len(),
94 app_state.multiget_max_items
95 )));
96 }
97
98 let item_type_str = item_type.to_string();
99 let keys = payload
100 .keys
101 .iter()
102 .map(|encoded_key| {
103 Key::new(item_type_str.as_str(), encoded_key.as_str())
104 .map_err(|err| ApiError::BadRequest(format!("invalid key '{encoded_key}': {err}")))
105 })
106 .collect::<Result<Vec<Key>, ApiError>>()?;
107
108 let results = app_state.kv_store_client.multi_get(keys).await?;
109
110 let bcs_data = bcs::to_bytes(&results).map_err(|_| ApiError::InternalServerError)?;
111 Ok(bcs_data.into_response())
112}