webrune 0.1.2

A composable web server.
Documentation
use crate::Extract;
use crate::Request;
use serde::Deserializer;
use serde::de::{DeserializeOwned, MapAccess, Visitor, value};
use std::collections::HashMap;
use std::fmt::{Display, Formatter};
use std::ops::Deref;

/// Extractor for path parameters.
///
/// `Path<T>` deserializes route parameters into a user-defined type `T`
/// using [`serde`].
///
/// Path parameters are populated by the router and stored in the request’s
/// extensions. This extractor reads those parameters and deserializes them
/// as if they were a map.
///
/// # Example
///
/// Given a route definition:
///
/// ```ignore
/// router.get("/users/:id", handler);
/// ```
///
/// A handler can extract path parameters as follows:
///
/// ```ignore
/// async fn handler(request: Request, state: MyState) -> Result<Json<Payload>, MyError> {
///     let path = UserPath::extract(&request)?;
///     ...
/// }
///
/// #[derive(Deserialize)]
/// struct UserPath {
///     id: u64,
/// }
/// ```
///
/// # Errors
///
/// Extraction fails if:
/// - the request contains no path parameters, or
/// - deserialization into `T` fails.
pub struct Path<T>(pub T);

impl<T> Extract for Path<T>
where
    T: DeserializeOwned,
{
    /// Error type returned when path extraction fails.
    type Error = PathError;

    fn extract<B>(request: &Request<B>) -> Result<Self, Self::Error> {
        let params = request
            .extensions()
            .get::<HashMap<String, String>>()
            .ok_or_else(|| PathError("No parameters in the request.".to_string()))?;

        let path =
            T::deserialize(PathDeserializer(params)).map_err(|e| PathError(e.to_string()))?;

        Ok(Path(path))
    }
}

impl<T> Deref for Path<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

/// Error returned when path parameter extraction fails.
pub struct PathError(pub String);

impl Display for PathError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

/// A serde deserializer over route path parameters.
///
/// This deserializer presents a `HashMap<String, String>` as a map-like
/// input to serde, allowing arbitrary structs to be deserialized from
/// path parameters.
///
/// This type is an implementation detail of [`Path`] and should not
/// be used directly.
struct PathDeserializer<'a>(&'a HashMap<String, String>);

impl<'de, 'a> Deserializer<'de> for PathDeserializer<'a> {
    type Error = value::Error;

    fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
    where
        V: Visitor<'de>,
    {
        visitor.visit_map(PathMapAccess {
            iter: self.0.iter(),
            value_opt: None,
        })
    }

    serde::forward_to_deserialize_any! {
        bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string
        seq tuple tuple_struct map struct enum identifier ignored_any option unit
        bytes byte_buf unit_struct newtype_struct
    }
}

/// [`MapAccess`] implementation over path parameters.
///
/// Iterates over key/value pairs and feeds them into serde as string
/// deserializers.
struct PathMapAccess<'a> {
    iter: std::collections::hash_map::Iter<'a, String, String>,
    value_opt: Option<&'a String>,
}

impl<'de, 'a> MapAccess<'de> for PathMapAccess<'a> {
    type Error = value::Error;

    fn next_key_seed<K>(&mut self, seed: K) -> Result<Option<K::Value>, Self::Error>
    where
        K: serde::de::DeserializeSeed<'de>,
    {
        if let Some((k, v)) = self.iter.next() {
            self.value_opt = Some(v);
            let key_de = value::StrDeserializer::new(k);
            let key = seed.deserialize(key_de)?;
            Ok(Some(key))
        } else {
            Ok(None)
        }
    }

    fn next_value_seed<V>(&mut self, seed: V) -> Result<V::Value, Self::Error>
    where
        V: serde::de::DeserializeSeed<'de>,
    {
        let v = self.value_opt.take().expect("value missing for key");
        let val_de = value::StrDeserializer::new(v);
        seed.deserialize(val_de)
    }
}