eure_document/parse/
variant_path.rs

1//! VariantPath type for nested union variant paths.
2
3extern crate alloc;
4
5use alloc::fmt;
6use alloc::vec::Vec;
7use core::str::FromStr;
8
9use crate::identifier::{Identifier, IdentifierError};
10
11/// A path through nested union variants (e.g., "ok.some.left" -> [ok, some, left]).
12///
13/// Used for parsing and validating nested unions with `$variant` extension.
14#[derive(Debug, Clone, PartialEq, Eq)]
15pub struct VariantPath(Vec<Identifier>);
16
17impl VariantPath {
18    /// Create a new VariantPath from a vector of identifiers.
19    pub fn new(segments: Vec<Identifier>) -> Self {
20        Self(segments)
21    }
22
23    /// Create an empty variant path.
24    pub fn empty() -> Self {
25        Self(Vec::new())
26    }
27
28    /// Parse a variant path from a dotted string (e.g., "ok.some.left").
29    pub fn parse(s: &str) -> Result<Self, IdentifierError> {
30        let segments: Result<Vec<_>, _> =
31            s.split('.').map(|seg| seg.parse::<Identifier>()).collect();
32        segments.map(Self)
33    }
34
35    /// Get the first segment.
36    pub fn first(&self) -> Option<&Identifier> {
37        self.0.first()
38    }
39
40    /// Get the remaining path after the first segment.
41    pub fn rest(&self) -> Option<Self> {
42        if self.0.len() > 1 {
43            Some(Self(self.0[1..].to_vec()))
44        } else {
45            None
46        }
47    }
48
49    /// Check if the path is empty.
50    pub fn is_empty(&self) -> bool {
51        self.0.is_empty()
52    }
53
54    /// Check if this is a single-segment path.
55    pub fn is_single(&self) -> bool {
56        self.0.len() == 1
57    }
58
59    /// Get the number of segments in the path.
60    pub fn len(&self) -> usize {
61        self.0.len()
62    }
63
64    /// Get the segments as a slice.
65    pub fn segments(&self) -> &[Identifier] {
66        &self.0
67    }
68}
69
70impl FromStr for VariantPath {
71    type Err = IdentifierError;
72
73    fn from_str(s: &str) -> Result<Self, Self::Err> {
74        Self::parse(s)
75    }
76}
77
78impl fmt::Display for VariantPath {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        let mut first = true;
81        for seg in &self.0 {
82            if !first {
83                write!(f, ".")?;
84            }
85            write!(f, "{}", seg)?;
86            first = false;
87        }
88        Ok(())
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    use alloc::string::ToString;
96
97    #[test]
98    fn test_parse_single() {
99        let path: VariantPath = "ok".parse().unwrap();
100        assert!(path.is_single());
101        assert_eq!(path.first().unwrap().as_ref(), "ok");
102        assert!(path.rest().is_none());
103    }
104
105    #[test]
106    fn test_parse_nested() {
107        let path: VariantPath = "ok.some.left".parse().unwrap();
108        assert!(!path.is_single());
109        assert_eq!(path.len(), 3);
110        assert_eq!(path.first().unwrap().as_ref(), "ok");
111
112        let rest = path.rest().unwrap();
113        assert_eq!(rest.len(), 2);
114        assert_eq!(rest.first().unwrap().as_ref(), "some");
115
116        let rest2 = rest.rest().unwrap();
117        assert!(rest2.is_single());
118        assert_eq!(rest2.first().unwrap().as_ref(), "left");
119        assert!(rest2.rest().is_none());
120    }
121
122    #[test]
123    fn test_display() {
124        let path: VariantPath = "ok.some.left".parse().unwrap();
125        assert_eq!(path.to_string(), "ok.some.left");
126    }
127
128    #[test]
129    fn test_invalid_identifier() {
130        let result = VariantPath::parse("ok.123invalid");
131        assert!(result.is_err());
132    }
133}