skp_validator_core/
path.rs1use std::fmt;
7
8#[cfg(feature = "serde")]
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
19pub enum PathSegment {
20 Field(String),
22 Index(usize),
24 Key(String),
26}
27
28impl PathSegment {
29 pub fn field(name: impl Into<String>) -> Self {
31 Self::Field(name.into())
32 }
33
34 pub fn index(idx: usize) -> Self {
36 Self::Index(idx)
37 }
38
39 pub fn key(key: impl Into<String>) -> Self {
41 Self::Key(key.into())
42 }
43}
44
45impl fmt::Display for PathSegment {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 match self {
48 Self::Field(name) => write!(f, "{}", name),
49 Self::Index(idx) => write!(f, "[{}]", idx),
50 Self::Key(key) => write!(f, "[\"{}\"]", key),
51 }
52 }
53}
54
55#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
72#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
73pub struct FieldPath {
74 segments: Vec<PathSegment>,
75}
76
77impl FieldPath {
78 pub fn new() -> Self {
80 Self {
81 segments: Vec::new(),
82 }
83 }
84
85 pub fn from_field(name: impl Into<String>) -> Self {
87 Self {
88 segments: vec![PathSegment::Field(name.into())],
89 }
90 }
91
92 pub fn is_empty(&self) -> bool {
94 self.segments.is_empty()
95 }
96
97 pub fn len(&self) -> usize {
99 self.segments.len()
100 }
101
102 pub fn push_field(mut self, name: impl Into<String>) -> Self {
104 self.segments.push(PathSegment::Field(name.into()));
105 self
106 }
107
108 pub fn push_index(mut self, idx: usize) -> Self {
110 self.segments.push(PathSegment::Index(idx));
111 self
112 }
113
114 pub fn push_key(mut self, key: impl Into<String>) -> Self {
116 self.segments.push(PathSegment::Key(key.into()));
117 self
118 }
119
120 pub fn append_field(&mut self, name: impl Into<String>) {
122 self.segments.push(PathSegment::Field(name.into()));
123 }
124
125 pub fn append_index(&mut self, idx: usize) {
127 self.segments.push(PathSegment::Index(idx));
128 }
129
130 pub fn append_key(&mut self, key: impl Into<String>) {
132 self.segments.push(PathSegment::Key(key.into()));
133 }
134
135 pub fn segments(&self) -> &[PathSegment] {
137 &self.segments
138 }
139
140 pub fn parent(&self) -> Option<Self> {
142 if self.segments.is_empty() {
143 None
144 } else {
145 let mut parent = self.clone();
146 parent.segments.pop();
147 Some(parent)
148 }
149 }
150
151 pub fn last(&self) -> Option<&PathSegment> {
153 self.segments.last()
154 }
155
156 pub fn last_field_name(&self) -> Option<&str> {
158 match self.last() {
159 Some(PathSegment::Field(name)) => Some(name),
160 _ => None,
161 }
162 }
163
164 pub fn child_field(&self, name: impl Into<String>) -> Self {
166 self.clone().push_field(name)
167 }
168
169 pub fn child_index(&self, idx: usize) -> Self {
171 self.clone().push_index(idx)
172 }
173
174 pub fn child_key(&self, key: impl Into<String>) -> Self {
176 self.clone().push_key(key)
177 }
178
179 pub fn to_dot_notation(&self) -> String {
181 self.to_string()
182 }
183
184 pub fn to_json_pointer(&self) -> String {
186 if self.segments.is_empty() {
187 return String::new();
188 }
189
190 let mut pointer = String::new();
191 for segment in &self.segments {
192 pointer.push('/');
193 match segment {
194 PathSegment::Field(name) => {
195 let escaped = name.replace('~', "~0").replace('/', "~1");
197 pointer.push_str(&escaped);
198 }
199 PathSegment::Index(idx) => {
200 pointer.push_str(&idx.to_string());
201 }
202 PathSegment::Key(key) => {
203 let escaped = key.replace('~', "~0").replace('/', "~1");
204 pointer.push_str(&escaped);
205 }
206 }
207 }
208 pointer
209 }
210}
211
212impl fmt::Display for FieldPath {
213 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214 for (i, segment) in self.segments.iter().enumerate() {
215 match segment {
216 PathSegment::Field(name) => {
217 if i > 0 {
218 write!(f, ".")?;
220 }
221 write!(f, "{}", name)?;
222 }
223 PathSegment::Index(idx) => {
224 write!(f, "[{}]", idx)?;
225 }
226 PathSegment::Key(key) => {
227 write!(f, "[\"{}\"]", key)?;
228 }
229 }
230 }
231 Ok(())
232 }
233}
234
235impl From<&str> for FieldPath {
236 fn from(s: &str) -> Self {
237 Self::from_field(s)
238 }
239}
240
241impl From<String> for FieldPath {
242 fn from(s: String) -> Self {
243 Self::from_field(s)
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 #[test]
252 fn test_simple_path() {
253 let path = FieldPath::from_field("email");
254 assert_eq!(path.to_string(), "email");
255 }
256
257 #[test]
258 fn test_nested_path() {
259 let path = FieldPath::new()
260 .push_field("user")
261 .push_field("address")
262 .push_field("city");
263 assert_eq!(path.to_string(), "user.address.city");
264 }
265
266 #[test]
267 fn test_array_path() {
268 let path = FieldPath::new()
269 .push_field("items")
270 .push_index(0)
271 .push_field("name");
272 assert_eq!(path.to_string(), "items[0].name");
273 }
274
275 #[test]
276 fn test_map_path() {
277 let path = FieldPath::new()
278 .push_field("metadata")
279 .push_key("custom")
280 .push_field("value");
281 assert_eq!(path.to_string(), "metadata[\"custom\"].value");
282 }
283
284 #[test]
285 fn test_json_pointer() {
286 let path = FieldPath::new()
287 .push_field("user")
288 .push_field("addresses")
289 .push_index(0)
290 .push_field("city");
291 assert_eq!(path.to_json_pointer(), "/user/addresses/0/city");
292 }
293
294 #[test]
295 fn test_parent() {
296 let path = FieldPath::new().push_field("user").push_field("email");
297 let parent = path.parent().unwrap();
298 assert_eq!(parent.to_string(), "user");
299 }
300}