1use std::borrow::Cow;
4use std::fmt;
5use std::hash::{Hash, Hasher};
6use std::ops::{Index, IndexMut};
7use std::str::FromStr;
8
9use crate::{AccessError, AccessErrorKind, Position};
10
11const BASE64: &str = "#base64:";
12
13pub struct Node {
22 value: Value,
23 position: Option<Position>,
24}
25
26#[derive(Clone, PartialEq, Eq, Hash)]
28pub enum Value {
29 List(Vec<Node>),
31 Str(String),
33 Atom(String),
36}
37
38impl Node {
39 pub(crate) fn parsed(value: Value, position: Position) -> Self {
40 Self {
41 value,
42 position: Some(position),
43 }
44 }
45
46 pub fn list(items: impl IntoIterator<Item = Node>) -> Self {
48 Value::List(items.into_iter().collect()).into()
49 }
50
51 pub fn atom(value: impl Into<String>) -> Self {
53 Value::Atom(value.into()).into()
54 }
55
56 pub fn string(value: impl Into<String>) -> Self {
60 Value::Str(value.into()).into()
61 }
62
63 pub fn counted(items: impl IntoIterator<Item = Node>) -> Self {
65 let items: Vec<Node> = items.into_iter().collect();
66 Node::list(std::iter::once(Node::from(items.len())).chain(items))
67 }
68
69 pub fn value(&self) -> &Value {
70 &self.value
71 }
72
73 pub fn into_value(mut self) -> Value {
74 self.take_value()
75 }
76
77 pub fn value_mut(&mut self) -> &mut Value {
79 &mut self.value
80 }
81
82 pub fn position(&self) -> Option<Position> {
86 self.position
87 }
88
89 pub fn is_list(&self) -> bool {
90 matches!(self.value, Value::List(_))
91 }
92
93 pub fn is_str(&self) -> bool {
94 matches!(self.value, Value::Str(_))
95 }
96
97 pub fn is_atom(&self) -> bool {
98 matches!(self.value, Value::Atom(_))
99 }
100
101 pub fn is_empty_atom(&self) -> bool {
103 matches!(&self.value, Value::Atom(s) if s.is_empty())
104 }
105
106 pub fn as_list(&self) -> Option<&[Node]> {
107 match &self.value {
108 Value::List(items) => Some(items),
109 _ => None,
110 }
111 }
112
113 pub fn as_str(&self) -> Option<&str> {
115 match &self.value {
116 Value::Str(s) | Value::Atom(s) => Some(s),
117 Value::List(_) => None,
118 }
119 }
120
121 pub fn as_quoted(&self) -> Option<&str> {
123 match &self.value {
124 Value::Str(s) => Some(s),
125 _ => None,
126 }
127 }
128
129 pub fn as_atom(&self) -> Option<&str> {
131 match &self.value {
132 Value::Atom(s) => Some(s),
133 _ => None,
134 }
135 }
136
137 pub fn as_parsed<T: FromStr>(&self) -> Option<T> {
139 self.as_atom()?.parse().ok()
140 }
141
142 pub fn as_i64(&self) -> Option<i64> {
143 self.as_parsed()
144 }
145
146 pub fn as_u64(&self) -> Option<u64> {
147 self.as_parsed()
148 }
149
150 pub fn as_f64(&self) -> Option<f64> {
151 self.as_parsed()
152 }
153
154 pub fn as_bool(&self) -> Option<bool> {
156 match self.as_atom()? {
157 "1" => Some(true),
158 "0" => Some(false),
159 _ => None,
160 }
161 }
162
163 pub fn as_guid(&self) -> Option<&str> {
165 self.as_atom().filter(|s| is_guid(s))
166 }
167
168 pub fn as_base64(&self) -> Option<String> {
170 let data = self.as_atom()?.strip_prefix(BASE64)?;
171 Some(data.chars().filter(|c| !c.is_ascii_whitespace()).collect())
172 }
173
174 pub fn get(&self, idx: usize) -> Option<&Node> {
176 self.as_list()?.get(idx)
177 }
178
179 pub fn get_path(&self, path: &[usize]) -> Option<&Node> {
181 path.iter().try_fold(self, |node, &idx| node.get(idx))
182 }
183
184 pub fn len(&self) -> usize {
186 self.as_list().map_or(0, <[Node]>::len)
187 }
188
189 pub fn is_empty(&self) -> bool {
191 self.len() == 0
192 }
193
194 pub fn iter(&self) -> std::slice::Iter<'_, Node> {
196 self.as_list().unwrap_or(&[]).iter()
197 }
198
199 pub fn into_list(mut self) -> Option<Vec<Node>> {
200 match self.take_value() {
201 Value::List(items) => Some(items),
202 _ => None,
203 }
204 }
205
206 pub fn into_string(mut self) -> Option<String> {
208 match self.take_value() {
209 Value::Str(s) | Value::Atom(s) => Some(s),
210 Value::List(_) => None,
211 }
212 }
213
214 pub fn try_list(&self) -> Result<&[Node], AccessError> {
215 self.as_list().ok_or_else(|| self.unexpected("list"))
216 }
217
218 pub fn try_str(&self) -> Result<&str, AccessError> {
219 self.as_str()
220 .ok_or_else(|| self.unexpected("string or value"))
221 }
222
223 pub fn try_quoted(&self) -> Result<&str, AccessError> {
224 self.as_quoted()
225 .ok_or_else(|| self.unexpected("quoted string"))
226 }
227
228 pub fn try_atom(&self) -> Result<&str, AccessError> {
229 self.as_atom().ok_or_else(|| self.unexpected("value"))
230 }
231
232 pub fn try_parse<T>(&self) -> Result<T, AccessError>
236 where
237 T: FromStr,
238 T::Err: fmt::Display,
239 {
240 let expected = type_label::<T>();
241 let atom = self
242 .as_atom()
243 .ok_or_else(|| self.unexpected(expected.clone()))?;
244 atom.parse().map_err(|e: T::Err| {
245 AccessError::new(
246 AccessErrorKind::InvalidValue {
247 expected,
248 found: self.describe(),
249 reason: e.to_string(),
250 },
251 self.position,
252 )
253 })
254 }
255
256 pub fn try_i64(&self) -> Result<i64, AccessError> {
257 self.try_parse()
258 }
259
260 pub fn try_u64(&self) -> Result<u64, AccessError> {
261 self.try_parse()
262 }
263
264 pub fn try_f64(&self) -> Result<f64, AccessError> {
265 self.try_parse()
266 }
267
268 pub fn try_bool(&self) -> Result<bool, AccessError> {
269 self.as_bool()
270 .ok_or_else(|| self.unexpected("boolean (0 or 1)"))
271 }
272
273 pub fn try_guid(&self) -> Result<&str, AccessError> {
274 self.as_guid().ok_or_else(|| self.unexpected("GUID"))
275 }
276
277 pub fn try_get(&self, idx: usize) -> Result<&Node, AccessError> {
278 let items = self.try_list()?;
279 items.get(idx).ok_or_else(|| {
280 AccessError::new(
281 AccessErrorKind::IndexOutOfBounds {
282 index: idx,
283 len: items.len(),
284 },
285 self.position,
286 )
287 })
288 }
289
290 pub fn try_get_path(&self, path: &[usize]) -> Result<&Node, AccessError> {
294 let mut node = self;
295 for (depth, &idx) in path.iter().enumerate() {
296 node = node.try_get(idx).map_err(|e| e.with_path(&path[..depth]))?;
297 }
298 Ok(node)
299 }
300
301 pub fn as_list_mut(&mut self) -> Option<&mut Vec<Node>> {
303 match &mut self.value {
304 Value::List(items) => Some(items),
305 _ => None,
306 }
307 }
308
309 pub fn get_mut(&mut self, idx: usize) -> Option<&mut Node> {
310 self.as_list_mut()?.get_mut(idx)
311 }
312
313 pub fn get_path_mut(&mut self, path: &[usize]) -> Option<&mut Node> {
314 path.iter().try_fold(self, |node, &idx| node.get_mut(idx))
315 }
316
317 pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Node> {
319 match &mut self.value {
320 Value::List(items) => items.iter_mut(),
321 _ => [].iter_mut(),
322 }
323 }
324
325 pub fn try_list_mut(&mut self) -> Result<&mut Vec<Node>, AccessError> {
326 if !self.is_list() {
327 return Err(self.unexpected("list"));
328 }
329 Ok(self.as_list_mut().expect("checked above"))
330 }
331
332 pub fn try_get_mut(&mut self, idx: usize) -> Result<&mut Node, AccessError> {
333 let position = self.position;
334 let items = self.try_list_mut()?;
335 let len = items.len();
336 items.get_mut(idx).ok_or_else(|| {
337 AccessError::new(
338 AccessErrorKind::IndexOutOfBounds { index: idx, len },
339 position,
340 )
341 })
342 }
343
344 pub fn try_get_path_mut(&mut self, path: &[usize]) -> Result<&mut Node, AccessError> {
346 let mut node = self;
347 for (depth, &idx) in path.iter().enumerate() {
348 node = node
349 .try_get_mut(idx)
350 .map_err(|e| e.with_path(&path[..depth]))?;
351 }
352 Ok(node)
353 }
354
355 pub fn try_counted(&self) -> Result<&[Node], AccessError> {
359 let declared: usize = self
360 .try_get(0)?
361 .try_parse()
362 .map_err(|e| e.with_path(&[0]))?;
363 let items = &self.try_list()?[1..];
364 if items.len() != declared {
365 return Err(AccessError::new(
366 AccessErrorKind::CountMismatch {
367 declared,
368 actual: items.len(),
369 },
370 self.position,
371 ));
372 }
373 Ok(items)
374 }
375
376 pub fn walk<'a>(&'a self, mut visit: impl FnMut(&[usize], &'a Node)) {
378 let mut stack = vec![(self, 0usize, 0usize)];
380 let mut path = Vec::new();
381 while let Some((node, depth, idx)) = stack.pop() {
382 if depth > 0 {
383 path.truncate(depth - 1);
384 path.push(idx);
385 }
386 visit(&path, node);
387 for (i, item) in node.iter().enumerate().rev() {
388 stack.push((item, depth + 1, i));
389 }
390 }
391 }
392
393 pub fn describe(&self) -> String {
395 const MAX: usize = 40;
396 let clip = |s: &str| match s.char_indices().nth(MAX) {
397 Some((end, _)) => format!("{}…", &s[..end]),
398 None => s.to_owned(),
399 };
400 match &self.value {
401 Value::List(items) => match items.len() {
402 0 => "empty list".to_owned(),
403 1 => "list of 1 item".to_owned(),
404 n => format!("list of {n} items"),
405 },
406 Value::Str(s) => format!("string {:?}", clip(s)),
407 Value::Atom(s) if s.is_empty() => "empty value".to_owned(),
408 Value::Atom(s) => format!("value `{}`", clip(s)),
409 }
410 }
411
412 fn take_value(&mut self) -> Value {
414 std::mem::replace(&mut self.value, Value::Atom(String::new()))
415 }
416
417 pub(crate) fn unexpected(&self, expected: impl Into<Cow<'static, str>>) -> AccessError {
418 AccessError::new(
419 AccessErrorKind::UnexpectedType {
420 expected: expected.into(),
421 found: self.describe(),
422 },
423 self.position,
424 )
425 }
426}
427
428pub(crate) fn type_label<T>() -> Cow<'static, str> {
430 let name = std::any::type_name::<T>();
431 if name.contains('<') {
432 Cow::Borrowed(name)
433 } else {
434 Cow::Borrowed(name.rsplit("::").next().unwrap_or(name))
435 }
436}
437
438fn is_guid(s: &str) -> bool {
439 let bytes = s.as_bytes();
440 bytes.len() == 36
441 && bytes.iter().enumerate().all(|(i, &b)| match i {
442 8 | 13 | 18 | 23 => b == b'-',
443 _ => b.is_ascii_hexdigit(),
444 })
445}
446
447impl fmt::Debug for Node {
448 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
450 self.value.fmt(f)
451 }
452}
453
454impl fmt::Debug for Value {
455 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
457 fn indent(f: &mut fmt::Formatter<'_>, depth: usize) -> fmt::Result {
458 (0..depth).try_for_each(|_| f.write_str(" "))
459 }
460
461 let pretty = f.alternate();
462 let mut stack: Vec<(std::slice::Iter<'_, Node>, usize)> = Vec::new();
464 let mut next = Some((self, 0));
465 loop {
466 if let Some((value, depth)) = next.take() {
467 let (name, text) = match value {
468 Value::List(items) if !items.is_empty() => {
469 if pretty {
470 f.write_str("List(\n")?;
471 indent(f, depth + 1)?;
472 f.write_str("[\n")?;
473 indent(f, depth + 2)?;
474 } else {
475 f.write_str("List([")?;
476 }
477 let mut items = items.iter();
478 next = items.next().map(|item| (&item.value, depth + 2));
479 stack.push((items, depth));
480 continue;
481 }
482 Value::List(_) => ("List", None),
483 Value::Str(s) => ("Str", Some(s)),
484 Value::Atom(s) => ("Atom", Some(s)),
485 };
486 f.write_str(name)?;
487 f.write_str("(")?;
488 if pretty {
489 f.write_str("\n")?;
490 indent(f, depth + 1)?;
491 }
492 match text {
493 Some(s) => write!(f, "{s:?}")?,
494 None => f.write_str("[]")?,
495 }
496 if pretty {
497 f.write_str(",\n")?;
498 indent(f, depth)?;
499 }
500 f.write_str(")")?;
501 } else {
502 let Some((_, depth)) = stack.pop() else {
504 return Ok(());
505 };
506 if pretty {
507 indent(f, depth + 1)?;
508 f.write_str("],\n")?;
509 indent(f, depth)?;
510 f.write_str(")")?;
511 } else {
512 f.write_str("])")?;
513 }
514 }
515 let Some((items, depth)) = stack.last_mut() else {
517 return Ok(());
518 };
519 if pretty {
520 f.write_str(",\n")?;
521 }
522 if let Some(item) = items.next() {
523 if pretty {
524 indent(f, *depth + 2)?;
525 } else {
526 f.write_str(", ")?;
527 }
528 next = Some((&item.value, *depth + 2));
529 }
530 }
531 }
532}
533
534impl Clone for Node {
535 fn clone(&self) -> Self {
537 let Value::List(items) = &self.value else {
538 return Self {
539 value: self.value.clone(),
540 position: self.position,
541 };
542 };
543 let mut stack = vec![(items.iter(), Vec::with_capacity(items.len()), self.position)];
545 loop {
546 let (source, copied, _) = stack.last_mut().expect("clone stack is not empty");
547 match source.next() {
548 Some(item) => match &item.value {
549 Value::List(items) => {
550 stack.push((items.iter(), Vec::with_capacity(items.len()), item.position));
551 }
552 scalar => copied.push(Self {
553 value: scalar.clone(),
554 position: item.position,
555 }),
556 },
557 None => {
558 let (_, items, position) = stack.pop().expect("clone stack is not empty");
559 let done = Self {
560 value: Value::List(items),
561 position,
562 };
563 match stack.last_mut() {
564 Some((_, parent, _)) => parent.push(done),
565 None => return done,
566 }
567 }
568 }
569 }
570 }
571}
572
573impl Drop for Node {
574 fn drop(&mut self) {
576 let Value::List(items) = &mut self.value else {
577 return;
578 };
579 if items.iter().all(Node::is_empty) {
581 return;
582 }
583 let mut stack = std::mem::take(items);
584 while let Some(mut node) = stack.pop() {
585 if let Value::List(items) = &mut node.value {
586 stack.append(items);
587 }
588 }
589 }
590}
591
592impl PartialEq for Node {
593 fn eq(&self, other: &Self) -> bool {
595 let mut stack = vec![(std::slice::from_ref(self), std::slice::from_ref(other))];
596 while let Some((left, right)) = stack.pop() {
597 if left.len() != right.len() {
598 return false;
599 }
600 for (left, right) in left.iter().zip(right) {
601 match (&left.value, &right.value) {
602 (Value::List(left), Value::List(right)) => stack.push((left, right)),
603 (left, right) if left != right => return false,
605 _ => {}
606 }
607 }
608 }
609 true
610 }
611}
612
613impl Eq for Node {}
614
615impl Hash for Node {
616 fn hash<H: Hasher>(&self, state: &mut H) {
618 let mut stack = vec![self];
619 while let Some(node) = stack.pop() {
620 std::mem::discriminant(&node.value).hash(state);
621 match &node.value {
622 Value::List(items) => {
623 items.len().hash(state);
624 stack.extend(items.iter().rev());
625 }
626 Value::Str(s) | Value::Atom(s) => s.hash(state),
627 }
628 }
629 }
630}
631
632impl Index<usize> for Node {
633 type Output = Node;
634
635 fn index(&self, idx: usize) -> &Node {
636 match self.try_get(idx) {
637 Ok(node) => node,
638 Err(e) => panic!("cannot index node: {e}"),
639 }
640 }
641}
642
643impl IndexMut<usize> for Node {
644 fn index_mut(&mut self, idx: usize) -> &mut Node {
645 match self.try_get_mut(idx) {
646 Ok(node) => node,
647 Err(e) => panic!("cannot index node: {e}"),
648 }
649 }
650}
651
652impl<'a> IntoIterator for &'a Node {
653 type Item = &'a Node;
654 type IntoIter = std::slice::Iter<'a, Node>;
655
656 fn into_iter(self) -> Self::IntoIter {
657 self.iter()
658 }
659}
660
661impl<'a> IntoIterator for &'a mut Node {
662 type Item = &'a mut Node;
663 type IntoIter = std::slice::IterMut<'a, Node>;
664
665 fn into_iter(self) -> Self::IntoIter {
666 self.iter_mut()
667 }
668}
669
670impl From<Value> for Node {
671 fn from(value: Value) -> Self {
672 Self {
673 value,
674 position: None,
675 }
676 }
677}
678
679impl From<bool> for Node {
680 fn from(b: bool) -> Self {
681 Node::atom(if b { "1" } else { "0" })
682 }
683}
684
685macro_rules! from_number {
686 ($($t:ty),*) => {$(
687 impl From<$t> for Node {
688 fn from(n: $t) -> Self {
689 Node::atom(n.to_string())
690 }
691 }
692 )*};
693}
694
695from_number!(i8, i16, i32, i64, u8, u16, u32, u64, usize, isize);
696
697impl From<Vec<Node>> for Node {
698 fn from(items: Vec<Node>) -> Self {
699 Value::List(items).into()
700 }
701}
702
703impl<T: Into<Node>> FromIterator<T> for Node {
704 fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
705 Node::list(iter.into_iter().map(Into::into))
706 }
707}
708
709#[cfg(test)]
710mod tests {
711 use super::*;
712
713 fn sample() -> Node {
714 Node::from(vec![
715 Node::atom("12"),
716 Node::from(vec![
717 Node::string("text"),
718 Node::atom("c2f34349-b1e0-4060-8abf-3c59f38ac69f"),
719 Node::from(true),
720 ]),
721 Node::atom("#base64:QUJD\r\r\nREVG\r\r\n"),
722 Node::atom(""),
723 ])
724 }
725
726 #[test]
727 fn typed_accessors() {
728 let node = sample();
729 assert_eq!(node[0].as_i64(), Some(12));
730 assert_eq!(node[0].as_f64(), Some(12.0));
731 assert_eq!(node[0].as_parsed::<u8>(), Some(12));
732 assert_eq!(
733 node.get_path(&[1, 0]).and_then(Node::as_quoted),
734 Some("text")
735 );
736 assert_eq!(node.get_path(&[1, 0]).and_then(Node::as_atom), None);
737 assert!(node.get_path(&[1, 1]).and_then(Node::as_guid).is_some());
738 assert_eq!(node.get_path(&[1, 2]).and_then(Node::as_bool), Some(true));
739 assert_eq!(node[2].as_base64().as_deref(), Some("QUJDREVG"));
740 assert!(node[3].is_empty_atom());
741 assert_eq!(node.get_path(&[1, 5]), None);
742 assert_eq!(node.len(), 4);
743 assert_eq!(node[0].iter().count(), 0);
744 assert_eq!(node[0].position(), None);
745 }
746
747 #[test]
748 fn try_accessors_explain_mismatch() {
749 let node = sample();
750 insta::assert_snapshot!(node.try_get(7).unwrap_err(), @"index 7 is out of bounds for list of length 4");
751 insta::assert_snapshot!(node[0].try_list().unwrap_err(), @"expected list, found value `12`");
752 insta::assert_snapshot!(node[1].try_i64().unwrap_err(), @"expected i64, found list of 3 items");
753 insta::assert_snapshot!(node[3].try_guid().unwrap_err(), @"expected GUID, found empty value");
754 insta::assert_snapshot!(node[1][0].try_bool().unwrap_err(), @r#"expected boolean (0 or 1), found string "text""#);
755 insta::assert_snapshot!(node[1][1].try_parse::<u32>().unwrap_err(), @"expected u32, found value `c2f34349-b1e0-4060-8abf-3c59f38ac69f`: invalid digit found in string");
756 insta::assert_snapshot!(node.try_get_path(&[1, 0, 3]).unwrap_err(), @r#"expected list, found string "text" (path [1, 0])"#);
757 insta::assert_snapshot!(node.try_get_path(&[1, 9]).unwrap_err(), @"index 9 is out of bounds for list of length 3 (path [1])");
758 }
759
760 #[test]
761 fn parsed_nodes_report_positions() {
762 let node: Node = "{1,\r\n{x,\"s\"}}".parse().unwrap();
763 insta::assert_snapshot!(node.try_get_path(&[1, 0]).unwrap().try_parse::<u8>().unwrap_err(), @"expected u8, found value `x` at line 2, column 2: invalid digit found in string");
764 insta::assert_snapshot!(node.try_get_path(&[1, 1, 0]).unwrap_err(), @r#"expected list, found string "s" at line 2, column 4 (path [1, 1])"#);
765 insta::assert_snapshot!(node[1].try_get(5).unwrap_err(), @"index 5 is out of bounds for list of length 2 at line 2, column 1");
766 }
767
768 #[test]
769 fn equality_ignores_positions() {
770 let parsed: Node = "{1,\"a\"}".parse().unwrap();
771 assert_eq!(parsed, Node::list([Node::from(1), Node::string("a")]));
772 }
773
774 #[test]
775 fn walks_with_paths() {
776 let mut visited = Vec::new();
777 let node = Node::from(vec![
778 Node::atom("12"),
779 Node::from(vec![Node::string("a"), Node::from(vec![Node::from(true)])]),
780 Node::atom(""),
781 ]);
782 node.walk(|path, node| visited.push(format!("{path:?} {}", node.describe())));
783 insta::assert_snapshot!(visited.join("\n"), @r#"
784 [] list of 3 items
785 [0] value `12`
786 [1] list of 2 items
787 [1, 0] string "a"
788 [1, 1] list of 1 item
789 [1, 1, 0] value `1`
790 [2] empty value
791 "#);
792 }
793
794 #[test]
795 #[should_panic(expected = "cannot index node: expected list, found value `12`")]
796 fn index_panics_with_message() {
797 let _ = &sample()[0][0];
798 }
799
800 #[test]
801 fn type_labels_are_short() {
802 struct Local;
803 assert_eq!(type_label::<u32>(), "u32");
804 assert_eq!(type_label::<Local>(), "Local");
805 assert_eq!(type_label::<String>(), "String");
806 }
807
808 #[test]
809 fn edits_in_place() {
810 let mut node: Node = "{1,\r\n{a,\"b\"}}".parse().unwrap();
811 node[0] = Node::from(2);
812 *node.try_get_path_mut(&[1, 1]).unwrap().value_mut() = Value::Str("c".into());
813 node.get_mut(1)
814 .unwrap()
815 .try_list_mut()
816 .unwrap()
817 .push(Node::atom("d"));
818 for item in &mut node[1] {
819 if item.as_atom() == Some("a") {
820 *item = Node::atom("z");
821 }
822 }
823 assert_eq!(node.to_string(), "{2,\r\n{z,\"c\",d}\r\n}");
824 assert_eq!(
826 node.get_path(&[1, 1]).unwrap().position().unwrap().column,
827 4
828 );
829 assert!(node.get_path_mut(&[0, 0]).is_none());
830 assert_eq!(node[0].iter_mut().count(), 0);
831
832 insta::assert_snapshot!(node.try_get_path_mut(&[1, 7]).unwrap_err(), @"index 7 is out of bounds for list of length 3 at line 2, column 1 (path [1])");
833 insta::assert_snapshot!(node[0].try_list_mut().unwrap_err(), @"expected list, found value `2`");
834 }
835
836 #[test]
837 #[should_panic(expected = "cannot index node: index 5 is out of bounds for list of length 2")]
838 fn index_mut_panics_with_message() {
839 let mut node = Node::list([Node::from(1), Node::from(2)]);
840 node[5] = Node::from(3);
841 }
842
843 #[test]
844 fn counted_lists() {
845 let node: Node = "{2,a,\r\n{b}}".parse().unwrap();
846 assert_eq!(
847 node.try_counted().unwrap(),
848 [Node::atom("a"), Node::list([Node::atom("b")])]
849 );
850 assert_eq!(Node::counted(node.try_counted().unwrap().to_vec()), node);
851 assert_eq!(Node::counted([]).to_string(), "{0}");
852
853 let bad: Node = "{\r\n{x},\r\n{3,a},\r\n{}}".parse().unwrap();
854 insta::assert_snapshot!(bad[0].try_counted().unwrap_err(), @"expected usize, found value `x` at line 2, column 2 (path [0]): invalid digit found in string");
855 insta::assert_snapshot!(bad[1].try_counted().unwrap_err(), @"list declares 3 items, but has 1 at line 3, column 1");
856 insta::assert_snapshot!(bad[2].try_counted().unwrap_err(), @"index 0 is out of bounds for list of length 0 at line 4, column 1");
857 }
858
859 #[test]
860 fn debug_matches_derived_layout() {
861 let node = Node::list([
862 Node::atom("1"),
863 Node::list([]),
864 Node::list([Node::string("a \"b\"")]),
865 ]);
866 assert_eq!(
867 format!("{node:?}"),
868 r#"List([Atom("1"), List([]), List([Str("a \"b\"")])])"#
869 );
870 insta::assert_snapshot!(format!("{node:#?}"), @r#"
871 List(
872 [
873 Atom(
874 "1",
875 ),
876 List(
877 [],
878 ),
879 List(
880 [
881 Str(
882 "a \"b\"",
883 ),
884 ],
885 ),
886 ],
887 )
888 "#);
889 }
890
891 #[test]
892 fn deep_trees_without_recursion() {
893 let depth = 100_000;
894 let deep = || {
895 (0..depth).fold(Node::string("leaf"), |inner, _| {
896 Node::list([Node::from(1), inner])
897 })
898 };
899 let node = deep();
900 let copy = node.clone();
901 assert_eq!(copy, node);
902 assert_ne!(copy, Node::list([]));
903
904 let hash = |node: &Node| {
905 let mut hasher = std::hash::DefaultHasher::new();
906 node.hash(&mut hasher);
907 hasher.finish()
908 };
909 assert_eq!(hash(©), hash(&deep()));
910
911 let debug = format!("{node:?}");
912 assert!(debug.starts_with("List([Atom(\"1\"), List(["));
913
914 assert_eq!(node.into_list().map(|items| items.len()), Some(2));
915 drop(copy);
916 }
917
918 #[test]
919 fn detects_guid() {
920 assert!(is_guid("c2f34349-b1e0-4060-8abf-3c59f38ac69f"));
921 assert!(!is_guid("c2f34349-b1e0-4060-8abf-3c59f38ac69"));
922 }
923}