use lazy_regex::regex;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::path::{Path, PathBuf};
#[derive(Clone, Debug, PartialEq, PartialOrd, Ord, Eq, Hash, Serialize)]
pub struct IPR(String);
impl AsRef<str> for IPR {
fn as_ref(&self) -> &str {
&self.0
}
}
impl<T> PartialEq<T> for IPR
where
T: ToIPR,
{
fn eq(&self, other: &T) -> bool {
self.eq(&other.to_ipr())
}
}
pub trait ToIPR: AsRef<str> {
fn to_ipr(&self) -> IPR {
let converted = IPR::canonize(self.as_ref());
IPR(converted.to_string())
}
}
impl ToIPR for &str {}
impl ToIPR for &&str {}
impl ToIPR for String {}
impl ToIPR for &String {}
impl From<&str> for IPR {
fn from(other: &str) -> IPR {
other.to_ipr()
}
}
impl From<&&str> for IPR {
fn from(other: &&str) -> IPR {
other.to_ipr()
}
}
impl From<String> for IPR {
fn from(other: String) -> IPR {
other.to_ipr()
}
}
impl From<&String> for IPR {
fn from(other: &String) -> IPR {
other.to_ipr()
}
}
#[derive(Debug)]
pub struct NonUnicodePath(pub PathBuf);
impl std::fmt::Display for NonUnicodePath {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
write!(f, "Path wasn't unicode: {:?}", self.0)
}
}
impl std::error::Error for NonUnicodePath {}
impl From<NonUnicodePath> for std::io::Error {
fn from(p: NonUnicodePath) -> Self {
Self::other(p)
}
}
impl TryFrom<&Path> for IPR {
type Error = NonUnicodePath;
fn try_from(other: &Path) -> Result<IPR, Self::Error> {
Ok(other
.to_str()
.ok_or_else(|| NonUnicodePath(other.to_path_buf()))?
.into())
}
}
impl TryFrom<PathBuf> for IPR {
type Error = NonUnicodePath;
fn try_from(other: PathBuf) -> Result<IPR, Self::Error> {
let p: &Path = other.as_ref();
Self::try_from(p)
}
}
struct IPRVisitor;
impl<'de> serde::de::Visitor<'de> for IPRVisitor {
type Value = IPR;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("a path string (IPR)")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(value.to_ipr())
}
}
impl<'de> Deserialize<'de> for IPR {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
deserializer.deserialize_str(IPRVisitor)
}
}
impl IPR {
pub fn is_well_formed(src: &str) -> bool {
enum SM {
Strt,
Slsh,
Dot1,
Dot2,
Char,
}
let mut sm = SM::Strt;
for c in src.chars() {
sm = match (sm, c) {
(SM::Strt, '/') => return false, (SM::Strt, '.') => SM::Dot1,
(SM::Strt, ___) => SM::Char,
(SM::Slsh, '/') => return false, (SM::Slsh, '.') => SM::Dot1,
(SM::Slsh, ___) => SM::Char,
(SM::Dot1, '/') => return false, (SM::Dot1, '.') => SM::Dot2,
(SM::Dot1, ___) => SM::Char,
(SM::Dot2, '/') => return false, (SM::Dot2, '.') => SM::Char, (SM::Dot2, ___) => SM::Char,
(SM::Char, '/') => SM::Slsh,
(SM::Char, '.') => SM::Char,
(SM::Char, ___) => SM::Char,
}
}
match sm {
SM::Strt => true,
SM::Slsh => false,
SM::Dot1 => false,
SM::Dot2 => false,
SM::Char => true,
}
}
pub fn canonize<'a>(src: &'a str) -> Cow<'a, str> {
if IPR::is_well_formed(&src) {
Cow::Borrowed(src)
} else {
Cow::Owned(Self::force_canonize(&src))
}
}
pub fn force_canonize(src: &str) -> String {
let r = regex!("/");
r.split(src)
.filter(|s| *s != "" && *s != "." && *s != "..")
.collect::<Vec<&str>>()
.join("/")
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn is_well_formed() {
fn check(case: &str, expected: bool) {
assert_eq!(IPR::is_well_formed(case), expected, "Failed on: {:?}", case);
}
check("", true);
check("foo", true);
check("foo/bar", true);
check("/foo/bar", false);
check("/foo/bar/", false);
check("foo/bar////", false);
check("////foo////bar", false);
check(".", false);
check("..", false);
check("...", true);
check("....", true);
check(".....", true);
check("foo.", true);
check("a/./b", false);
check("a/../b", false);
check("a/.../b", true);
check("a/..../b", true);
check("a/...../b", true);
}
#[test]
fn canonize() {
fn check(case: &str, expected: &str) {
assert_eq!(
IPR::canonize(case),
expected,
"IPR::canonize failed on: {:?}",
case
);
assert_eq!(
IPR::force_canonize(case),
expected,
"IPR::force_canonize failed on: {:?}",
case
);
}
check("", "");
check("foo", "foo");
check("foo/bar", "foo/bar");
check("/foo/bar", "foo/bar");
check("/foo/bar/", "foo/bar");
check("foo/bar////", "foo/bar");
check("////foo////bar", "foo/bar");
check(".", "");
check("..", "");
check("...", "...");
check("....", "....");
check(".....", ".....");
check("foo.", "foo.");
check("a/./b", "a/b");
check("a/../b", "a/b");
check("a/.../b", "a/.../b");
check("a/..../b", "a/..../b");
check("a/...../b", "a/...../b");
}
#[test]
fn convert_str() {
let original: &str = "/hello/world/";
let converted: IPR = original.into();
assert_eq!(converted.0, "hello/world".to_owned());
}
#[test]
fn convert_string() {
let original: String = "/hello/world/".to_owned();
let converted: IPR = original.into();
assert_eq!(converted.0, "hello/world".to_owned());
}
#[test]
fn convert_path() {
let original: &Path = Path::new("/foo/bar//baz");
let converted: IPR = original.try_into().expect("That's basically sane");
assert_eq!(converted.0, "foo/bar/baz");
}
#[test]
fn convert_pathbuf() {
let original: PathBuf = Path::new("/foo/bar//baz").to_path_buf();
let converted: IPR = original.try_into().expect("That's basically sane");
assert_eq!(converted.0, "foo/bar/baz");
}
#[test]
fn serialize() {
let some_path: IPR = "/fixme/".into();
let serialized = serde_json::to_string(&some_path).expect("Serialized OK");
assert_eq!(serialized, "\"fixme\"");
}
#[test]
fn deserialize() {
let serialized = "\"/fixme/\""; let deserialized: IPR = serde_json::from_str(&serialized).expect("Deserialized OK");
assert_eq!(deserialized.0, "fixme".to_owned());
}
}