flow_value/crud/
path.rs

1use std::borrow::Cow;
2use thiserror::Error as ThisError;
3
4/// [JSON Pointer](https://www.rfc-editor.org/rfc/rfc6901)
5pub 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            // validate escape characters
56            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}