1use std::{
2 any::Any,
3 borrow::Cow,
4 collections::{BTreeMap, HashMap},
5 fmt::{Debug, Display},
6 ops::{Deref, Range},
7 rc::Rc,
8 sync::Arc,
9};
10
11use itertools::Itertools;
12
13#[cfg(feature = "derive")]
14pub use ploidy_pointer_derive::JsonPointee;
15
16#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
18pub struct JsonPointer<'a>(Cow<'a, [JsonPointerSegment<'a>]>);
19
20impl JsonPointer<'static> {
21 pub fn parse_owned(s: &str) -> Result<Self, BadJsonPointerSyntax> {
24 if s.is_empty() {
25 return Ok(Self::empty());
26 }
27 let Some(s) = s.strip_prefix('/') else {
28 return Err(BadJsonPointerSyntax::MissingLeadingSlash);
29 };
30 let segments = s
31 .split('/')
32 .map(str::to_owned)
33 .map(JsonPointerSegment::from_str)
34 .collect_vec();
35 Ok(Self(segments.into()))
36 }
37}
38
39impl<'a> JsonPointer<'a> {
40 pub fn empty() -> Self {
42 Self(Cow::Borrowed(&[]))
43 }
44
45 pub fn parse(s: &'a str) -> Result<Self, BadJsonPointerSyntax> {
48 if s.is_empty() {
49 return Ok(Self::empty());
50 }
51 let Some(s) = s.strip_prefix('/') else {
52 return Err(BadJsonPointerSyntax::MissingLeadingSlash);
53 };
54 let segments = s.split('/').map(JsonPointerSegment::from_str).collect_vec();
55 Ok(Self(segments.into()))
56 }
57
58 pub fn is_empty(&self) -> bool {
60 self.0.is_empty()
61 }
62
63 pub fn head(&self) -> Option<&JsonPointerSegment<'a>> {
66 self.0.first()
67 }
68
69 pub fn tail(&self) -> JsonPointer<'_> {
73 self.0
74 .get(1..)
75 .map(|tail| JsonPointer(tail.into()))
76 .unwrap_or_else(JsonPointer::empty)
77 }
78
79 pub fn segments(&self) -> JsonPointerSegments<'_> {
81 JsonPointerSegments(self.0.iter())
82 }
83
84 pub fn into_segments(self) -> IntoJsonPointerSegments<'a> {
86 IntoJsonPointerSegments(self.0.into_owned().into_iter())
87 }
88}
89
90impl Display for JsonPointer<'_> {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 match &*self.0 {
93 [] => Ok(()),
94 segments => write!(f, "/{}", segments.iter().format("/")),
95 }
96 }
97}
98
99pub trait JsonPointee: Any {
101 fn resolve(&self, pointer: JsonPointer<'_>) -> Result<&dyn JsonPointee, BadJsonPointer>;
103}
104
105impl dyn JsonPointee {
106 #[inline]
109 pub fn downcast_ref<T: JsonPointee>(&self) -> Option<&T> {
110 (self as &dyn Any).downcast_ref::<T>()
111 }
112
113 #[inline]
115 pub fn is<T: JsonPointee>(&self) -> bool {
116 (self as &dyn Any).is::<T>()
117 }
118}
119
120#[derive(Clone, Debug)]
122pub struct JsonPointerSegments<'a>(std::slice::Iter<'a, JsonPointerSegment<'a>>);
123
124impl<'a> Iterator for JsonPointerSegments<'a> {
125 type Item = &'a JsonPointerSegment<'a>;
126
127 #[inline]
128 fn next(&mut self) -> Option<Self::Item> {
129 self.0.next()
130 }
131
132 #[inline]
133 fn size_hint(&self) -> (usize, Option<usize>) {
134 self.0.size_hint()
135 }
136
137 #[inline]
138 fn count(self) -> usize {
139 self.0.count()
140 }
141
142 #[inline]
143 fn last(mut self) -> Option<Self::Item> {
144 self.next_back()
145 }
146}
147
148impl ExactSizeIterator for JsonPointerSegments<'_> {}
149
150impl DoubleEndedIterator for JsonPointerSegments<'_> {
151 #[inline]
152 fn next_back(&mut self) -> Option<Self::Item> {
153 self.0.next_back()
154 }
155}
156
157#[derive(Debug)]
159pub struct IntoJsonPointerSegments<'a>(std::vec::IntoIter<JsonPointerSegment<'a>>);
160
161impl<'a> Iterator for IntoJsonPointerSegments<'a> {
162 type Item = JsonPointerSegment<'a>;
163
164 #[inline]
165 fn next(&mut self) -> Option<Self::Item> {
166 self.0.next()
167 }
168
169 #[inline]
170 fn size_hint(&self) -> (usize, Option<usize>) {
171 self.0.size_hint()
172 }
173
174 #[inline]
175 fn count(self) -> usize {
176 self.0.count()
177 }
178
179 #[inline]
180 fn last(mut self) -> Option<Self::Item> {
181 self.next_back()
182 }
183}
184
185impl ExactSizeIterator for IntoJsonPointerSegments<'_> {}
186
187impl DoubleEndedIterator for IntoJsonPointerSegments<'_> {
188 #[inline]
189 fn next_back(&mut self) -> Option<Self::Item> {
190 self.0.next_back()
191 }
192}
193
194#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
196pub struct JsonPointerSegment<'a>(Cow<'a, str>);
197
198impl<'a> JsonPointerSegment<'a> {
199 #[inline]
200 fn from_str(s: impl Into<Cow<'a, str>>) -> Self {
201 let s = s.into();
202 if s.contains('~') {
203 Self(s.replace("~1", "/").replace("~0", "~").into())
204 } else {
205 Self(s)
206 }
207 }
208
209 #[inline]
211 pub fn as_str(&self) -> &str {
212 self
213 }
214
215 #[inline]
218 pub fn to_index(&self) -> Option<usize> {
219 match self.as_bytes() {
220 [b'0'] => Some(0),
221 [b'1'..=b'9', rest @ ..] if rest.iter().all(|b: &u8| b.is_ascii_digit()) => {
222 self.parse().ok()
225 }
226 _ => None,
227 }
228 }
229}
230
231impl Deref for JsonPointerSegment<'_> {
232 type Target = str;
233
234 #[inline]
235 fn deref(&self) -> &Self::Target {
236 &self.0
237 }
238}
239
240impl Display for JsonPointerSegment<'_> {
241 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
242 write!(f, "{}", self.replace("~", "~0").replace("/", "~1"))
243 }
244}
245
246macro_rules! impl_pointee_for {
247 () => {};
248 (#[$($attrs:tt)+] $ty:ty $(, $($rest:tt)*)?) => {
249 #[$($attrs)*]
250 impl_pointee_for!($ty);
251 $(impl_pointee_for!($($rest)*);)?
252 };
253 ($ty:ty $(, $($rest:tt)*)?) => {
254 impl JsonPointee for $ty {
255 fn resolve(&self, pointer: JsonPointer<'_>) -> Result<&dyn JsonPointee, BadJsonPointer> {
256 if pointer.is_empty() {
257 Ok(self)
258 } else {
259 Err({
260 #[cfg(feature = "did-you-mean")]
261 let err = BadJsonPointerTy::with_ty(
262 &pointer,
263 JsonPointeeTy::Named(stringify!($ty)),
264 );
265 #[cfg(not(feature = "did-you-mean"))]
266 let err = BadJsonPointerTy::new(&pointer);
267 err
268 })?
269 }
270 }
271 }
272 $(impl_pointee_for!($($rest)*);)?
273 };
274}
275
276impl_pointee_for!(
277 i8, u8, i16, u16, i32, u32, i64, u64, i128, u128, isize, usize, f32, f64, bool, String, &'static str,
278 #[cfg(feature = "chrono")] chrono::DateTime<chrono::Utc>,
279 #[cfg(feature = "url")] url::Url,
280);
281
282impl<T: JsonPointee> JsonPointee for Option<T> {
283 fn resolve(&self, pointer: JsonPointer<'_>) -> Result<&dyn JsonPointee, BadJsonPointer> {
284 if let Some(value) = self {
285 value.resolve(pointer)
286 } else {
287 let Some(key) = pointer.head() else {
288 return Ok(&None::<T>);
289 };
290 Err({
291 #[cfg(feature = "did-you-mean")]
292 let err = BadJsonPointerKey::with_ty(key, JsonPointeeTy::name_of(self));
293 #[cfg(not(feature = "did-you-mean"))]
294 let err = BadJsonPointerKey::new(key);
295 err
296 })?
297 }
298 }
299}
300
301impl<T: JsonPointee> JsonPointee for Box<T> {
302 fn resolve(&self, pointer: JsonPointer<'_>) -> Result<&dyn JsonPointee, BadJsonPointer> {
303 (**self).resolve(pointer)
304 }
305}
306
307impl<T: JsonPointee> JsonPointee for Arc<T> {
308 fn resolve(&self, pointer: JsonPointer<'_>) -> Result<&dyn JsonPointee, BadJsonPointer> {
309 (**self).resolve(pointer)
310 }
311}
312
313impl<T: JsonPointee> JsonPointee for Rc<T> {
314 fn resolve(&self, pointer: JsonPointer<'_>) -> Result<&dyn JsonPointee, BadJsonPointer> {
315 (**self).resolve(pointer)
316 }
317}
318
319impl<T: JsonPointee> JsonPointee for Vec<T> {
320 fn resolve(&self, pointer: JsonPointer<'_>) -> Result<&dyn JsonPointee, BadJsonPointer> {
321 let Some(key) = pointer.head() else {
322 return Ok(self);
323 };
324 if let Some(index) = key.to_index() {
325 if let Some(item) = self.get(index) {
326 item.resolve(pointer.tail())
327 } else {
328 Err(BadJsonPointer::Index(index, 0..self.len()))
329 }
330 } else {
331 Err({
332 #[cfg(feature = "did-you-mean")]
333 let err =
334 BadJsonPointerTy::with_ty(&pointer, JsonPointeeTy::Named(stringify!($ty)));
335 #[cfg(not(feature = "did-you-mean"))]
336 let err = BadJsonPointerTy::new(&pointer);
337 err
338 })?
339 }
340 }
341}
342
343impl<T: JsonPointee> JsonPointee for HashMap<String, T> {
344 fn resolve(&self, pointer: JsonPointer<'_>) -> Result<&dyn JsonPointee, BadJsonPointer> {
345 let Some(key) = pointer.head() else {
346 return Ok(self);
347 };
348 if let Some(value) = self.get(key.as_str()) {
349 value.resolve(pointer.tail())
350 } else {
351 Err({
352 #[cfg(feature = "did-you-mean")]
353 let err = BadJsonPointerKey::with_suggestions(
354 key,
355 JsonPointeeTy::name_of(self),
356 self.keys().map(|key| key.as_str()),
357 );
358 #[cfg(not(feature = "did-you-mean"))]
359 let err = BadJsonPointerKey::new(key);
360 err
361 })?
362 }
363 }
364}
365
366impl<T: JsonPointee> JsonPointee for BTreeMap<String, T> {
367 fn resolve(&self, pointer: JsonPointer<'_>) -> Result<&dyn JsonPointee, BadJsonPointer> {
368 let Some(key) = pointer.head() else {
369 return Ok(self);
370 };
371 if let Some(value) = self.get(key.as_str()) {
372 value.resolve(pointer.tail())
373 } else {
374 Err({
375 #[cfg(feature = "did-you-mean")]
376 let err = BadJsonPointerKey::with_suggestions(
377 key,
378 JsonPointeeTy::name_of(self),
379 self.keys().map(|key| key.as_str()),
380 );
381 #[cfg(not(feature = "did-you-mean"))]
382 let err = BadJsonPointerKey::new(key);
383 err
384 })?
385 }
386 }
387}
388
389#[cfg(feature = "indexmap")]
390impl<T: JsonPointee> JsonPointee for indexmap::IndexMap<String, T> {
391 fn resolve(&self, pointer: JsonPointer<'_>) -> Result<&dyn JsonPointee, BadJsonPointer> {
392 let Some(key) = pointer.head() else {
393 return Ok(self);
394 };
395 if let Some(value) = self.get(key.as_str()) {
396 value.resolve(pointer.tail())
397 } else {
398 Err({
399 #[cfg(feature = "did-you-mean")]
400 let err = BadJsonPointerKey::with_suggestions(
401 key,
402 JsonPointeeTy::name_of(self),
403 self.keys().map(|key| key.as_str()),
404 );
405 #[cfg(not(feature = "did-you-mean"))]
406 let err = BadJsonPointerKey::new(key);
407 err
408 })?
409 }
410 }
411}
412
413#[cfg(feature = "serde_json")]
414impl JsonPointee for serde_json::Value {
415 fn resolve(&self, pointer: JsonPointer<'_>) -> Result<&dyn JsonPointee, BadJsonPointer> {
416 let Some(key) = pointer.head() else {
417 return Ok(self);
418 };
419 match self {
420 serde_json::Value::Object(map) => {
421 if let Some(value) = map.get(key.as_str()) {
422 value.resolve(pointer.tail())
423 } else {
424 Err({
425 #[cfg(feature = "did-you-mean")]
426 let err = BadJsonPointerKey::with_suggestions(
427 key,
428 JsonPointeeTy::name_of(map),
429 map.keys().map(|key| key.as_str()),
430 );
431 #[cfg(not(feature = "did-you-mean"))]
432 let err = BadJsonPointerKey::new(key);
433 err
434 })?
435 }
436 }
437 serde_json::Value::Array(array) => {
438 let Some(index) = key.to_index() else {
439 return Err({
440 #[cfg(feature = "did-you-mean")]
441 let err =
442 BadJsonPointerTy::with_ty(&pointer, JsonPointeeTy::name_of(array));
443 #[cfg(not(feature = "did-you-mean"))]
444 let err = BadJsonPointerTy::new(&pointer);
445 err
446 })?;
447 };
448 if let Some(item) = array.get(index) {
449 item.resolve(pointer.tail())
450 } else {
451 Err(BadJsonPointer::Index(index, 0..array.len()))
452 }
453 }
454 serde_json::Value::Null => Err({
455 #[cfg(feature = "did-you-mean")]
456 let err = BadJsonPointerKey::with_ty(key, JsonPointeeTy::name_of(self));
457 #[cfg(not(feature = "did-you-mean"))]
458 let err = BadJsonPointerKey::new(key);
459 err
460 })?,
461 _ => Err({
462 #[cfg(feature = "did-you-mean")]
463 let err = BadJsonPointerTy::with_ty(&pointer, JsonPointeeTy::name_of(self));
464 #[cfg(not(feature = "did-you-mean"))]
465 let err = BadJsonPointerTy::new(&pointer);
466 err
467 })?,
468 }
469 }
470}
471
472#[derive(Debug, thiserror::Error)]
474pub enum BadJsonPointerSyntax {
475 #[error("JSON Pointer must start with `/`")]
476 MissingLeadingSlash,
477}
478
479#[derive(Debug, thiserror::Error)]
481pub enum BadJsonPointer {
482 #[error(transparent)]
483 Key(#[from] BadJsonPointerKey),
484 #[error("index {} out of range {}..{}", .0, .1.start, .1.end)]
485 Index(usize, Range<usize>),
486 #[error(transparent)]
487 Ty(#[from] BadJsonPointerTy),
488}
489
490#[derive(Debug)]
494pub struct BadJsonPointerKey {
495 pub key: String,
496 pub context: Option<BadJsonPointerKeyContext>,
497}
498
499impl BadJsonPointerKey {
500 #[cold]
501 pub fn new(key: &JsonPointerSegment<'_>) -> Self {
502 Self {
503 key: key.to_string(),
504 context: None,
505 }
506 }
507
508 #[cfg(feature = "did-you-mean")]
509 #[cold]
510 pub fn with_ty(key: &JsonPointerSegment<'_>, ty: JsonPointeeTy) -> Self {
511 Self {
512 key: key.to_string(),
513 context: Some(BadJsonPointerKeyContext {
514 ty,
515 suggestion: None,
516 }),
517 }
518 }
519
520 #[cfg(feature = "did-you-mean")]
521 #[cold]
522 pub fn with_suggestions<'a>(
523 key: &'a JsonPointerSegment<'_>,
524 ty: JsonPointeeTy,
525 suggestions: impl IntoIterator<Item = &'a str>,
526 ) -> Self {
527 let suggestion = suggestions
528 .into_iter()
529 .map(|suggestion| (suggestion, strsim::jaro_winkler(key.as_str(), suggestion)))
530 .max_by(|&(_, a), &(_, b)| {
531 a.partial_cmp(&b).unwrap_or(std::cmp::Ordering::Equal)
534 })
535 .map(|(suggestion, _)| suggestion.to_owned());
536 Self {
537 key: key.to_string(),
538 context: Some(BadJsonPointerKeyContext { ty, suggestion }),
539 }
540 }
541}
542
543impl std::error::Error for BadJsonPointerKey {}
544
545impl Display for BadJsonPointerKey {
546 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
547 match &self.context {
548 Some(BadJsonPointerKeyContext {
549 ty,
550 suggestion: Some(suggestion),
551 }) => write!(
552 f,
553 "unknown key {:?} for value of {ty}; did you mean {suggestion:?}?",
554 self.key
555 ),
556 Some(BadJsonPointerKeyContext {
557 ty,
558 suggestion: None,
559 }) => write!(f, "unknown key {:?} for value of {ty}", self.key),
560 None => write!(f, "unknown key {:?}", self.key),
561 }
562 }
563}
564
565#[derive(Debug)]
566pub struct BadJsonPointerKeyContext {
567 pub ty: JsonPointeeTy,
568 pub suggestion: Option<String>,
569}
570
571#[derive(Debug)]
574pub struct BadJsonPointerTy {
575 pub pointer: String,
576 pub ty: Option<JsonPointeeTy>,
577}
578
579impl BadJsonPointerTy {
580 pub fn new(pointer: &JsonPointer<'_>) -> Self {
581 Self {
582 pointer: pointer.to_string(),
583 ty: None,
584 }
585 }
586
587 #[cfg(feature = "did-you-mean")]
588 #[cold]
589 pub fn with_ty(pointer: &JsonPointer<'_>, ty: JsonPointeeTy) -> Self {
590 Self {
591 pointer: pointer.to_string(),
592 ty: Some(ty),
593 }
594 }
595}
596
597impl std::error::Error for BadJsonPointerTy {}
598
599impl Display for BadJsonPointerTy {
600 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
601 match self.ty {
602 Some(ty) => write!(f, "can't resolve {:?} against value of {ty}", self.pointer),
603 None => write!(f, "can't resolve {:?}", self.pointer),
604 }
605 }
606}
607
608#[derive(Clone, Copy, Debug, Eq, PartialEq)]
610pub enum JsonPointeeTy {
611 Struct(JsonPointeeStructTy),
612 Variant(&'static str, JsonPointeeStructTy),
613 Named(&'static str),
614}
615
616impl JsonPointeeTy {
617 #[inline]
618 pub fn struct_named(ty: &'static str) -> Self {
619 Self::Struct(JsonPointeeStructTy::Named(ty))
620 }
621
622 #[inline]
623 pub fn tuple_struct_named(ty: &'static str) -> Self {
624 Self::Struct(JsonPointeeStructTy::Tuple(ty))
625 }
626
627 #[inline]
628 pub fn unit_struct_named(ty: &'static str) -> Self {
629 Self::Struct(JsonPointeeStructTy::Unit(ty))
630 }
631
632 #[inline]
633 pub fn struct_variant_named(ty: &'static str, variant: &'static str) -> Self {
634 Self::Variant(ty, JsonPointeeStructTy::Named(variant))
635 }
636
637 #[inline]
638 pub fn tuple_variant_named(ty: &'static str, variant: &'static str) -> Self {
639 Self::Variant(ty, JsonPointeeStructTy::Tuple(variant))
640 }
641
642 #[inline]
643 pub fn unit_variant_named(ty: &'static str, variant: &'static str) -> Self {
644 Self::Variant(ty, JsonPointeeStructTy::Unit(variant))
645 }
646
647 #[inline]
648 pub fn named<T: ?Sized>() -> Self {
649 Self::Named(std::any::type_name::<T>())
650 }
651
652 #[inline]
653 pub fn name_of<T: ?Sized>(value: &T) -> Self {
654 Self::Named(std::any::type_name_of_val(value))
655 }
656}
657
658impl Display for JsonPointeeTy {
659 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
660 match self {
661 Self::Struct(JsonPointeeStructTy::Named(ty)) => write!(f, "struct `{ty}`"),
662 Self::Struct(JsonPointeeStructTy::Tuple(ty)) => write!(f, "tuple struct `{ty}`"),
663 Self::Struct(JsonPointeeStructTy::Unit(ty)) => write!(f, "unit struct `{ty}`"),
664 Self::Variant(ty, JsonPointeeStructTy::Named(variant)) => {
665 write!(f, "variant `{variant}` of `{ty}`")
666 }
667 Self::Variant(ty, JsonPointeeStructTy::Tuple(variant)) => {
668 write!(f, "tuple variant `{variant}` of `{ty}`")
669 }
670 Self::Variant(ty, JsonPointeeStructTy::Unit(variant)) => {
671 write!(f, "unit variant `{variant}` of `{ty}`")
672 }
673 Self::Named(ty) => write!(f, "type `{ty}`"),
674 }
675 }
676}
677
678#[derive(Clone, Copy, Debug, Eq, PartialEq)]
681pub enum JsonPointeeStructTy {
682 Named(&'static str),
683 Tuple(&'static str),
684 Unit(&'static str),
685}
686
687#[cfg(test)]
688mod tests {
689 use super::*;
690
691 #[test]
692 fn test_parse_pointer() {
693 let pointer = JsonPointer::parse("/foo/bar/0").unwrap();
694 let mut segments = pointer.into_segments();
695 assert_eq!(segments.len(), 3);
696 assert_eq!(segments.next(), Some(JsonPointerSegment::from_str("foo")));
697 assert_eq!(segments.next(), Some(JsonPointerSegment::from_str("bar")));
698 assert_eq!(segments.next(), Some(JsonPointerSegment::from_str("0")));
701 assert_eq!(segments.next(), None);
702 }
703
704 #[test]
705 fn test_parse_pointer_escaping() {
706 let pointer = JsonPointer::parse("/foo~1bar/baz~0qux").unwrap();
707 let mut segments = pointer.into_segments();
708 assert_eq!(segments.len(), 2);
709 assert_eq!(
710 segments.next(),
711 Some(JsonPointerSegment::from_str("foo~1bar"))
712 );
713 assert_eq!(
714 segments.next(),
715 Some(JsonPointerSegment::from_str("baz~0qux"))
716 );
717 assert_eq!(segments.next(), None);
718 }
719
720 #[test]
721 fn test_resolve_vec() {
722 let data = vec![1, 2, 3];
723 let pointer = JsonPointer::parse("/1").unwrap();
724 let result = data.resolve(pointer).unwrap();
725 assert_eq!(result.downcast_ref::<i32>(), Some(&2));
726 }
727
728 #[test]
729 fn test_resolve_hashmap() {
730 let mut data = HashMap::new();
731 data.insert("foo".to_string(), 42);
732
733 let pointer = JsonPointer::parse("/foo").unwrap();
734 let result = data.resolve(pointer).unwrap();
735 assert_eq!(result.downcast_ref::<i32>(), Some(&42));
736 }
737
738 #[test]
739 fn test_resolve_option() {
740 let data = Some(42);
741 let pointer = JsonPointer::parse("").unwrap();
742 let result = data.resolve(pointer).unwrap();
743 assert_eq!(result.downcast_ref::<i32>(), Some(&42));
744 }
745
746 #[test]
747 fn test_primitive_empty_path() {
748 let data = 42;
749 let pointer = JsonPointer::parse("").unwrap();
750 let result = data.resolve(pointer).unwrap();
751 assert_eq!(result.downcast_ref::<i32>(), Some(&42));
752 }
753
754 #[test]
755 fn test_primitive_non_empty_path() {
756 let data = 42;
757 let pointer = JsonPointer::parse("/foo").unwrap();
758 assert!(data.resolve(pointer).is_err());
759 }
760
761 #[test]
762 fn test_segments() {
763 let pointer = JsonPointer::parse("/foo/bar/baz").unwrap();
764
765 let segments: Vec<_> = pointer.segments().map(|s| s.as_str()).collect();
767 assert_eq!(segments, vec!["foo", "bar", "baz"]);
768
769 let segments_again: Vec<_> = pointer.segments().map(|s| s.as_str()).collect();
771 assert_eq!(segments_again, vec!["foo", "bar", "baz"]);
772
773 assert_eq!(pointer.segments().len(), 3);
775 assert_eq!(pointer.segments().last().map(|s| s.as_str()), Some("baz"));
776 assert_eq!(
777 pointer.segments().next_back().map(|s| s.as_str()),
778 Some("baz")
779 );
780 }
781}