use std::fmt::{Display, Formatter};
use crate::parse::Error;
use crate::parse::Error::InvalidPath;
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug)]
pub struct Path<'a> {
path: &'a str,
}
impl Default for Path<'static> {
fn default() -> Self {
Self { path: "/" }
}
}
impl<'a> Path<'a> {
pub unsafe fn new(path: &'a str) -> Self {
debug_assert!(Self::is_valid(path));
Self { path }
}
}
impl<'a> TryFrom<&'a str> for Path<'a> {
type Error = Error;
fn try_from(path: &'a str) -> Result<Self, Self::Error> {
if Self::is_valid(path) {
Ok(Self { path })
} else {
Err(InvalidPath)
}
}
}
impl<'a> Path<'a> {
fn is_valid_char(c: u8) -> bool {
c.is_ascii_alphanumeric() || (c.is_ascii_punctuation() && c != b'?' && c != b'#')
}
pub fn is_valid(path: &str) -> bool {
!path.is_empty()
&& path.as_bytes()[0] == b'/'
&& path.as_bytes()[1..].iter().all(|c| Self::is_valid_char(*c))
}
}
impl<'a> Path<'a> {
pub const fn as_str(&self) -> &str {
self.path
}
}
impl<'a> AsRef<str> for Path<'a> {
fn as_ref(&self) -> &str {
self.path
}
}
impl<'a> Display for Path<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.path)
}
}
impl<'a> Path<'a> {
pub const fn iter_segments(&self) -> impl Iterator<Item = &'a str> {
SegmentIterator {
remaining: self.path,
}
}
}
struct SegmentIterator<'a> {
remaining: &'a str,
}
impl<'a> Iterator for SegmentIterator<'a> {
type Item = &'a str;
fn next(&mut self) -> Option<Self::Item> {
if self.remaining.is_empty() {
None
} else {
self.remaining = &self.remaining[1..];
if let Some(slash) = self.remaining.as_bytes().iter().position(|c| *c == b'/') {
let segment: &str = &self.remaining[..slash];
self.remaining = &self.remaining[slash..];
Some(segment)
} else {
let segment: &str = self.remaining;
self.remaining = "";
Some(segment)
}
}
}
}
#[cfg(test)]
mod tests {
use crate::parse::Error::InvalidPath;
use crate::Path;
#[test]
fn new() {
let path: Path = unsafe { Path::new("/the/path") };
assert_eq!(path.path, "/the/path");
}
#[test]
fn default() {
let path: Path = Path::default();
assert_eq!(path.as_str(), "/");
}
#[test]
fn try_from_str() {
assert_eq!(Path::try_from("/").unwrap().as_str(), "/");
assert_eq!(Path::try_from("/the/path").unwrap().as_str(), "/the/path");
assert_eq!(Path::try_from(""), Err(InvalidPath));
assert_eq!(Path::try_from("no-slash"), Err(InvalidPath));
assert_eq!(Path::try_from("/?"), Err(InvalidPath));
assert_eq!(Path::try_from("/#"), Err(InvalidPath));
}
#[test]
fn is_valid() {
let test_cases: &[(&str, bool)] = &[
("", false),
("/", true),
("///", true),
("/azAZ09", true),
("/!/&/=/~/", true),
("/?", false),
("/#", false),
];
for (path, expected) in test_cases {
let result: bool = Path::is_valid(path);
assert_eq!(result, *expected, "path={}", path);
}
}
#[test]
fn display() {
let path: Path = unsafe { Path::new("/the/path") };
assert_eq!(path.as_str(), "/the/path");
assert_eq!(path.as_ref(), "/the/path");
assert_eq!(path.to_string(), "/the/path");
}
#[test]
fn iter_segments() {
let path: Path = unsafe { Path::new("/") };
let result: Vec<&str> = path.iter_segments().collect();
let expected: Vec<&str> = vec![""];
assert_eq!(result, expected);
let path: Path = unsafe { Path::new("/the/path") };
let result: Vec<&str> = path.iter_segments().collect();
let expected: Vec<&str> = vec!["the", "path"];
assert_eq!(result, expected);
let path: Path = unsafe { Path::new("/the/path/") };
let result: Vec<&str> = path.iter_segments().collect();
let expected: Vec<&str> = vec!["the", "path", ""];
assert_eq!(result, expected)
}
}