1use std::borrow::Cow;
2use thiserror::Error as ThisError;
3
4pub struct Path<'a> {
6 pub segments: Vec<Cow<'a, str>>,
7}
8
9#[derive(ThisError, Debug)]
10#[error("invalid path")]
11pub struct InvalidPath;
12
13impl<'a> Path<'a> {
14 pub fn parse(s: &str) -> Result<Path<'_>, InvalidPath> {
15 parse(s)
16 }
17
18 pub fn to_owned(self) -> Path<'static> {
19 Path {
20 segments: self
21 .segments
22 .into_iter()
23 .map(|s: Cow<'a, str>| -> Cow<'static, str> {
24 Cow::Owned(match s {
25 Cow::Borrowed(b) => b.to_owned(),
26 Cow::Owned(o) => o,
27 })
28 })
29 .collect(),
30 }
31 }
32
33 pub fn iter(&self) -> std::slice::Iter<'_, Cow<str>> {
34 self.segments.iter()
35 }
36}
37
38fn parse(mut s: &str) -> Result<Path<'_>, InvalidPath> {
39 if s.is_empty() {
40 return Ok(Path {
41 segments: Vec::new(),
42 });
43 }
44
45 if s.starts_with('/') {
46 s = &s[1..];
47 }
48
49 let mut vec = Vec::new();
50
51 loop {
52 let end = s.find('/').unwrap_or(s.len());
53 let segment = &s[..end];
54 if segment.contains('~') {
55 let buf = segment.as_bytes();
57 if *buf
58 .last()
59 .expect("segment.contains('~') so it is not empty")
60 == b'~'
61 {
62 return Err(InvalidPath);
63 }
64 for w in buf.windows(2) {
65 if w[0] == b'~' && w[1] != b'0' && w[1] != b'1' {
66 return Err(InvalidPath);
67 }
68 }
69
70 let mut segment = segment.replace("~1", "/");
71 segment = segment.replace("~0", "~");
72 vec.push(Cow::Owned(segment));
73 } else {
74 vec.push(Cow::Borrowed(segment));
75 }
76
77 if end == s.len() {
78 break;
79 } else {
80 s = &s[(end + 1)..];
81 }
82 }
83
84 Ok(Path { segments: vec })
85}
86
87impl<'a> IntoIterator for Path<'a> {
88 type Item = <Vec<Cow<'a, str>> as IntoIterator>::Item;
89
90 type IntoIter = <Vec<Cow<'a, str>> as IntoIterator>::IntoIter;
91
92 fn into_iter(self) -> Self::IntoIter {
93 self.segments.into_iter()
94 }
95}