use std::fmt;
use std::borrow::Cow;
use crate::http::uri::{self, Origin};
use crate::http::ext::IntoOwned;
use crate::form::ValueField;
use crate::route::Segment;
#[derive(Clone)]
pub struct RouteUri<'a> {
source: Cow<'a, str>,
pub base: Origin<'a>,
pub unmounted_origin: Origin<'a>,
pub origin: Origin<'a>,
pub(crate) metadata: Metadata,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Color {
Static = 3,
Partial = 2,
Wild = 1,
}
#[derive(Debug, Clone)]
pub(crate) struct Metadata {
pub base_segs: Vec<Segment>,
pub path_segs: Vec<Segment>,
pub static_query_fields: Vec<(String, String)>,
pub path_color: Color,
pub query_color: Option<Color>,
pub trailing_path: bool,
}
type Result<T, E = uri::Error<'static>> = std::result::Result<T, E>;
impl<'a> RouteUri<'a> {
pub(crate) fn try_new(base: &str, uri: &str) -> Result<RouteUri<'static>> {
let mut base = Origin::parse(base)
.map_err(|e| e.into_owned())?
.into_normalized()
.into_owned();
base.clear_query();
let unmounted_origin = Origin::parse_route(uri)
.map_err(|e| e.into_owned())?
.into_normalized()
.into_owned();
let origin = Origin::parse_route(&format!("{}/{}", base, unmounted_origin))
.map_err(|e| e.into_owned())?
.into_normalized()
.into_owned();
let source = origin.to_string().into();
let metadata = Metadata::from(&base, &origin);
Ok(RouteUri { source, base, unmounted_origin, origin, metadata })
}
#[track_caller]
pub(crate) fn new(base: &str, uri: &str) -> RouteUri<'static> {
Self::try_new(base, uri).expect("Expected valid URIs")
}
#[inline(always)]
pub fn base(&self) -> &str {
self.base.path().as_str()
}
#[inline(always)]
pub fn path(&self) -> &str {
self.origin.path().as_str()
}
#[inline(always)]
pub fn query(&self) -> Option<&str> {
self.origin.query().map(|q| q.as_str())
}
#[inline(always)]
pub fn as_str(&self) -> &str {
&self.source
}
pub(crate) fn default_rank(&self) -> isize {
let raw_path_weight = self.metadata.path_color as u8;
let raw_query_weight = self.metadata.query_color.map_or(0, |c| c as u8);
let raw_weight = (raw_path_weight << 2) | raw_query_weight;
-((raw_weight as isize) - 3)
}
}
impl Metadata {
fn from(base: &Origin<'_>, origin: &Origin<'_>) -> Self {
let base_segs = base.path().raw_segments()
.map(Segment::from)
.collect::<Vec<_>>();
let path_segs = origin.path().raw_segments()
.map(Segment::from)
.collect::<Vec<_>>();
let query_segs = origin.query()
.map(|q| q.raw_segments().map(Segment::from).collect::<Vec<_>>())
.unwrap_or_default();
let static_query_fields = query_segs.iter().filter(|s| !s.dynamic)
.map(|s| ValueField::parse(&s.value))
.map(|f| (f.name.source().to_string(), f.value.to_string()))
.collect();
let static_path = path_segs.iter().all(|s| !s.dynamic);
let wild_path = !path_segs.is_empty() && path_segs.iter().all(|s| s.dynamic);
let path_color = match (static_path, wild_path) {
(true, _) => Color::Static,
(_, true) => Color::Wild,
(_, _) => Color::Partial
};
let query_color = (!query_segs.is_empty()).then(|| {
let static_query = query_segs.iter().all(|s| !s.dynamic);
let wild_query = query_segs.iter().all(|s| s.dynamic);
match (static_query, wild_query) {
(true, _) => Color::Static,
(_, true) => Color::Wild,
(_, _) => Color::Partial
}
});
let trailing_path = path_segs.last().map_or(false, |p| p.trailing);
Metadata {
base_segs, path_segs, static_query_fields, path_color, query_color,
trailing_path,
}
}
}
impl<'a> std::ops::Deref for RouteUri<'a> {
type Target = Origin<'a>;
fn deref(&self) -> &Self::Target {
&self.origin
}
}
impl fmt::Display for RouteUri<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.origin.fmt(f)
}
}
impl fmt::Debug for RouteUri<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RouteUri")
.field("base", &self.base)
.field("unmounted_origin", &self.unmounted_origin)
.field("origin", &self.origin)
.field("metadata", &self.metadata)
.finish()
}
}
impl<'a, 'b> PartialEq<Origin<'b>> for RouteUri<'a> {
fn eq(&self, other: &Origin<'b>) -> bool { &self.origin == other }
}
impl PartialEq<str> for RouteUri<'_> {
fn eq(&self, other: &str) -> bool { self.as_str() == other }
}
impl PartialEq<&str> for RouteUri<'_> {
fn eq(&self, other: &&str) -> bool { self.as_str() == *other }
}