#![allow(clippy::module_name_repetitions)]
use crate::error::{Component, Error as IriError, ErrorKind, Result as IriResult};
use crate::pct_encoding::{path_map, pct_encode};
use crate::{parse, ValidateStr};
use crate::{Normalize, PercentEncoding};
use std::fmt::{Display, Formatter};
use std::str::FromStr;
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Path(String);
const PATH_SEP: &str = "/";
const DOT: &str = ".";
const DOT_DOT: &str = "..";
const WELL_KNOWN: &str = "/.well-known/";
impl Default for Path {
fn default() -> Self {
Self(String::new())
}
}
impl Display for Path {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for Path {
type Err = IriError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if Self::is_valid(s) {
Ok(Self(s.to_string()))
} else {
Err(ErrorKind::InvalidChar(Component::Path).into())
}
}
}
impl ValidateStr for Path {
fn is_valid(s: &str) -> bool {
parse::is_path(s)
}
}
impl Normalize for Path {
fn normalize(self) -> IriResult<Self> {
let mut segments = self.hierarchical_segments();
let mut index: usize = 0;
while index < segments.len() {
let segment = segments.get(index).unwrap();
if (segment.is_empty() && index != 0 && index != segments.len() - 1) || segment == DOT {
let _ = segments.remove(index);
} else if segment == DOT_DOT {
let _ = segments.remove(index);
if index > 0 {
index -= 1;
let _ = segments.remove(index);
}
} else {
index += 1;
}
}
Ok(Self(segments.join(PATH_SEP)))
}
}
impl PercentEncoding for Path {
fn encode(&self, for_uri: bool) -> Self
where
Self: Sized,
{
Self(pct_encode(&self.0, path_map(), for_uri))
}
}
impl Path {
pub fn root() -> Self {
Self(PATH_SEP.to_string())
}
pub fn well_known() -> Self {
Self(WELL_KNOWN.to_string())
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn is_absolute(&self) -> bool {
self.0.starts_with(PATH_SEP)
}
pub fn value(&self) -> &String {
&self.0
}
pub fn resolve(&self, relative_path: &Path) -> IriResult<Self> {
let new_path = if relative_path.is_empty() {
self.clone()
} else if relative_path.is_absolute() {
relative_path.clone()
} else if self.0.ends_with(PATH_SEP) {
let mut new = self.clone();
new.push(&relative_path.0)?;
new
} else {
let mut new = self.clone();
let _ = new.pop_slug();
new.push(&relative_path.0)?;
new
}
.normalize()?;
Ok(new_path)
}
pub fn is_normalized(&self) -> bool {
self.0
.split(PATH_SEP)
.all(|segment| segment != DOT && segment != DOT_DOT)
}
pub fn is_well_known(&self) -> bool {
self.0.starts_with(WELL_KNOWN)
}
fn hierarchical_segments(&self) -> Vec<String> {
self.0.split(PATH_SEP).map(|s| s.to_string()).collect()
}
pub fn push(&mut self, segment: &str) -> IriResult<()> {
if parse::is_path(segment) {
if self.0.ends_with(PATH_SEP) {
self.0 = format!("{}{}", self.0, segment);
} else {
self.0 = format!("{}/{}", self.0, segment);
}
Ok(())
} else {
Err(ErrorKind::InvalidChar(Component::Path).into())
}
}
pub fn pop(&mut self) -> Option<String> {
let mut segments = self.hierarchical_segments();
let last = segments.pop();
self.0 = segments.join(PATH_SEP);
last
}
pub fn has_slug(&self) -> bool {
!self.0.is_empty() && !self.0.ends_with(PATH_SEP)
}
pub fn slug(&mut self) -> Option<String> {
if self.has_slug() {
let segments = self.hierarchical_segments();
segments.last().cloned()
} else {
None
}
}
pub fn pop_slug(&mut self) -> Option<String> {
let mut segments = self.hierarchical_segments();
let last = segments.pop();
self.0 = segments.join(PATH_SEP);
if !self.0.is_empty() {
self.0 = format!("{}{}", self.0, PATH_SEP);
}
last
}
}