Skip to main content

bh_sd_jwt/models/
path.rs

1// Copyright (C) 2020-2026  The Blockhouse Technology Limited (TBTL).
2//
3// This program is free software: you can redistribute it and/or modify it
4// under the terms of the GNU Affero General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or (at your
6// option) any later version.
7//
8// This program is distributed in the hope that it will be useful, but
9// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10// or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
11// License for more details.
12//
13// You should have received a copy of the GNU Affero General Public License
14// along with this program.  If not, see <https://www.gnu.org/licenses/>.
15
16use crate::{JsonObject, Value};
17
18/// Type of JSON node paths, represented as a list of segments to follow
19/// starting from the root of the JWT.
20///
21/// Not to be confused with the JSONPath query syntax.
22pub type JsonNodePath<'a> = [JsonNodePathSegment<'a>];
23
24/// A path segment, either an object key or an array index.
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub enum JsonNodePathSegment<'a> {
27    /// Object key path segment.
28    Key(&'a str),
29    /// Array index path segment.
30    Index(u32),
31}
32
33impl<'a> From<&'a str> for JsonNodePathSegment<'a> {
34    fn from(key: &'a str) -> Self {
35        Self::Key(key)
36    }
37}
38
39impl From<u32> for JsonNodePathSegment<'_> {
40    fn from(index: u32) -> Self {
41        Self::Index(index)
42    }
43}
44
45impl std::fmt::Display for JsonNodePathSegment<'_> {
46    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
47        match self {
48            JsonNodePathSegment::Key(key) => write!(f, "Key: {}", key),
49            JsonNodePathSegment::Index(ind) => write!(f, "Ind: {}", ind),
50        }
51    }
52}
53
54/// Utility macro for writing path literals more ergonomically.
55///
56/// Every element is converted into the [`JsonNodePathSegment`] type via [`From`], which lets the
57/// syntax use heterogeneous expressions.
58///
59/// ```
60/// let path = bh_sd_jwt::path!["address", "region", "country"];
61/// ```
62///
63/// ```
64/// let path = bh_sd_jwt::path!["array", 2];
65/// ```
66#[macro_export]
67macro_rules! path {
68    [ $( $segment:expr ),* ] => {
69        &[ $( $crate::JsonNodePathSegment::from($segment) ),* ]
70    };
71}
72
73// TODO(issues/56) how to expose this nicely? is the rendered syntax even correct?
74/// Wrapper struct implementing [`std::fmt::Display`] for [`JsonNodePath`].
75pub struct DisplayWrapper<'a, T: ?Sized>(pub &'a T);
76
77impl std::fmt::Display for DisplayWrapper<'_, JsonNodePath<'_>> {
78    /// Note: several tests depend on the injectivity of this implementation
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        write!(f, "$")?;
81        for segment in self.0 {
82            match segment {
83                // Use `.{}` to conform to third-party expectations.
84                // Note: this approach does not support keys that contain dots.
85                JsonNodePathSegment::Key(key) => write!(f, ".{}", key)?,
86                JsonNodePathSegment::Index(index) => write!(f, "[{}]", index)?,
87            }
88        }
89        Ok(())
90    }
91}
92
93fn index_by_path<'v>(mut value: &'v Value, path: &JsonNodePath) -> Option<&'v Value> {
94    for segment in path {
95        match (value, segment) {
96            (Value::Array(array), JsonNodePathSegment::Index(index)) => {
97                value = array.get(*index as usize)?;
98            }
99            (Value::Object(object), JsonNodePathSegment::Key(key)) => {
100                value = object.get(*key)?;
101            }
102            _ => return None,
103        }
104    }
105    Some(value)
106}
107
108/// For empty paths, `None` is returned, as `&JsonObject` cannot be converted to `&Value`.
109fn index_object_by_path<'o>(object: &'o JsonObject, path: &JsonNodePath) -> Option<&'o Value> {
110    let (head, tail) = path.split_first()?;
111    match head {
112        JsonNodePathSegment::Key(key) => index_by_path(object.get(*key)?, tail),
113        _ => None,
114    }
115}
116
117/// For empty paths, `None` is returned, because it would need to return provided `object`
118/// argument wrapped as [Value] which takes ownership
119pub(crate) fn index_mut_object_by_path<'a>(
120    object: &'a mut JsonObject,
121    path: &JsonNodePath,
122) -> Option<&'a mut Value> {
123    let (head, tail) = path.split_first()?;
124    match head {
125        JsonNodePathSegment::Key(key) => index_mut_by_path(object.get_mut(*key)?, tail),
126        _ => None,
127    }
128}
129
130fn index_mut_by_path<'a>(mut value: &'a mut Value, path: &JsonNodePath) -> Option<&'a mut Value> {
131    for segment in path {
132        match (value, segment) {
133            (Value::Array(array), JsonNodePathSegment::Index(index)) => {
134                value = array.get_mut(*index as usize)?;
135            }
136            (Value::Object(object), JsonNodePathSegment::Key(key)) => {
137                value = object.get_mut(*key)?;
138            }
139            _ => return None,
140        }
141    }
142    Some(value)
143}
144
145pub(crate) fn paths_exist<'a, 'p>(
146    value: &'a JsonObject,
147    paths: &[&'p JsonNodePath<'a>],
148) -> Result<(), Vec<&'p JsonNodePath<'a>>> {
149    let mut nonexistent_paths = vec![];
150
151    for path in paths {
152        if !path.is_empty() && index_object_by_path(value, path).is_none() {
153            nonexistent_paths.push(*path);
154        }
155    }
156
157    if nonexistent_paths.is_empty() {
158        Ok(())
159    } else {
160        Err(nonexistent_paths)
161    }
162}
163
164impl<'a> From<&serde_json_path::PathElement<'a>> for JsonNodePathSegment<'a> {
165    fn from(value: &serde_json_path::PathElement<'a>) -> Self {
166        match *value {
167            serde_json_path::PathElement::Index(index) => Self::Index(index as u32),
168            serde_json_path::PathElement::Name(name) => Self::Key(name),
169        }
170    }
171}