fnroute 0.1.0

A small function router with axum-style handler extraction.
Documentation
use alloc::{
    collections::BTreeMap,
    string::{String, ToString},
};
use core::{fmt, str::FromStr};

use crate::{RouteContext, RouteError, RouteResult};

pub trait FromRoute<S>: Sized {
    fn from_route(context: &RouteContext, state: &S) -> RouteResult<Self>;
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct State<S>(pub S);

impl<S> FromRoute<S> for State<S>
where
    S: Clone,
{
    fn from_route(_context: &RouteContext, state: &S) -> RouteResult<Self> {
        Ok(Self(state.clone()))
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Param<T>(pub T);

impl<S, T> FromRoute<S> for Param<T>
where
    T: FromStr,
    T::Err: fmt::Display,
{
    fn from_route(context: &RouteContext, _state: &S) -> RouteResult<Self> {
        let value = match context.params.len() {
            0 => {
                return Err(RouteError::Extract {
                    message: "missing route parameter".to_string(),
                });
            }
            1 => context.params.values().next().expect("length checked"),
            _ => {
                return Err(RouteError::Extract {
                    message: "Param<T> expects exactly one parameter; use Params for multiple"
                        .to_string(),
                });
            }
        };

        value.parse().map(Self).map_err(|err| RouteError::Extract {
            message: err.to_string(),
        })
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Params(pub BTreeMap<String, String>);

impl<S> FromRoute<S> for Params {
    fn from_route(context: &RouteContext, _state: &S) -> RouteResult<Self> {
        Ok(Self(context.params.clone()))
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Input<T>(pub T);

impl<S, T> FromRoute<S> for Input<T>
where
    T: Clone + Send + Sync + 'static,
{
    fn from_route(context: &RouteContext, _state: &S) -> RouteResult<Self> {
        context
            .input
            .as_ref()
            .and_then(|input| input.downcast_ref::<T>())
            .cloned()
            .map(Self)
            .ok_or_else(|| RouteError::Extract {
                message: alloc::format!("missing input {}", core::any::type_name::<T>()),
            })
    }
}

impl<S> FromRoute<S> for RouteContext {
    fn from_route(context: &RouteContext, _state: &S) -> RouteResult<Self> {
        Ok(context.clone())
    }
}