mini-serve 0.13.8

An HTTP server: trie router, middleware, CORS, optional TLS. Built on hyper + tokio.
Documentation
use hyper::Request;
use serde::de::value::{Error as ValueError, MapDeserializer};
use serde::de::{self, DeserializeOwned, Deserializer, IntoDeserializer, Visitor};

use crate::error::ServeError;
use crate::router::{PathParams, QueryParams};

/// Shared empty maps handed out when a request carries no params.
///
/// The router and app skip inserting `PathParams` and `QueryParams` when they would be
/// empty, so most requests never pay to box one. Readers must not be able to tell the
/// difference between "absent" and "empty", or the saving is paid for with a silent
/// breakage at every call site — hence these, rather than each reader inventing its own
/// fallback. `HashMap::new` does not allocate, so neither of these ever does.
static EMPTY_PATH_PARAMS: std::sync::OnceLock<PathParams> = std::sync::OnceLock::new();
static EMPTY_QUERY_PARAMS: std::sync::OnceLock<QueryParams> = std::sync::OnceLock::new();

/// Read the request's query parameters, treating a request with no query string as one
/// with no parameters.
///
/// Always returns a map: a request whose query string was absent, empty, or unparseable
/// is indistinguishable here from one that carried `?` and nothing else.
///
/// # Example
///
/// ```ignore
/// use mini_serve::query_params;
///
/// let page = query_params(&req).0.get("page").cloned();
/// ```
pub fn query_params<B>(req: &Request<B>) -> &QueryParams {
	req.extensions()
		.get::<QueryParams>()
		.unwrap_or_else(|| EMPTY_QUERY_PARAMS.get_or_init(QueryParams::default))
}

/// Extract path parameters from the request and deserialize into type `T`.
///
/// Path parameters are decoded and matched by the router, then deserialized
/// via serde's `MapDeserializer`. Returns `400 Bad Request` if deserialization
/// fails (e.g., an unparseable segment for a numeric type) or if the route
/// captured no parameters at all — asking a param-less route for its params is
/// a bad request, not a server fault, and it used to report `500`.
///
/// # Example
///
/// ```ignore
/// use serde::Deserialize;
/// use mini_serve::path_params;
///
/// #[derive(Deserialize)]
/// struct ItemId {
///     id: u64,
/// }
///
/// let item = path_params::<ItemId, _>(req)?;
/// println!("Item ID: {}", item.id);
/// ```
pub fn path_params<T: DeserializeOwned, B>(req: &Request<B>) -> Result<T, ServeError> {
	let params = req
		.extensions()
		.get::<PathParams>()
		.unwrap_or_else(|| EMPTY_PATH_PARAMS.get_or_init(PathParams::default));

	let pairs = params.0.iter().map(|(k, v)| (k.clone(), ParamValue(v.clone())));
	let deserializer = MapDeserializer::<_, ValueError>::new(pairs);
	T::deserialize(deserializer)
		.map_err(|_| ServeError::new(400, "invalid path parameters"))
}

/// Deserializes a single path-param string into whatever scalar type the
/// target struct field asks for, parsing on demand rather than going through
/// an intermediate query-string representation.
struct ParamValue(String);

macro_rules! deserialize_parsed {
	($($method:ident => $visit:ident : $ty:ty),* $(,)?) => {
		$(
			fn $method<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
				match self.0.parse::<$ty>() {
					Ok(v) => visitor.$visit(v),
					Err(_) => Err(de::Error::invalid_value(
						de::Unexpected::Str(&self.0),
						&stringify!($ty),
					)),
				}
			}
		)*
	};
}

impl<'de> Deserializer<'de> for ParamValue {
	type Error = ValueError;

	fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
		visitor.visit_string(self.0)
	}

	fn deserialize_option<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Self::Error> {
		visitor.visit_some(self)
	}

	deserialize_parsed! {
		deserialize_bool => visit_bool: bool,
		deserialize_i8 => visit_i8: i8,
		deserialize_i16 => visit_i16: i16,
		deserialize_i32 => visit_i32: i32,
		deserialize_i64 => visit_i64: i64,
		deserialize_i128 => visit_i128: i128,
		deserialize_u8 => visit_u8: u8,
		deserialize_u16 => visit_u16: u16,
		deserialize_u32 => visit_u32: u32,
		deserialize_u64 => visit_u64: u64,
		deserialize_u128 => visit_u128: u128,
		deserialize_f32 => visit_f32: f32,
		deserialize_f64 => visit_f64: f64,
		deserialize_char => visit_char: char,
	}

	serde::forward_to_deserialize_any! {
		str string bytes byte_buf unit unit_struct newtype_struct seq tuple
		tuple_struct map struct enum identifier ignored_any
	}
}

impl<'de> IntoDeserializer<'de, ValueError> for ParamValue {
	type Deserializer = Self;

	fn into_deserializer(self) -> Self {
		self
	}
}

#[cfg(test)]
#[path = "../tests/unit/extract.rs"]
mod tests;