use crate::route::Route;
use std::fmt::Write;
#[allow(bare_trait_objects)]
pub type Routable = Switch;
pub trait Switch: Sized {
fn switch<STATE>(route: Route<STATE>) -> Option<Self> {
Self::from_route_part(route.route, Some(route.state)).0
}
fn from_route_part<STATE>(part: String, state: Option<STATE>) -> (Option<Self>, Option<STATE>);
fn build_route_section<STATE>(self, route: &mut String) -> Option<STATE>;
fn key_not_available() -> Option<Self> {
None
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct LeadingSlash<T>(pub T);
impl<U: Switch> Switch for LeadingSlash<U> {
fn from_route_part<STATE>(part: String, state: Option<STATE>) -> (Option<Self>, Option<STATE>) {
if let Some(part) = part.strip_prefix('/') {
let (inner, state) = U::from_route_part(part.to_owned(), state);
(inner.map(LeadingSlash), state)
} else {
(None, None)
}
}
fn build_route_section<T>(self, route: &mut String) -> Option<T> {
write!(route, "/").ok()?;
self.0.build_route_section(route)
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct Permissive<U>(pub Option<U>);
impl<U: Switch> Switch for Permissive<U> {
fn from_route_part<STATE>(part: String, state: Option<STATE>) -> (Option<Self>, Option<STATE>) {
let (inner, inner_state) = U::from_route_part(part, state);
if inner.is_some() {
(Some(Permissive(inner)), inner_state)
} else {
(Some(Permissive(None)), None)
}
}
fn build_route_section<STATE>(self, route: &mut String) -> Option<STATE> {
if let Some(inner) = self.0 {
inner.build_route_section(route)
} else {
None
}
}
fn key_not_available() -> Option<Self> {
Some(Permissive(None))
}
}
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct AllowMissing<U: std::fmt::Debug>(pub Option<U>);
impl<U: Switch + std::fmt::Debug> Switch for AllowMissing<U> {
fn from_route_part<STATE>(part: String, state: Option<STATE>) -> (Option<Self>, Option<STATE>) {
let route = part.clone();
let (inner, inner_state) = U::from_route_part(part, state);
if inner.is_some() {
(Some(AllowMissing(inner)), inner_state)
} else if route.is_empty()
|| route.starts_with('/')
|| route.starts_with('?')
|| route.starts_with('&')
|| route.starts_with('#')
{
(Some(AllowMissing(None)), inner_state)
} else {
(None, None)
}
}
fn build_route_section<STATE>(self, route: &mut String) -> Option<STATE> {
if let AllowMissing(Some(inner)) = self {
inner.build_route_section(route)
} else {
None
}
}
}
fn build_route_from_switch<SW: Switch, STATE: Default>(switch: SW) -> Route<STATE> {
let mut buf = String::with_capacity(255);
let state: STATE = switch.build_route_section(&mut buf).unwrap_or_default();
buf.shrink_to_fit();
Route { route: buf, state }
}
impl<SW: Switch, STATE: Default> From<SW> for Route<STATE> {
fn from(switch: SW) -> Self {
build_route_from_switch(switch)
}
}
impl<T: std::str::FromStr + std::fmt::Display> Switch for T {
fn from_route_part<U>(part: String, state: Option<U>) -> (Option<Self>, Option<U>) {
(::std::str::FromStr::from_str(&part).ok(), state)
}
fn build_route_section<U>(self, route: &mut String) -> Option<U> {
write!(route, "{}", self).expect("Writing to string should never fail.");
None
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn isize_build_route() {
let mut route = "/".to_string();
let mut _state: Option<String> = None;
_state = _state.or_else(|| (-432isize).build_route_section(&mut route));
assert_eq!(route, "/-432".to_string());
}
#[test]
fn can_get_string_from_empty_str() {
let (s, _state) = String::from_route_part::<()>("".to_string(), Some(()));
assert_eq!(s, Some("".to_string()))
}
#[test]
fn uuid_from_route() {
let x = uuid::Uuid::switch::<()>(Route {
route: "5dc48134-35b5-4b8c-aa93-767bf00ae1d8".to_string(),
state: (),
});
assert!(x.is_some())
}
#[test]
fn uuid_to_route() {
use std::str::FromStr;
let id =
uuid::Uuid::from_str("5dc48134-35b5-4b8c-aa93-767bf00ae1d8").expect("should parse");
let mut buf = String::new();
id.build_route_section::<()>(&mut buf);
assert_eq!(buf, "5dc48134-35b5-4b8c-aa93-767bf00ae1d8".to_string())
}
#[test]
fn can_get_option_string_from_empty_str() {
let (s, _state): (Option<Permissive<String>>, Option<()>) =
Permissive::from_route_part("".to_string(), Some(()));
assert_eq!(s, Some(Permissive(Some("".to_string()))))
}
}