use std::borrow::Cow;
use crate::ext::IntoOwned;
use crate::parse::{Extent, IndexedStr, uri::tables::is_pchar};
use crate::uri::{Error, Path, Query, Data, as_utf8_unchecked, fmt};
use crate::{RawStr, RawStrBuf};
#[derive(Debug, Clone)]
pub struct Origin<'a> {
pub(crate) source: Option<Cow<'a, str>>,
pub(crate) path: Data<'a, fmt::Path>,
pub(crate) query: Option<Data<'a, fmt::Query>>,
}
impl<'a> Origin<'a> {
#[doc(hidden)]
pub const ROOT: Origin<'static> = Origin::const_new("/", None);
#[inline]
pub(crate) unsafe fn raw(
source: Cow<'a, [u8]>,
path: Extent<&'a [u8]>,
query: Option<Extent<&'a [u8]>>
) -> Origin<'a> {
Origin {
source: Some(as_utf8_unchecked(source)),
path: Data::raw(path),
query: query.map(Data::raw)
}
}
#[doc(hidden)]
pub fn new<P, Q>(path: P, query: Option<Q>) -> Origin<'a>
where P: Into<Cow<'a, str>>, Q: Into<Cow<'a, str>>
{
Origin {
source: None,
path: Data::new(path.into()),
query: query.map(Data::new),
}
}
#[doc(hidden)]
pub fn path_only<P: Into<Cow<'a, str>>>(path: P) -> Origin<'a> {
Origin::new(path, None::<&'a str>)
}
#[doc(hidden)]
pub const fn const_new(path: &'a str, query: Option<&'a str>) -> Origin<'a> {
Origin {
source: None,
path: Data {
value: IndexedStr::Concrete(Cow::Borrowed(path)),
decoded_segments: state::InitCell::new(),
},
query: match query {
Some(query) => Some(Data {
value: IndexedStr::Concrete(Cow::Borrowed(query)),
decoded_segments: state::InitCell::new(),
}),
None => None,
},
}
}
pub(crate) fn set_query<Q: Into<Option<Cow<'a, str>>>>(&mut self, query: Q) {
self.query = query.into().map(Data::new);
}
pub fn parse(string: &'a str) -> Result<Origin<'a>, Error<'a>> {
crate::parse::uri::origin_from_str(string)
}
#[doc(hidden)]
pub fn parse_route(string: &'a str) -> Result<Origin<'a>, Error<'a>> {
use pear::error::Expected;
if !string.starts_with('/') {
return Err(Error {
expected: Expected::token(Some(&b'/'), string.as_bytes().get(0).cloned()),
index: 0,
});
}
let (path, query) = RawStr::new(string).split_at_byte(b'?');
let query = (!query.is_empty()).then(|| query.as_str());
Ok(Origin::new(path.as_str(), query))
}
pub fn parse_owned(string: String) -> Result<Origin<'static>, Error<'static>> {
let origin = Origin::parse(&string).map_err(|e| e.into_owned())?;
debug_assert!(origin.source.is_some(), "Origin parsed w/o source");
Ok(Origin {
path: origin.path.into_owned(),
query: origin.query.into_owned(),
source: Some(Cow::Owned(string))
})
}
#[inline]
pub fn path(&self) -> Path<'_> {
Path { source: &self.source, data: &self.path }
}
#[inline]
pub fn query(&self) -> Option<Query<'_>> {
self.query.as_ref().map(|data| Query { source: &self.source, data })
}
#[inline]
pub fn map_path<'s, F, P>(&'s self, f: F) -> Option<Self>
where F: FnOnce(&'s RawStr) -> P, P: Into<RawStrBuf> + 's
{
let path = f(self.path().raw()).into();
if !path.starts_with('/') || !path.as_bytes().iter().all(is_pchar) {
return None;
}
Some(Origin {
source: self.source.clone(),
path: Data::new(Cow::from(path.into_string())),
query: self.query.clone(),
})
}
pub fn clear_query(&mut self) {
self.set_query(None);
}
pub fn is_normalized(&self) -> bool {
self.path().is_normalized(true) && self.query().map_or(true, |q| q.is_normalized())
}
pub fn normalize(&mut self) {
if !self.path().is_normalized(true) {
self.path = self.path().to_normalized(true);
}
if let Some(query) = self.query() {
if !query.is_normalized() {
self.query = query.to_normalized();
}
}
}
pub fn into_normalized(mut self) -> Self {
self.normalize();
self
}
}
impl_serde!(Origin<'a>, "an origin-form URI");
impl_traits!(Origin, path, query);
impl std::fmt::Display for Origin<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.path())?;
if let Some(query) = self.query() {
write!(f, "?{}", query)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::Origin;
fn seg_count(path: &str, expected: usize) -> bool {
let origin = Origin::parse(path).unwrap();
let segments = origin.path().segments();
let actual = segments.len();
if actual != expected {
eprintln!("Count mismatch: expected {}, got {}.", expected, actual);
eprintln!("{}", if actual != expected { "lifetime" } else { "buf" });
eprintln!("Segments (for {}):", path);
for (i, segment) in segments.enumerate() {
eprintln!("{}: {}", i, segment);
}
}
actual == expected
}
fn eq_segments(path: &str, expected: &[&str]) -> bool {
let uri = match Origin::parse(path) {
Ok(uri) => uri,
Err(e) => panic!("failed to parse {}: {}", path, e)
};
let actual: Vec<&str> = uri.path().segments().collect();
actual == expected
}
#[test]
fn send_and_sync() {
fn assert<T: Send + Sync>() {}
assert::<Origin<'_>>();
}
#[test]
fn simple_segment_count() {
assert!(seg_count("/", 0));
assert!(seg_count("/a", 1));
assert!(seg_count("/a/", 1));
assert!(seg_count("/a/", 1));
assert!(seg_count("/a/b", 2));
assert!(seg_count("/a/b/", 2));
assert!(seg_count("/a/b/", 2));
assert!(seg_count("/ab/", 1));
}
#[test]
fn segment_count() {
assert!(seg_count("////", 0));
assert!(seg_count("//a//", 1));
assert!(seg_count("//abc//", 1));
assert!(seg_count("//abc/def/", 2));
assert!(seg_count("//////abc///def//////////", 2));
assert!(seg_count("/a/b/c/d/e/f/g", 7));
assert!(seg_count("/a/b/c/d/e/f/g", 7));
assert!(seg_count("/a/b/c/d/e/f/g/", 7));
assert!(seg_count("/a/b/cdjflk/d/e/f/g", 7));
assert!(seg_count("//aaflja/b/cdjflk/d/e/f/g", 7));
assert!(seg_count("/a/b", 2));
}
#[test]
fn single_segments_match() {
assert!(eq_segments("/", &[]));
assert!(eq_segments("/a", &["a"]));
assert!(eq_segments("/a/", &["a"]));
assert!(eq_segments("///a/", &["a"]));
assert!(eq_segments("///a///////", &["a"]));
assert!(eq_segments("/a///////", &["a"]));
assert!(eq_segments("//a", &["a"]));
assert!(eq_segments("/abc", &["abc"]));
assert!(eq_segments("/abc/", &["abc"]));
assert!(eq_segments("///abc/", &["abc"]));
assert!(eq_segments("///abc///////", &["abc"]));
assert!(eq_segments("/abc///////", &["abc"]));
assert!(eq_segments("//abc", &["abc"]));
}
#[test]
fn multi_segments_match() {
assert!(eq_segments("/a/b/c", &["a", "b", "c"]));
assert!(eq_segments("/a/b", &["a", "b"]));
assert!(eq_segments("/a///b", &["a", "b"]));
assert!(eq_segments("/a/b/c/d", &["a", "b", "c", "d"]));
assert!(eq_segments("///a///////d////c", &["a", "d", "c"]));
assert!(eq_segments("/abc/abc", &["abc", "abc"]));
assert!(eq_segments("/abc/abc/", &["abc", "abc"]));
assert!(eq_segments("///abc///////a", &["abc", "a"]));
assert!(eq_segments("/////abc/b", &["abc", "b"]));
assert!(eq_segments("//abc//c////////d", &["abc", "c", "d"]));
}
#[test]
fn multi_segments_match_funky_chars() {
assert!(eq_segments("/a/b/c!!!", &["a", "b", "c!!!"]));
}
#[test]
fn segment_mismatch() {
assert!(!eq_segments("/", &["a"]));
assert!(!eq_segments("/a", &[]));
assert!(!eq_segments("/a/a", &["a"]));
assert!(!eq_segments("/a/b", &["b", "a"]));
assert!(!eq_segments("/a/a/b", &["a", "b"]));
assert!(!eq_segments("///a/", &[]));
}
fn test_query(uri: &str, query: Option<&str>) {
let uri = Origin::parse(uri).unwrap();
assert_eq!(uri.query().map(|q| q.as_str()), query);
}
#[test]
fn query_does_not_exist() {
test_query("/test", None);
test_query("/a/b/c/d/e", None);
test_query("/////", None);
test_query("//a///", None);
test_query("/a/b/c", None);
test_query("/", None);
}
#[test]
fn query_exists() {
test_query("/test?abc", Some("abc"));
test_query("/a/b/c?abc", Some("abc"));
test_query("/a/b/c/d/e/f/g/?abc", Some("abc"));
test_query("/?123", Some("123"));
test_query("/?", Some(""));
test_query("/?", Some(""));
test_query("/?hi", Some("hi"));
}
#[test]
fn normalized() {
let uri_to_string = |s| Origin::parse(s)
.unwrap()
.into_normalized()
.to_string();
assert_eq!(uri_to_string("/"), "/".to_string());
assert_eq!(uri_to_string("//"), "/".to_string());
assert_eq!(uri_to_string("//////a/"), "/a".to_string());
assert_eq!(uri_to_string("//ab"), "/ab".to_string());
assert_eq!(uri_to_string("//a"), "/a".to_string());
assert_eq!(uri_to_string("/a/b///c"), "/a/b/c".to_string());
assert_eq!(uri_to_string("/a///b/c/d///"), "/a/b/c/d".to_string());
}
}