use std::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Segment<'a> {
Key(&'a str),
Index(usize),
}
#[derive(Clone, Copy, Debug)]
pub struct Path<'a> {
parent: Option<&'a Path<'a>>,
segment: Option<Segment<'a>>,
}
impl Path<'static> {
pub const ROOT: Path<'static> = Path {
parent: None,
segment: None,
};
}
impl<'a> Path<'a> {
pub fn key<'b>(&'b self, key: &'b str) -> Path<'b> {
Path {
parent: Some(self),
segment: Some(Segment::Key(key)),
}
}
pub fn index(&self, index: usize) -> Path<'_> {
Path {
parent: Some(self),
segment: Some(Segment::Index(index)),
}
}
pub fn is_root(&self) -> bool {
self.segment.is_none()
}
pub fn to_pointer(&self) -> Pointer {
let mut segments = Vec::new();
let mut at = Some(self);
while let Some(path) = at {
match path.segment {
Some(Segment::Key(key)) => segments.push(key.to_owned()),
Some(Segment::Index(index)) => segments.push(index.to_string()),
None => {}
}
at = path.parent;
}
segments.reverse();
Pointer { segments }
}
}
impl fmt::Display for Path<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.to_pointer().fmt(f)
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Pointer {
segments: Vec<String>,
}
impl Pointer {
pub fn root() -> Self {
Self::default()
}
pub fn segments(&self) -> &[String] {
&self.segments
}
pub fn is_root(&self) -> bool {
self.segments.is_empty()
}
pub fn join(mut self, segment: impl Into<String>) -> Self {
self.segments.push(segment.into());
self
}
pub fn prefixed(&self, prefix: &Path<'_>) -> Pointer {
let mut pointer = prefix.to_pointer();
pointer.segments.extend(self.segments.iter().cloned());
pointer
}
pub fn parse(text: &str) -> Option<Self> {
if text.is_empty() {
return Some(Self::root());
}
let rest = text.strip_prefix('/')?;
let mut segments = Vec::new();
for raw in rest.split('/') {
let mut segment = String::with_capacity(raw.len());
let mut chars = raw.chars();
while let Some(c) = chars.next() {
if c == '~' {
match chars.next() {
Some('0') => segment.push('~'),
Some('1') => segment.push('/'),
_ => return None,
}
} else {
segment.push(c);
}
}
segments.push(segment);
}
Some(Self { segments })
}
}
impl<S: Into<String>> FromIterator<S> for Pointer {
fn from_iter<T: IntoIterator<Item = S>>(iter: T) -> Self {
Self {
segments: iter.into_iter().map(Into::into).collect(),
}
}
}
impl fmt::Display for Pointer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for segment in &self.segments {
f.write_str("/")?;
for c in segment.chars() {
match c {
'~' => f.write_str("~0")?,
'/' => f.write_str("~1")?,
c => fmt::Write::write_char(f, c)?,
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn root_is_the_empty_pointer() {
assert_eq!(Path::ROOT.to_pointer().to_string(), "");
assert!(Path::ROOT.is_root());
}
#[test]
fn keys_and_indices_are_written_in_order() {
let items = Path::ROOT.key("items");
let first = items.index(0);
let name = first.key("name");
assert_eq!(name.to_string(), "/items/0/name");
}
#[test]
fn tilde_and_slash_are_escaped() {
let path = Path::ROOT.key("a/b~c");
assert_eq!(path.to_string(), "/a~1b~0c");
}
#[test]
fn an_empty_key_is_one_segment() {
let path = Path::ROOT.key("");
assert_eq!(path.to_string(), "/");
assert_eq!(path.to_pointer().segments(), [""]);
}
#[test]
fn prefixed_puts_the_prefix_first() {
let user = Path::ROOT.key("user");
let relative: Pointer = ["id"].into_iter().collect();
assert_eq!(relative.prefixed(&user).to_string(), "/user/id");
}
proptest! {
#[test]
fn a_written_pointer_reads_back_to_its_segments(segments in prop::collection::vec(".*", 0..5)) {
let pointer: Pointer = segments.iter().cloned().collect();
let read = Pointer::parse(&pointer.to_string()).unwrap();
prop_assert_eq!(read.segments(), &segments[..]);
}
}
}