selective_disclosure_jwt/core/json_pointer/
path.rs

1use crate::error::SdjError;
2use std::borrow::Cow;
3
4#[derive(Debug, Clone, derive_more::AsRef, derive_more::Deref)]
5pub(crate) struct JsonPointerPath<'a>(Cow<'a, str>);
6
7impl<'a> JsonPointerPath<'a> {
8    const DELIMITER: char = '/';
9
10    pub fn object_key(&self) -> Option<&str> {
11        let (_, last) = self.rsplit_once(Self::DELIMITER)?;
12
13        use std::str::FromStr as _;
14        // meaning it's an array index
15        if usize::from_str(last).is_ok() {
16            None
17        } else {
18            Some(last)
19        }
20    }
21
22    pub fn is_key(&self) -> bool {
23        self.object_key().is_some()
24    }
25
26    pub fn parent(&'a self) -> Option<Self> {
27        if let Some((parent, _)) = self.rsplit_once(Self::DELIMITER) {
28            Some(Self(Cow::Borrowed(parent)))
29        } else {
30            None
31        }
32    }
33
34    pub fn append(&mut self, path: &str) {
35        self.0 = Cow::Owned(format!("{}{}{path}", self.0, Self::DELIMITER))
36    }
37
38    // FIXME: both validations are wrong according to the RFC
39    fn is_valid(&self) -> bool {
40        !self.is_empty() && self.starts_with(Self::DELIMITER)
41    }
42}
43
44impl<'a> TryFrom<&'a str> for JsonPointerPath<'a> {
45    type Error = SdjError;
46
47    fn try_from(path: &'a str) -> Result<Self, Self::Error> {
48        let jpp = Self(Cow::Borrowed(path));
49        if !jpp.is_valid() {
50            return Err(SdjError::InvalidJsonPointerPath(path.to_string()));
51        }
52        Ok(jpp)
53    }
54}
55
56#[cfg(test)]
57pub mod tests {
58
59    use super::*;
60
61    mod object_key {
62        use super::*;
63
64        #[test]
65        fn should_find_object_key() {
66            assert_eq!(JsonPointerPath::try_from("/a/b/c").unwrap().object_key(), Some("c"));
67            assert_eq!(JsonPointerPath::try_from("/a").unwrap().object_key(), Some("a"));
68            assert_eq!(JsonPointerPath::try_from("/a/0/b").unwrap().object_key(), Some("b"));
69        }
70
71        #[test]
72        fn should_not_find_object_key_for_array_items() {
73            assert!(JsonPointerPath::try_from("/a/0").unwrap().object_key().is_none());
74        }
75    }
76
77    mod validity {
78        use super::*;
79
80        #[test]
81        fn should_be_invalid_when_empty() {
82            assert!(matches!(
83                JsonPointerPath::try_from("").unwrap_err(),
84                SdjError::InvalidJsonPointerPath(p) if p.is_empty()
85            ));
86        }
87
88        #[test]
89        fn should_be_invalid_when_does_not_start_with_slash() {
90            assert!(JsonPointerPath::try_from("/a").is_ok());
91            assert!(matches!(
92                JsonPointerPath::try_from("a").unwrap_err(),
93                SdjError::InvalidJsonPointerPath(p) if p == "a"
94            ));
95            assert!(matches!(
96                JsonPointerPath::try_from("!a").unwrap_err(),
97                SdjError::InvalidJsonPointerPath(p) if p == "!a"
98            ));
99        }
100    }
101}