iota_rest_kv/
extractors.rs

1// Copyright (c) 2025 IOTA Stiftung
2// SPDX-License-Identifier: Apache-2.0
3
4//! This module includes custom extractors needed for validation and custom
5//! errors messages on the input provided by the client.
6
7use core::str;
8
9use axum::{
10    async_trait,
11    extract::{FromRequestParts, Path},
12    http::request::Parts,
13};
14use iota_storage::http_key_value_store::{ItemType, Key};
15use serde::Deserialize;
16
17use crate::errors::ApiError;
18
19/// Path segment labels will be matched with struct field names.
20#[derive(Deserialize, Debug)]
21struct RequestParams {
22    /// The supported items that are associated with the [`Key`].
23    item_type: ItemType,
24    /// The **digest**, **object id**, or a **checkpoint sequence number**
25    /// encoded as [`base64_url`].
26    key: String,
27}
28
29/// We define our own extractor that includes validation and custom error
30/// message.
31///
32/// This custom extractor matches [`Path`] segments and deserilize them
33/// internally into [`RequestParams`] and constructs a [`Key`].
34pub struct ExtractPath(pub Key);
35
36#[async_trait]
37impl<S> FromRequestParts<S> for ExtractPath
38where
39    S: Send + Sync,
40{
41    type Rejection = ApiError;
42
43    async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
44        match Path::<RequestParams>::from_request_parts(parts, state).await {
45            Ok(value) => {
46                // based on the item type and encoded key construct the Key enum
47                let key = Key::new(value.item_type.to_string().as_str(), value.key.as_str())
48                    .map_err(|err| ApiError::BadRequest(format!("invalid input: {err}")))?;
49                Ok(ExtractPath(key))
50            }
51            Err(e) => Err(ApiError::BadRequest(format!(
52                "invalid path parameter provided: {e}",
53            ))),
54        }
55    }
56}