Skip to main content

sova_core/extract/
mod.rs

1//! Typed request extractors (`Path`, `Query`, `Json`, …) for [`IntoHandler`](crate::handler::IntoHandler).
2//!
3//! Existing `async fn(req: Request)` handlers keep working. Extractor handlers use separate
4//! marker impls so they do not conflict with `Fn(Request)`.
5
6mod form;
7mod json;
8mod path;
9mod query;
10mod state;
11
12pub use form::Form;
13pub use json::Json;
14pub use path::Path;
15pub use query::Query;
16pub use state::{Extension, State};
17
18use crate::error::{Error, Result};
19use crate::request::Request;
20use std::future::Future;
21use std::pin::Pin;
22
23/// Boxed future tied to the request borrow (body extractors).
24pub type ExtractFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
25
26/// Extract a value from request parts (no body).
27pub trait FromRequestParts: Sized + Send {
28    fn from_request_parts(req: &Request) -> Result<Self>;
29}
30
31/// Extract a value from the request (may read the body).
32pub trait FromRequest: Sized + Send {
33    fn from_request(req: &mut Request) -> ExtractFuture<'_, Result<Self>>;
34}
35
36impl<T> FromRequest for T
37where
38    T: FromRequestParts + 'static,
39{
40    fn from_request(req: &mut Request) -> ExtractFuture<'_, Result<Self>> {
41        Box::pin(async move { T::from_request_parts(req) })
42    }
43}
44
45/// Deserialize path params (string map) into `T` via JSON intermediate.
46pub(crate) fn params_as<T: serde::de::DeserializeOwned>(req: &Request) -> Result<T> {
47    let map: serde_json::Map<String, serde_json::Value> = req
48        .params
49        .iter()
50        .map(|(k, v)| (k.clone(), serde_json::Value::String(v.clone())))
51        .collect();
52    serde_json::from_value(serde_json::Value::Object(map))
53        .map_err(|e| Error::BadRequest(format!("path params: {e}")))
54}