selective_disclosure_jwt/core/json_pointer/
path.rs1use 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 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 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}