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}