1use std::sync::Arc;
2
3#[derive(Debug, Clone, PartialEq, Eq, Hash)]
37pub struct Path(Vec<PathSegment>);
38
39#[derive(Debug, Clone, PartialEq, Eq, Hash)]
40pub enum PathSegment {
41 Field(Arc<str>),
42 Index(usize),
43}
44
45impl Path {
46 pub fn root() -> Self {
57 Self(Vec::new())
58 }
59
60 pub fn field(mut self, name: impl Into<Arc<str>>) -> Self {
74 self.0.push(PathSegment::Field(name.into()));
75 self
76 }
77
78 pub fn index(mut self, idx: usize) -> Self {
92 self.0.push(PathSegment::Index(idx));
93 self
94 }
95
96 pub fn parse(s: &str) -> Self {
114 let mut segments = Vec::new();
115 let mut current = String::new();
116
117 let chars: Vec<char> = s.chars().collect();
118 let mut i = 0;
119
120 while i < chars.len() {
121 match chars[i] {
122 '.' => {
123 if !current.is_empty() {
124 segments.push(PathSegment::Field(Arc::from(current.as_str())));
125 current.clear();
126 }
127 i += 1;
128 }
129 '[' => {
130 if !current.is_empty() {
131 segments.push(PathSegment::Field(Arc::from(current.as_str())));
132 current.clear();
133 }
134
135 i += 1;
136 let mut index_str = String::new();
137 while i < chars.len() && chars[i] != ']' {
138 index_str.push(chars[i]);
139 i += 1;
140 }
141
142 if let Ok(idx) = index_str.parse::<usize>() {
143 segments.push(PathSegment::Index(idx));
144 }
145
146 i += 1;
147 }
148 _ => {
149 current.push(chars[i]);
150 i += 1;
151 }
152 }
153 }
154
155 if !current.is_empty() {
156 segments.push(PathSegment::Field(Arc::from(current.as_str())));
157 }
158
159 Path(segments)
160 }
161
162 pub fn segments(&self) -> &[PathSegment] {
173 &self.0
174 }
175
176 pub fn push_field(&mut self, name: impl Into<Arc<str>>) {
188 self.0.push(PathSegment::Field(name.into()));
189 }
190
191 pub fn push_index(&mut self, idx: usize) {
204 self.0.push(PathSegment::Index(idx));
205 }
206}
207
208impl core::fmt::Display for Path {
209 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
210 for (i, segment) in self.0.iter().enumerate() {
211 match segment {
212 PathSegment::Field(name) => {
213 if i > 0 {
214 write!(f, ".")?;
215 }
216 write!(f, "{}", name)?;
217 }
218 PathSegment::Index(idx) => write!(f, "[{}]", idx)?,
219 }
220 }
221 Ok(())
222 }
223}
224
225impl From<&'static str> for Path {
226 fn from(s: &'static str) -> Self {
227 Path(vec![PathSegment::Field(Arc::from(s))])
228 }
229}
230
231impl From<String> for Path {
232 fn from(s: String) -> Self {
233 Path::parse(&s)
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 #[test]
242 fn test_root() {
243 let path = Path::root();
244 assert!(path.segments().is_empty());
245 assert_eq!(path.to_string(), "");
246 }
247
248 #[test]
249 fn test_field() {
250 let path = Path::root().field("email");
251 assert_eq!(path.segments().len(), 1);
252 assert_eq!(path.to_string(), "email");
253 }
254
255 #[test]
256 fn test_nested_field() {
257 let path = Path::root().field("guest").field("email");
258 assert_eq!(path.segments().len(), 2);
259 assert_eq!(path.to_string(), "guest.email");
260 }
261
262 #[test]
263 fn test_index() {
264 let path = Path::root().field("guests").index(0);
265 assert_eq!(path.segments().len(), 2);
266 assert_eq!(path.to_string(), "guests[0]");
267 }
268
269 #[test]
270 fn test_complex_path() {
271 let path = Path::root()
272 .field("booking")
273 .field("guests")
274 .index(0)
275 .field("email");
276 assert_eq!(path.to_string(), "booking.guests[0].email");
277 }
278
279 #[test]
280 fn test_from_str() {
281 let path = Path::from("email");
282 assert_eq!(path.segments().len(), 1);
283 assert_eq!(path.to_string(), "email");
284 }
285
286 #[test]
287 fn test_parse_simple() {
288 let path = Path::parse("email");
289 assert_eq!(path.to_string(), "email");
290 }
291
292 #[test]
293 fn test_parse_nested() {
294 let path = Path::parse("guest.email");
295 assert_eq!(path.to_string(), "guest.email");
296 }
297
298 #[test]
299 fn test_parse_with_index() {
300 let path = Path::parse("guests[0].email");
301 assert_eq!(path.to_string(), "guests[0].email");
302 }
303
304 #[test]
305 fn test_parse_complex() {
306 let path = Path::parse("booking.guests[0].email");
307 assert_eq!(path.to_string(), "booking.guests[0].email");
308 }
309
310 #[test]
312 fn test_push_field_basic() {
313 let mut path = Path::root();
314 path.push_field("email");
315 assert_eq!(path.segments().len(), 1);
316 assert_eq!(path.to_string(), "email");
317 }
318
319 #[test]
320 fn test_push_field_multiple() {
321 let mut path = Path::root();
322 path.push_field("user");
323 path.push_field("profile");
324 path.push_field("email");
325 assert_eq!(path.segments().len(), 3);
326 assert_eq!(path.to_string(), "user.profile.email");
327 }
328
329 #[test]
330 fn test_push_index_basic() {
331 let mut path = Path::root();
332 path.push_field("items");
333 path.push_index(0);
334 assert_eq!(path.segments().len(), 2);
335 assert_eq!(path.to_string(), "items[0]");
336 }
337
338 #[test]
339 fn test_push_index_multiple() {
340 let mut path = Path::root();
341 path.push_field("matrix");
342 path.push_index(0);
343 path.push_index(1);
344 assert_eq!(path.segments().len(), 3);
345 assert_eq!(path.to_string(), "matrix[0][1]");
346 }
347
348 #[test]
349 fn test_push_field_and_index_mixed() {
350 let mut path = Path::root();
351 path.push_field("orders");
352 path.push_index(5);
353 path.push_field("items");
354 path.push_index(3);
355 path.push_field("sku");
356 assert_eq!(path.segments().len(), 5);
357 assert_eq!(path.to_string(), "orders[5].items[3].sku");
358 }
359
360 #[test]
361 fn test_push_with_string() {
362 let mut path = Path::root();
363 path.push_field(String::from("dynamic_field"));
364 assert_eq!(path.to_string(), "dynamic_field");
365 }
366
367 #[test]
369 fn test_parse_empty_string() {
370 let path = Path::parse("");
371 assert!(path.segments().is_empty());
372 assert_eq!(path.to_string(), "");
373 }
374
375 #[test]
376 fn test_parse_leading_dot() {
377 let path = Path::parse(".field");
378 assert_eq!(path.to_string(), "field");
380 }
381
382 #[test]
383 fn test_parse_trailing_dot() {
384 let path = Path::parse("field.");
385 assert_eq!(path.to_string(), "field");
387 }
388
389 #[test]
390 fn test_parse_consecutive_dots() {
391 let path = Path::parse("a..b");
392 assert_eq!(path.to_string(), "a.b");
394 }
395
396 #[test]
397 fn test_parse_consecutive_indices() {
398 let path = Path::parse("items[0][1][2]");
399 assert_eq!(path.segments().len(), 4);
400 assert_eq!(path.to_string(), "items[0][1][2]");
401 }
402
403 #[test]
404 fn test_parse_invalid_index_ignored() {
405 let path = Path::parse("items[abc]");
406 assert_eq!(path.segments().len(), 1);
408 assert_eq!(path.to_string(), "items");
409 }
410
411 #[test]
412 fn test_parse_negative_index_ignored() {
413 let path = Path::parse("items[-1]");
414 assert_eq!(path.segments().len(), 1);
416 assert_eq!(path.to_string(), "items");
417 }
418
419 #[test]
420 fn test_parse_unclosed_bracket() {
421 let path = Path::parse("items[0");
422 assert_eq!(path.segments().len(), 2);
424 assert_eq!(path.to_string(), "items[0]");
425 }
426
427 #[test]
428 fn test_parse_deep_nesting() {
429 let path = Path::parse("a.b.c.d.e.f.g.h.i.j.k");
430 assert_eq!(path.segments().len(), 11);
431 assert_eq!(path.to_string(), "a.b.c.d.e.f.g.h.i.j.k");
432 }
433
434 #[test]
435 fn test_parse_deep_mixed_nesting() {
436 let path = Path::parse("a[0].b[1].c[2].d[3].e[4]");
437 assert_eq!(path.segments().len(), 10);
438 assert_eq!(path.to_string(), "a[0].b[1].c[2].d[3].e[4]");
439 }
440
441 #[test]
442 fn test_parse_large_index() {
443 let path = Path::parse("items[999999]");
444 assert_eq!(path.to_string(), "items[999999]");
445 }
446
447 #[test]
449 fn test_parsed_equals_constructed() {
450 let parsed = Path::parse("user.items[0].name");
451 let constructed = Path::root()
452 .field("user")
453 .field("items")
454 .index(0)
455 .field("name");
456 assert_eq!(parsed, constructed);
457 }
458
459 #[test]
460 fn test_path_hash_consistency() {
461 use std::collections::HashMap;
462
463 let path1 = Path::parse("user.email");
464 let path2 = Path::root().field("user").field("email");
465
466 let mut map = HashMap::new();
467 map.insert(path1.clone(), "value1");
468
469 assert_eq!(map.get(&path2), Some(&"value1"));
471 }
472
473 #[test]
474 fn test_clone_independence() {
475 let original = Path::root().field("test");
476 let mut cloned = original.clone();
477 cloned.push_field("extra");
478
479 assert_eq!(original.segments().len(), 1);
480 assert_eq!(cloned.segments().len(), 2);
481 assert_eq!(original.to_string(), "test");
482 assert_eq!(cloned.to_string(), "test.extra");
483 }
484
485 #[test]
486 fn test_from_string() {
487 let path: Path = String::from("user.email").into();
488 assert_eq!(path.to_string(), "user.email");
489 }
490
491 #[test]
492 fn test_segment_types() {
493 let path = Path::root().field("items").index(0).field("name");
494 let segments = path.segments();
495
496 assert!(matches!(&segments[0], PathSegment::Field(_)));
497 assert!(matches!(&segments[1], PathSegment::Index(0)));
498 assert!(matches!(&segments[2], PathSegment::Field(_)));
499 }
500}