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
13#[derive(Clone)]
18pub struct Node {
19 value: Value,
20 position: Option<Position>,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq, Hash)]
25pub enum Value {
26 List(Vec<Node>),
28 Str(String),
30 Atom(String),
33}
34
35impl Node {
36 pub(crate) fn parsed(value: Value, position: Position) -> Self {
37 Self {
38 value,
39 position: Some(position),
40 }
41 }
42
43 pub fn list(items: impl IntoIterator<Item = Node>) -> Self {
45 Value::List(items.into_iter().collect()).into()
46 }
47
48 pub fn atom(value: impl Into<String>) -> Self {
50 Value::Atom(value.into()).into()
51 }
52
53 pub fn string(value: impl Into<String>) -> Self {
57 Value::Str(value.into()).into()
58 }
59
60 pub fn counted(items: impl IntoIterator<Item = Node>) -> Self {
62 let items: Vec<Node> = items.into_iter().collect();
63 Node::list(std::iter::once(Node::from(items.len())).chain(items))
64 }
65
66 pub fn value(&self) -> &Value {
67 &self.value
68 }
69
70 pub fn into_value(self) -> Value {
71 self.value
72 }
73
74 pub fn value_mut(&mut self) -> &mut Value {
76 &mut self.value
77 }
78
79 pub fn position(&self) -> Option<Position> {
83 self.position
84 }
85
86 pub fn is_list(&self) -> bool {
87 matches!(self.value, Value::List(_))
88 }
89
90 pub fn is_str(&self) -> bool {
91 matches!(self.value, Value::Str(_))
92 }
93
94 pub fn is_atom(&self) -> bool {
95 matches!(self.value, Value::Atom(_))
96 }
97
98 pub fn is_empty_atom(&self) -> bool {
100 matches!(&self.value, Value::Atom(s) if s.is_empty())
101 }
102
103 pub fn as_list(&self) -> Option<&[Node]> {
104 match &self.value {
105 Value::List(items) => Some(items),
106 _ => None,
107 }
108 }
109
110 pub fn as_str(&self) -> Option<&str> {
112 match &self.value {
113 Value::Str(s) | Value::Atom(s) => Some(s),
114 Value::List(_) => None,
115 }
116 }
117
118 pub fn as_quoted(&self) -> Option<&str> {
120 match &self.value {
121 Value::Str(s) => Some(s),
122 _ => None,
123 }
124 }
125
126 pub fn as_atom(&self) -> Option<&str> {
128 match &self.value {
129 Value::Atom(s) => Some(s),
130 _ => None,
131 }
132 }
133
134 pub fn as_parsed<T: FromStr>(&self) -> Option<T> {
136 self.as_atom()?.parse().ok()
137 }
138
139 pub fn as_i64(&self) -> Option<i64> {
140 self.as_parsed()
141 }
142
143 pub fn as_u64(&self) -> Option<u64> {
144 self.as_parsed()
145 }
146
147 pub fn as_f64(&self) -> Option<f64> {
148 self.as_parsed()
149 }
150
151 pub fn as_bool(&self) -> Option<bool> {
153 match self.as_atom()? {
154 "1" => Some(true),
155 "0" => Some(false),
156 _ => None,
157 }
158 }
159
160 pub fn as_guid(&self) -> Option<&str> {
162 self.as_atom().filter(|s| is_guid(s))
163 }
164
165 pub fn as_base64(&self) -> Option<String> {
167 let data = self.as_atom()?.strip_prefix(BASE64)?;
168 Some(data.chars().filter(|c| !c.is_ascii_whitespace()).collect())
169 }
170
171 pub fn get(&self, idx: usize) -> Option<&Node> {
173 self.as_list()?.get(idx)
174 }
175
176 pub fn get_path(&self, path: &[usize]) -> Option<&Node> {
178 path.iter().try_fold(self, |node, &idx| node.get(idx))
179 }
180
181 pub fn len(&self) -> usize {
183 self.as_list().map_or(0, <[Node]>::len)
184 }
185
186 pub fn is_empty(&self) -> bool {
188 self.len() == 0
189 }
190
191 pub fn iter(&self) -> std::slice::Iter<'_, Node> {
193 self.as_list().unwrap_or(&[]).iter()
194 }
195
196 pub fn into_list(self) -> Option<Vec<Node>> {
197 match self.value {
198 Value::List(items) => Some(items),
199 _ => None,
200 }
201 }
202
203 pub fn into_string(self) -> Option<String> {
205 match self.value {
206 Value::Str(s) | Value::Atom(s) => Some(s),
207 Value::List(_) => None,
208 }
209 }
210
211 pub fn try_list(&self) -> Result<&[Node], AccessError> {
212 self.as_list().ok_or_else(|| self.unexpected("list"))
213 }
214
215 pub fn try_str(&self) -> Result<&str, AccessError> {
216 self.as_str()
217 .ok_or_else(|| self.unexpected("string or value"))
218 }
219
220 pub fn try_quoted(&self) -> Result<&str, AccessError> {
221 self.as_quoted()
222 .ok_or_else(|| self.unexpected("quoted string"))
223 }
224
225 pub fn try_atom(&self) -> Result<&str, AccessError> {
226 self.as_atom().ok_or_else(|| self.unexpected("value"))
227 }
228
229 pub fn try_parse<T>(&self) -> Result<T, AccessError>
233 where
234 T: FromStr,
235 T::Err: fmt::Display,
236 {
237 let expected = type_label::<T>();
238 let atom = self
239 .as_atom()
240 .ok_or_else(|| self.unexpected(expected.clone()))?;
241 atom.parse().map_err(|e: T::Err| {
242 AccessError::new(
243 AccessErrorKind::InvalidValue {
244 expected,
245 found: self.describe(),
246 reason: e.to_string(),
247 },
248 self.position,
249 )
250 })
251 }
252
253 pub fn try_i64(&self) -> Result<i64, AccessError> {
254 self.try_parse()
255 }
256
257 pub fn try_u64(&self) -> Result<u64, AccessError> {
258 self.try_parse()
259 }
260
261 pub fn try_f64(&self) -> Result<f64, AccessError> {
262 self.try_parse()
263 }
264
265 pub fn try_bool(&self) -> Result<bool, AccessError> {
266 self.as_bool()
267 .ok_or_else(|| self.unexpected("boolean (0 or 1)"))
268 }
269
270 pub fn try_guid(&self) -> Result<&str, AccessError> {
271 self.as_guid().ok_or_else(|| self.unexpected("GUID"))
272 }
273
274 pub fn try_get(&self, idx: usize) -> Result<&Node, AccessError> {
275 let items = self.try_list()?;
276 items.get(idx).ok_or_else(|| {
277 AccessError::new(
278 AccessErrorKind::IndexOutOfBounds {
279 index: idx,
280 len: items.len(),
281 },
282 self.position,
283 )
284 })
285 }
286
287 pub fn try_get_path(&self, path: &[usize]) -> Result<&Node, AccessError> {
291 let mut node = self;
292 for (depth, &idx) in path.iter().enumerate() {
293 node = node.try_get(idx).map_err(|e| e.with_path(&path[..depth]))?;
294 }
295 Ok(node)
296 }
297
298 pub fn as_list_mut(&mut self) -> Option<&mut Vec<Node>> {
300 match &mut self.value {
301 Value::List(items) => Some(items),
302 _ => None,
303 }
304 }
305
306 pub fn get_mut(&mut self, idx: usize) -> Option<&mut Node> {
307 self.as_list_mut()?.get_mut(idx)
308 }
309
310 pub fn get_path_mut(&mut self, path: &[usize]) -> Option<&mut Node> {
311 path.iter().try_fold(self, |node, &idx| node.get_mut(idx))
312 }
313
314 pub fn iter_mut(&mut self) -> std::slice::IterMut<'_, Node> {
316 match &mut self.value {
317 Value::List(items) => items.iter_mut(),
318 _ => [].iter_mut(),
319 }
320 }
321
322 pub fn try_list_mut(&mut self) -> Result<&mut Vec<Node>, AccessError> {
323 if !self.is_list() {
324 return Err(self.unexpected("list"));
325 }
326 Ok(self.as_list_mut().expect("checked above"))
327 }
328
329 pub fn try_get_mut(&mut self, idx: usize) -> Result<&mut Node, AccessError> {
330 let position = self.position;
331 let items = self.try_list_mut()?;
332 let len = items.len();
333 items.get_mut(idx).ok_or_else(|| {
334 AccessError::new(
335 AccessErrorKind::IndexOutOfBounds { index: idx, len },
336 position,
337 )
338 })
339 }
340
341 pub fn try_get_path_mut(&mut self, path: &[usize]) -> Result<&mut Node, AccessError> {
343 let mut node = self;
344 for (depth, &idx) in path.iter().enumerate() {
345 node = node
346 .try_get_mut(idx)
347 .map_err(|e| e.with_path(&path[..depth]))?;
348 }
349 Ok(node)
350 }
351
352 pub fn try_counted(&self) -> Result<&[Node], AccessError> {
356 let declared: usize = self
357 .try_get(0)?
358 .try_parse()
359 .map_err(|e| e.with_path(&[0]))?;
360 let items = &self.try_list()?[1..];
361 if items.len() != declared {
362 return Err(AccessError::new(
363 AccessErrorKind::CountMismatch {
364 declared,
365 actual: items.len(),
366 },
367 self.position,
368 ));
369 }
370 Ok(items)
371 }
372
373 pub fn walk<'a>(&'a self, mut visit: impl FnMut(&[usize], &'a Node)) {
375 let mut stack = vec![(self, 0usize, 0usize)];
377 let mut path = Vec::new();
378 while let Some((node, depth, idx)) = stack.pop() {
379 if depth > 0 {
380 path.truncate(depth - 1);
381 path.push(idx);
382 }
383 visit(&path, node);
384 for (i, item) in node.iter().enumerate().rev() {
385 stack.push((item, depth + 1, i));
386 }
387 }
388 }
389
390 pub fn describe(&self) -> String {
392 const MAX: usize = 40;
393 let clip = |s: &str| match s.char_indices().nth(MAX) {
394 Some((end, _)) => format!("{}…", &s[..end]),
395 None => s.to_owned(),
396 };
397 match &self.value {
398 Value::List(items) => match items.len() {
399 0 => "empty list".to_owned(),
400 1 => "list of 1 item".to_owned(),
401 n => format!("list of {n} items"),
402 },
403 Value::Str(s) => format!("string {:?}", clip(s)),
404 Value::Atom(s) if s.is_empty() => "empty value".to_owned(),
405 Value::Atom(s) => format!("value `{}`", clip(s)),
406 }
407 }
408
409 pub(crate) fn unexpected(&self, expected: impl Into<Cow<'static, str>>) -> AccessError {
410 AccessError::new(
411 AccessErrorKind::UnexpectedType {
412 expected: expected.into(),
413 found: self.describe(),
414 },
415 self.position,
416 )
417 }
418}
419
420pub(crate) fn type_label<T>() -> Cow<'static, str> {
422 let name = std::any::type_name::<T>();
423 if name.contains('<') {
424 Cow::Borrowed(name)
425 } else {
426 Cow::Borrowed(name.rsplit("::").next().unwrap_or(name))
427 }
428}
429
430fn is_guid(s: &str) -> bool {
431 let bytes = s.as_bytes();
432 bytes.len() == 36
433 && bytes.iter().enumerate().all(|(i, &b)| match i {
434 8 | 13 | 18 | 23 => b == b'-',
435 _ => b.is_ascii_hexdigit(),
436 })
437}
438
439impl fmt::Debug for Node {
440 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
442 self.value.fmt(f)
443 }
444}
445
446impl PartialEq for Node {
447 fn eq(&self, other: &Self) -> bool {
448 self.value == other.value
449 }
450}
451
452impl Eq for Node {}
453
454impl Hash for Node {
455 fn hash<H: Hasher>(&self, state: &mut H) {
456 self.value.hash(state);
457 }
458}
459
460impl Index<usize> for Node {
461 type Output = Node;
462
463 fn index(&self, idx: usize) -> &Node {
464 match self.try_get(idx) {
465 Ok(node) => node,
466 Err(e) => panic!("cannot index node: {e}"),
467 }
468 }
469}
470
471impl IndexMut<usize> for Node {
472 fn index_mut(&mut self, idx: usize) -> &mut Node {
473 match self.try_get_mut(idx) {
474 Ok(node) => node,
475 Err(e) => panic!("cannot index node: {e}"),
476 }
477 }
478}
479
480impl<'a> IntoIterator for &'a Node {
481 type Item = &'a Node;
482 type IntoIter = std::slice::Iter<'a, Node>;
483
484 fn into_iter(self) -> Self::IntoIter {
485 self.iter()
486 }
487}
488
489impl<'a> IntoIterator for &'a mut Node {
490 type Item = &'a mut Node;
491 type IntoIter = std::slice::IterMut<'a, Node>;
492
493 fn into_iter(self) -> Self::IntoIter {
494 self.iter_mut()
495 }
496}
497
498impl From<Value> for Node {
499 fn from(value: Value) -> Self {
500 Self {
501 value,
502 position: None,
503 }
504 }
505}
506
507impl From<bool> for Node {
508 fn from(b: bool) -> Self {
509 Node::atom(if b { "1" } else { "0" })
510 }
511}
512
513macro_rules! from_number {
514 ($($t:ty),*) => {$(
515 impl From<$t> for Node {
516 fn from(n: $t) -> Self {
517 Node::atom(n.to_string())
518 }
519 }
520 )*};
521}
522
523from_number!(i8, i16, i32, i64, u8, u16, u32, u64, usize, isize);
524
525impl From<Vec<Node>> for Node {
526 fn from(items: Vec<Node>) -> Self {
527 Value::List(items).into()
528 }
529}
530
531impl<T: Into<Node>> FromIterator<T> for Node {
532 fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
533 Node::list(iter.into_iter().map(Into::into))
534 }
535}
536
537#[cfg(test)]
538mod tests {
539 use super::*;
540
541 fn sample() -> Node {
542 Node::from(vec![
543 Node::atom("12"),
544 Node::from(vec![
545 Node::string("text"),
546 Node::atom("c2f34349-b1e0-4060-8abf-3c59f38ac69f"),
547 Node::from(true),
548 ]),
549 Node::atom("#base64:QUJD\r\r\nREVG\r\r\n"),
550 Node::atom(""),
551 ])
552 }
553
554 #[test]
555 fn typed_accessors() {
556 let node = sample();
557 assert_eq!(node[0].as_i64(), Some(12));
558 assert_eq!(node[0].as_f64(), Some(12.0));
559 assert_eq!(node[0].as_parsed::<u8>(), Some(12));
560 assert_eq!(
561 node.get_path(&[1, 0]).and_then(Node::as_quoted),
562 Some("text")
563 );
564 assert_eq!(node.get_path(&[1, 0]).and_then(Node::as_atom), None);
565 assert!(node.get_path(&[1, 1]).and_then(Node::as_guid).is_some());
566 assert_eq!(node.get_path(&[1, 2]).and_then(Node::as_bool), Some(true));
567 assert_eq!(node[2].as_base64().as_deref(), Some("QUJDREVG"));
568 assert!(node[3].is_empty_atom());
569 assert_eq!(node.get_path(&[1, 5]), None);
570 assert_eq!(node.len(), 4);
571 assert_eq!(node[0].iter().count(), 0);
572 assert_eq!(node[0].position(), None);
573 }
574
575 #[test]
576 fn try_accessors_explain_mismatch() {
577 let node = sample();
578 insta::assert_snapshot!(node.try_get(7).unwrap_err(), @"index 7 is out of bounds for list of length 4");
579 insta::assert_snapshot!(node[0].try_list().unwrap_err(), @"expected list, found value `12`");
580 insta::assert_snapshot!(node[1].try_i64().unwrap_err(), @"expected i64, found list of 3 items");
581 insta::assert_snapshot!(node[3].try_guid().unwrap_err(), @"expected GUID, found empty value");
582 insta::assert_snapshot!(node[1][0].try_bool().unwrap_err(), @r#"expected boolean (0 or 1), found string "text""#);
583 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");
584 insta::assert_snapshot!(node.try_get_path(&[1, 0, 3]).unwrap_err(), @r#"expected list, found string "text" (path [1, 0])"#);
585 insta::assert_snapshot!(node.try_get_path(&[1, 9]).unwrap_err(), @"index 9 is out of bounds for list of length 3 (path [1])");
586 }
587
588 #[test]
589 fn parsed_nodes_report_positions() {
590 let node: Node = "{1,\r\n{x,\"s\"}}".parse().unwrap();
591 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");
592 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])"#);
593 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");
594 }
595
596 #[test]
597 fn equality_ignores_positions() {
598 let parsed: Node = "{1,\"a\"}".parse().unwrap();
599 assert_eq!(parsed, Node::list([Node::from(1), Node::string("a")]));
600 }
601
602 #[test]
603 fn walks_with_paths() {
604 let mut visited = Vec::new();
605 let node = Node::from(vec![
606 Node::atom("12"),
607 Node::from(vec![Node::string("a"), Node::from(vec![Node::from(true)])]),
608 Node::atom(""),
609 ]);
610 node.walk(|path, node| visited.push(format!("{path:?} {}", node.describe())));
611 insta::assert_snapshot!(visited.join("\n"), @r#"
612 [] list of 3 items
613 [0] value `12`
614 [1] list of 2 items
615 [1, 0] string "a"
616 [1, 1] list of 1 item
617 [1, 1, 0] value `1`
618 [2] empty value
619 "#);
620 }
621
622 #[test]
623 #[should_panic(expected = "cannot index node: expected list, found value `12`")]
624 fn index_panics_with_message() {
625 let _ = &sample()[0][0];
626 }
627
628 #[test]
629 fn type_labels_are_short() {
630 struct Local;
631 assert_eq!(type_label::<u32>(), "u32");
632 assert_eq!(type_label::<Local>(), "Local");
633 assert_eq!(type_label::<String>(), "String");
634 }
635
636 #[test]
637 fn edits_in_place() {
638 let mut node: Node = "{1,\r\n{a,\"b\"}}".parse().unwrap();
639 node[0] = Node::from(2);
640 *node.try_get_path_mut(&[1, 1]).unwrap().value_mut() = Value::Str("c".into());
641 node.get_mut(1)
642 .unwrap()
643 .try_list_mut()
644 .unwrap()
645 .push(Node::atom("d"));
646 for item in &mut node[1] {
647 if item.as_atom() == Some("a") {
648 *item = Node::atom("z");
649 }
650 }
651 assert_eq!(node.to_string(), "{2,\r\n{z,\"c\",d}\r\n}");
652 assert_eq!(
654 node.get_path(&[1, 1]).unwrap().position().unwrap().column,
655 4
656 );
657 assert!(node.get_path_mut(&[0, 0]).is_none());
658 assert_eq!(node[0].iter_mut().count(), 0);
659
660 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])");
661 insta::assert_snapshot!(node[0].try_list_mut().unwrap_err(), @"expected list, found value `2`");
662 }
663
664 #[test]
665 #[should_panic(expected = "cannot index node: index 5 is out of bounds for list of length 2")]
666 fn index_mut_panics_with_message() {
667 let mut node = Node::list([Node::from(1), Node::from(2)]);
668 node[5] = Node::from(3);
669 }
670
671 #[test]
672 fn counted_lists() {
673 let node: Node = "{2,a,\r\n{b}}".parse().unwrap();
674 assert_eq!(
675 node.try_counted().unwrap(),
676 [Node::atom("a"), Node::list([Node::atom("b")])]
677 );
678 assert_eq!(Node::counted(node.try_counted().unwrap().to_vec()), node);
679 assert_eq!(Node::counted([]).to_string(), "{0}");
680
681 let bad: Node = "{\r\n{x},\r\n{3,a},\r\n{}}".parse().unwrap();
682 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");
683 insta::assert_snapshot!(bad[1].try_counted().unwrap_err(), @"list declares 3 items, but has 1 at line 3, column 1");
684 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");
685 }
686
687 #[test]
688 fn detects_guid() {
689 assert!(is_guid("c2f34349-b1e0-4060-8abf-3c59f38ac69f"));
690 assert!(!is_guid("c2f34349-b1e0-4060-8abf-3c59f38ac69"));
691 }
692}