skyzen 0.2.1

A fast, ergonomic HTTP framework that works everywhere
use core::future::{ready, Future};
use std::convert::Infallible;
use std::fmt;

use http_kit::{HttpError, Request, StatusCode};
use skyzen_core::Extractor;

/// Extract param defined in route.
#[derive(Debug, Clone)]
pub struct Params(Vec<(String, String)>);

/// Error returned when attempting to read a missing route parameter.
#[derive(Debug, Clone)]
pub struct MissingParam {
    name: String,
}

impl MissingParam {
    /// Create an error naming the parameter that was missing.
    pub fn new(name: impl Into<String>) -> Self {
        Self { name: name.into() }
    }
}

impl fmt::Display for MissingParam {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Missing param `{}`", self.name)
    }
}

impl std::error::Error for MissingParam {}

impl HttpError for MissingParam {
    fn status(&self) -> StatusCode {
        StatusCode::BAD_REQUEST
    }
}

impl Params {
    pub(crate) const fn new(vec: Vec<(String, String)>) -> Self {
        Self(vec)
    }

    /// The captured parameters, in the order the route pattern declares them.
    ///
    /// [`Path<T>`](crate::extract::Path) deserializes from exactly this list; reach for it here
    /// only when the parameter names are not known until runtime.
    #[must_use]
    pub fn pairs(&self) -> &[(String, String)] {
        &self.0
    }

    pub(crate) const fn empty() -> Self {
        Self(Vec::new())
    }

    /// Get the route parameter by the name.
    ///
    /// # Errors
    ///
    /// Returns an error if the requested parameter is not present.
    pub fn get(&self, name: &str) -> Result<&str, MissingParam> {
        self.0
            .iter()
            .find_map(|(k, v)| if k == name { Some(v.as_str()) } else { None })
            .ok_or_else(|| MissingParam::new(name))
    }
}

impl Extractor for Params {
    type Error = Infallible;
    // Reading the params back out of the extensions is a synchronous clone, so the future is ready
    // on creation rather than an `async` block with nothing to await.
    fn extract(request: &mut Request) -> impl Future<Output = Result<Self, Self::Error>> + Send {
        // Clone rather than remove so the params survive repeated extraction.
        ready(Ok(request
            .extensions()
            .get::<Self>()
            .cloned()
            .unwrap_or(Self::empty())))
    }

    // Individual path parameters are derived from the route's `{name}` segments when the OpenAPI
    // document is built, so the whole-map `Params` extractor contributes no schema of its own.
}