use core::{
cmp::Ordering,
fmt,
hash::{Hash, Hasher},
str,
};
use unicode_ident::{is_xid_continue, is_xid_start};
use crate::{
str::Str,
value::{FromValue, ToValue, Value},
};
#[derive(Clone)]
pub struct Path<'a>(Str<'a>);
impl<'a, 'b> From<&'a Path<'b>> for Path<'a> {
fn from(value: &'a Path<'b>) -> Self {
value.by_ref()
}
}
impl<'a> ToValue for Path<'a> {
fn to_value(&self) -> Value<'_> {
self.0.to_value()
}
}
impl<'a> FromValue<'a> for Path<'a> {
fn from_value(value: Value<'a>) -> Option<Self> {
Path::new_str(value.cast()?).ok()
}
}
impl Path<'static> {
pub fn new(path: &'static str) -> Result<Self, InvalidPathError> {
Path::new_str(Str::new(path))
}
pub const fn new_raw(path: &'static str) -> Self {
Path::new_str_raw(Str::new(path))
}
}
impl<'a> Path<'a> {
pub fn new_ref(path: &'a str) -> Result<Self, InvalidPathError> {
Self::new_str(Str::new_ref(path))
}
pub const fn new_ref_raw(path: &'a str) -> Self {
Self::new_str_raw(Str::new_ref(path))
}
pub fn new_str(path: Str<'a>) -> Result<Self, InvalidPathError> {
if is_valid_path(path.get()) {
Ok(Path(path))
} else {
Err(InvalidPathError {})
}
}
pub const fn new_str_raw(path: Str<'a>) -> Self {
Path(path)
}
pub fn by_ref<'b>(&'b self) -> Path<'b> {
Path(self.0.by_ref())
}
pub fn segments(&self) -> Segments<'_> {
Segments {
inner: match self.0.get_static() {
Some(inner) => SegmentsInner::Static(inner.split("::")),
None => SegmentsInner::Borrowed(self.0.get().split("::")),
},
}
}
pub fn is_child_of<'b>(&self, other: &Path<'b>) -> bool {
let child = self.0.get();
let parent = other.0.get();
if child.is_char_boundary(parent.len()) {
let (child_prefix, child_suffix) = child.split_at(parent.len());
child_prefix == parent && (child_suffix.is_empty() || child_suffix.starts_with("::"))
} else {
false
}
}
}
pub fn is_valid_path(path: &str) -> bool {
if path.len() == 0 {
return false;
}
if path.starts_with(':') {
return false;
}
let mut separators = 0;
for c in path.chars() {
match c {
':' if separators == 0 => {
separators = 1;
}
':' if separators == 1 => {
separators = 2;
}
c if separators % 2 == 0 && is_xid_start(c) => {
separators = 0;
}
c if is_xid_continue(c) => (),
_ => return false,
}
}
if separators != 0 {
return false;
}
true
}
#[derive(Debug)]
pub struct InvalidPathError {}
impl fmt::Display for InvalidPathError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "the path is not valid")
}
}
#[cfg(feature = "std")]
impl std::error::Error for InvalidPathError {}
pub struct Segments<'a> {
inner: SegmentsInner<'a>,
}
enum SegmentsInner<'a> {
Borrowed(str::Split<'a, &'static str>),
Static(str::Split<'static, &'static str>),
}
impl<'a> Iterator for Segments<'a> {
type Item = Str<'a>;
fn next(&mut self) -> Option<Self::Item> {
match self.inner {
SegmentsInner::Borrowed(ref mut inner) => inner.next().map(Str::new_ref),
SegmentsInner::Static(ref mut inner) => inner.next().map(Str::new),
}
}
}
impl<'a> Eq for Path<'a> {}
impl<'a, 'b> PartialEq<Path<'b>> for Path<'a> {
fn eq(&self, other: &Path<'b>) -> bool {
self.0 == other.0
}
}
impl<'a, 'b, 'c> PartialEq<&'c Path<'b>> for Path<'a> {
fn eq(&self, other: &&'c Path<'b>) -> bool {
self.0 == other.0
}
}
impl<'a, 'b, 'c> PartialEq<Path<'c>> for &'b Path<'a> {
fn eq(&self, other: &Path<'c>) -> bool {
self.0 == other.0
}
}
impl<'a> Hash for Path<'a> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.hash(state)
}
}
impl<'a, 'b> PartialEq<Str<'b>> for Path<'a> {
fn eq(&self, other: &Str<'b>) -> bool {
self.0 == *other
}
}
impl<'a, 'b> PartialEq<Path<'b>> for Str<'a> {
fn eq(&self, other: &Path<'b>) -> bool {
*self == other.0
}
}
impl<'a> PartialEq<str> for Path<'a> {
fn eq(&self, other: &str) -> bool {
self.0 == other
}
}
impl<'a> PartialEq<Path<'a>> for str {
fn eq(&self, other: &Path<'a>) -> bool {
self == other.0
}
}
impl<'a, 'b> PartialEq<&'b str> for Path<'a> {
fn eq(&self, other: &&'b str) -> bool {
self.0 == *other
}
}
impl<'a> PartialOrd for Path<'a> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.0.partial_cmp(&other.0)
}
}
impl<'a> Ord for Path<'a> {
fn cmp(&self, other: &Self) -> Ordering {
self.0.cmp(&other.0)
}
}
impl<'a> fmt::Debug for Path<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
impl<'a> fmt::Display for Path<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
#[cfg(feature = "sval")]
impl<'a> sval::Value for Path<'a> {
fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result {
use sval_ref::ValueRef as _;
self.0.stream_ref(stream)
}
}
#[cfg(feature = "sval")]
impl<'a> sval_ref::ValueRef<'a> for Path<'a> {
fn stream_ref<S: sval::Stream<'a> + ?Sized>(&self, stream: &mut S) -> sval::Result {
self.0.stream_ref(stream)
}
}
#[cfg(feature = "serde")]
impl<'a> serde::Serialize for Path<'a> {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.0.serialize(serializer)
}
}
#[cfg(feature = "alloc")]
mod alloc_support {
use super::*;
use alloc::{borrow::Cow, boxed::Box};
impl Path<'static> {
pub fn new_owned(path: impl Into<Box<str>>) -> Result<Self, InvalidPathError> {
Path::new_str(Str::new_owned(path))
}
pub fn new_owned_raw(path: impl Into<Box<str>>) -> Self {
Path::new_str_raw(Str::new_owned(path))
}
}
impl<'a> Path<'a> {
pub fn new_cow_ref(path: Cow<'a, str>) -> Result<Self, InvalidPathError> {
Path::new_str(Str::new_cow_ref(path))
}
pub fn new_cow_ref_raw(path: Cow<'a, str>) -> Self {
Path::new_str_raw(Str::new_cow_ref(path))
}
pub fn to_owned(&self) -> Path<'static> {
Path(self.0.to_owned())
}
pub fn to_cow(&self) -> Cow<'static, str> {
self.0.to_cow()
}
pub fn append<'b>(self, other: impl Into<Path<'b>>) -> Self {
let mut base = self.0.into_string();
base.push_str("::");
base.push_str(other.into().0.get());
Path::new_owned_raw(base)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn to_owned() {
let path = Path::new_ref_raw("module");
assert_eq!(path, path.to_owned());
}
#[test]
fn to_cow() {
for (path, expected) in [
(Path::new_raw("module"), Cow::Borrowed("module")),
(Path::new_ref_raw("module"), Cow::Owned("module".to_owned())),
] {
assert_eq!(expected, path.to_cow());
}
}
#[test]
fn append() {
for (a, b, expected) in [
("a", "b", "a::b"),
("a::b", "c", "a::b::c"),
("a", "b::c", "a::b::c"),
] {
assert_eq!(expected, Path::new_raw(a).append(Path::new_raw(&b)).0.get(),);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn by_ref() {
let path = Path::new_raw("module");
assert_eq!(path, path.by_ref());
}
#[test]
fn segments() {
for (case, segments, root, last_child) in [
("a", vec!["a"], "a", "a"),
("a::b", vec!["a", "b"], "a", "b"),
] {
let path = Path::new(case).unwrap();
assert_eq!(
segments,
path.segments()
.map(|segment| segment.get_static().unwrap())
.collect::<Vec<_>>()
);
assert_eq!(root, path.segments().next().unwrap().get_static().unwrap());
assert_eq!(
last_child,
path.segments().last().unwrap().get_static().unwrap()
);
}
}
#[test]
fn is_child_of() {
let a = Path::new("a").unwrap();
let aa = Path::new("aa").unwrap();
let b = Path::new("b").unwrap();
let a_b = Path::new("a::b").unwrap();
assert!(!aa.is_child_of(&a));
assert!(!b.is_child_of(&a));
assert!(!a.is_child_of(&a_b));
assert!(a.is_child_of(&a));
assert!(a_b.is_child_of(&a));
}
#[test]
fn is_valid() {
for (case, is_valid) in [
("a", true),
("a::b", true),
("", false),
("::", false),
("::a", false),
("a::", false),
("a:b", false),
("a::::b", false),
("a::{b, c}", false),
("a::*", false),
] {
assert_eq!(is_valid_path(case), is_valid);
}
}
#[test]
fn to_from_value() {
let path = Path::new_raw("module");
for value in [Value::from_any(&path), Value::from("module")] {
assert_eq!(path, value.cast::<Path>().unwrap());
}
}
}