via 2.0.0-gm.19

An async multi-threaded web framework for people who appreciate simplicity.
Documentation
use std::borrow::Cow;
use std::str::FromStr;
use via_router::Ident;

use super::query::QueryParser;
use crate::util::UriEncoding;
use crate::{Error, raise};

pub(super) type ParamRange = [Option<usize>; 2];
pub(super) type PathParamEntry = (Ident, ParamRange);
pub(super) type QueryParamEntry<'a> = (Cow<'a, str>, Option<ParamRange>);

pub struct Param<'a, 'b> {
    encoding: UriEncoding,
    source: Option<&'a str>,
    name: &'b str,
    at: Option<ParamRange>,
}

pub struct PathParams<'a> {
    path: &'a str,
    at: &'a [PathParamEntry],
}

pub struct QueryParams<'a> {
    query: Option<&'a str>,
    at: Vec<QueryParamEntry<'a>>,
}

fn query_pos_for_key(predicate: &str, key: &str, value: &Option<ParamRange>) -> Option<ParamRange> {
    if key == predicate { *value } else { None }
}

impl<'a> PathParams<'a> {
    pub(crate) fn new(path: &'a str, at: &'a [PathParamEntry]) -> Self {
        Self { path, at }
    }

    pub fn get<'b>(&self, name: &'b str) -> Param<'a, 'b> {
        let at = self.at.iter().find_map(|(key, value)| {
            if key.as_ref() == name {
                Some(*value)
            } else {
                None
            }
        });

        Param::new(Some(self.path), name, at)
    }
}

impl<'a> QueryParams<'a> {
    pub(crate) fn new(query: Option<&'a str>) -> Self {
        let at = query
            .map(|input| QueryParser::new(input).collect())
            .unwrap_or_default();

        Self { query, at }
    }

    pub fn all<'b>(&self, name: &'b str) -> impl Iterator<Item = Param<'a, 'b>> {
        self.at.iter().filter_map(move |(key, value)| {
            if key.as_ref() == name {
                Some(Param::new(self.query, name, *value))
            } else {
                None
            }
        })
    }

    pub fn contains(&self, name: &str) -> bool {
        self.at.iter().any(|(key, _)| key.as_ref() == name)
    }

    pub fn first<'b>(&self, name: &'b str) -> Param<'a, 'b> {
        let at = self
            .at
            .iter()
            .find_map(|(key, value)| query_pos_for_key(name, key, value));

        Param::new(self.query, name, at)
    }

    pub fn last<'b>(&self, name: &'b str) -> Param<'a, 'b> {
        let at = self
            .at
            .iter()
            .rev()
            .find_map(|(key, value)| query_pos_for_key(name, key, value));

        Param::new(self.query, name, at)
    }
}

impl<'a, 'b> Param<'a, 'b> {
    #[inline]
    fn new(source: Option<&'a str>, name: &'b str, at: Option<ParamRange>) -> Self {
        Self {
            encoding: UriEncoding::Unencoded,
            source,
            name,
            at: at.or(Some([Some(0); 2])),
        }
    }

    #[inline]
    fn slice(&self) -> Option<&'a str> {
        self.source
            .zip(self.at)
            .and_then(|(path, span)| match span {
                [Some(from), Some(to)] if from == to => None,
                [Some(from), Some(to)] => path.get(from..to),
                [Some(from), None] => path.get(from..),
                [None, _] => None,
            })
    }

    /// Returns a new `Param` that will percent-decode the parameter value with
    /// when the parameter is converted to a result.
    ///
    #[inline]
    pub fn decode(self) -> Self {
        Self {
            encoding: UriEncoding::Percent,
            ..self
        }
    }

    pub fn optional(self) -> Result<Option<Cow<'a, str>>, Error> {
        self.slice()
            .map(|value| self.encoding.decode_as(self.name, value))
            .transpose()
    }

    /// Calls [`str::parse`] on the parameter value if it exists and returns the
    /// result. If the param is encoded, it will be decoded before it is parsed.
    ///
    pub fn parse<U>(self) -> Result<U, Error>
    where
        U: FromStr,
        U::Err: std::error::Error + Send + Sync + 'static,
    {
        self.into_result()?
            .as_ref()
            .parse()
            .or_else(|error| raise!(400, error))
    }

    /// Returns a result with the parameter value if it exists. If the param is
    /// encoded, it will be decoded before it is returned.
    ///
    /// # Errors
    ///
    /// If the parameter does not exist or could not be decoded with the
    /// implementation of `T::decode`, an error is returned with a 400 Bad
    /// Request status code.
    ///
    pub fn into_result(self) -> Result<Cow<'a, str>, Error> {
        self.slice()
            .map(|value| self.encoding.decode_as(self.name, value))
            .unwrap_or_else(|| {
                raise!(
                    400,
                    message = format!("missing required parameter: \"{}\"", self.name)
                )
            })
    }
}