1use std::fmt::{self, Write};
4use std::iter::Peekable;
5use std::str::{CharIndices, FromStr};
6
7use serde::de::{self, Visitor};
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9use thiserror::Error;
10
11const SEPARATOR: char = '.';
13const QUOTE: char = '"';
15const ESCAPE: char = '\\';
17const ESCAPED_CHARACTERS: [char; 2] = [QUOTE, ESCAPE];
19
20#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
22pub struct ElementPath {
23 segments: Vec<String>,
24}
25
26impl ElementPath {
27 pub fn root() -> Self {
29 Self {
30 segments: Vec::new(),
31 }
32 }
33
34 pub fn from_segments<I, S>(segments: I) -> Self
36 where
37 I: IntoIterator<Item = S>,
38 S: Into<String>,
39 {
40 let segments = segments.into_iter().map(Into::into).collect();
41 Self { segments }
42 }
43
44 pub fn child(&self, segment: impl Into<String>) -> Self {
46 let mut segments = self.segments.clone();
47 let segment = segment.into();
48 segments.push(segment);
49 Self { segments }
50 }
51
52 pub fn enclosing_level(&self) -> Self {
54 let parent = self.parent();
55 parent.unwrap_or_else(Self::root)
56 }
57
58 pub fn parent(&self) -> Option<Self> {
60 let (_last_segment, parent_segment_slice) = self.segments.split_last()?;
61 let parent_segments = parent_segment_slice.iter().cloned();
62 let parent = Self::from_segments(parent_segments);
63 Some(parent)
64 }
65
66 pub fn is_ancestor_of(&self, other: &Self) -> bool {
68 let is_shorter = self.segments.len() < other.segments.len();
69 let is_prefix = other.segments.starts_with(&self.segments);
70 is_shorter && is_prefix
71 }
72
73 pub fn shares_lineage_with(&self, other: &Self) -> bool {
75 let is_other_ancestor_or_self = self.segments.starts_with(&other.segments);
76 let is_other_descendant_or_self = other.segments.starts_with(&self.segments);
77 is_other_ancestor_or_self || is_other_descendant_or_self
78 }
79
80 pub fn lowest_common_ancestor(&self, other: &Self) -> Self {
82 let shared_segments = self
83 .segments
84 .iter()
85 .zip(&other.segments)
86 .take_while(|(own, others)| own == others)
87 .map(|(own, _others)| own.clone());
88 Self::from_segments(shared_segments)
89 }
90
91 pub fn ancestors(&self) -> impl Iterator<Item = Self> + '_ {
93 let proper_prefix_lengths = 1..self.segments.len();
94 proper_prefix_lengths.map(|depth| self.ancestor_or_self_at_depth(depth))
95 }
96
97 pub fn ancestor_or_self_at_depth(&self, depth: usize) -> Self {
99 let prefix_segments = self.segments[..depth].iter().cloned();
100 Self::from_segments(prefix_segments)
101 }
102
103 pub fn truncated_to_depth(&self, depth: usize) -> Self {
105 let own_depth = self.depth();
106 let prefix_length = own_depth.min(depth);
107 self.ancestor_or_self_at_depth(prefix_length)
108 }
109
110 pub fn is_component_id(text: &str) -> bool {
112 let mut characters = text.chars();
113 let Some(first_character) = characters.next() else {
114 return false;
115 };
116 let is_valid_start = first_character.is_ascii_alphabetic() || first_character == '_';
117 let has_only_id_characters = characters
118 .all(|character| character.is_ascii_alphanumeric() || matches!(character, '_' | '-'));
119 let has_double_hyphen = text.contains("--");
120 let has_trailing_hyphen = text.ends_with('-');
121 let has_dangling_hyphen = has_double_hyphen || has_trailing_hyphen;
122 let is_every_hyphen_inner_and_single = !has_dangling_hyphen;
123 is_valid_start && has_only_id_characters && is_every_hyphen_inner_and_single
124 }
125
126 fn write_segment(formatter: &mut fmt::Formatter<'_>, segment: &str) -> fmt::Result {
128 let is_empty = segment.is_empty();
129 let has_reserved_character = segment.contains([SEPARATOR, QUOTE, ESCAPE]);
130 let needs_quotes = is_empty || has_reserved_character;
131 if needs_quotes {
132 return Self::write_quoted_segment(formatter, segment);
133 }
134 formatter.write_str(segment)
135 }
136
137 fn write_quoted_segment(formatter: &mut fmt::Formatter<'_>, segment: &str) -> fmt::Result {
139 formatter.write_char(QUOTE)?;
140 for character in segment.chars() {
141 let is_escaped = ESCAPED_CHARACTERS.contains(&character);
142 if is_escaped {
143 formatter.write_char(ESCAPE)?;
144 }
145 formatter.write_char(character)?;
146 }
147 formatter.write_char(QUOTE)
148 }
149
150 pub fn is_root(&self) -> bool {
152 self.segments.is_empty()
153 }
154
155 pub fn depth(&self) -> usize {
157 self.segments.len()
158 }
159
160 pub fn name(&self) -> Option<&str> {
162 self.segments.last().map(String::as_str)
163 }
164
165 pub fn segments(&self) -> &[String] {
167 &self.segments
168 }
169}
170
171impl fmt::Display for ElementPath {
172 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
173 for (index, segment) in self.segments.iter().enumerate() {
174 let is_after_first = index > 0;
175 if is_after_first {
176 formatter.write_char(SEPARATOR)?;
177 }
178 Self::write_segment(formatter, segment)?;
179 }
180 Ok(())
181 }
182}
183
184impl FromStr for ElementPath {
185 type Err = PathParseError;
186
187 fn from_str(text: &str) -> Result<Self, Self::Err> {
188 let is_empty = text.is_empty();
189 if is_empty {
190 return Err(PathParseError::Empty);
191 }
192 let mut parser = PathParser::new(text);
193 parser.parse_path()
194 }
195}
196
197#[derive(Debug, Clone, PartialEq, Eq, Error)]
199pub enum PathParseError {
200 #[error("an element path must have at least one segment")]
201 Empty,
202 #[error("empty segment at byte {offset}; quote it as \"\" to mean an empty id")]
203 EmptySegment { offset: usize },
204 #[error("unexpected `{character}` at byte {offset}; quote the segment to use it")]
205 UnexpectedCharacter { character: char, offset: usize },
206 #[error("unterminated quoted segment starting at byte {offset}")]
207 UnterminatedQuote { offset: usize },
208 #[error("invalid escape `\\{character}` at byte {offset}; only `\\\"` and `\\\\` are allowed")]
209 InvalidEscape { character: char, offset: usize },
210}
211
212struct PathParser<'a> {
214 characters: Peekable<CharIndices<'a>>,
215 end_offset: usize,
216}
217
218impl<'a> PathParser<'a> {
219 fn new(text: &'a str) -> Self {
221 let characters = text.char_indices().peekable();
222 let end_offset = text.len();
223 Self {
224 characters,
225 end_offset,
226 }
227 }
228
229 fn parse_path(&mut self) -> Result<ElementPath, PathParseError> {
231 let mut segments = Vec::new();
232 loop {
233 let segment = self.parse_segment()?;
234 segments.push(segment);
235 let Some((offset, character)) = self.characters.next() else {
236 return Ok(ElementPath { segments });
237 };
238 let is_unexpected = character != SEPARATOR;
239 if is_unexpected {
240 return Err(PathParseError::UnexpectedCharacter { character, offset });
241 }
242 }
243 }
244
245 fn parse_segment(&mut self) -> Result<String, PathParseError> {
247 let Some(&(opening_offset, QUOTE)) = self.characters.peek() else {
248 return self.parse_bare_segment();
249 };
250 self.characters.next();
251 self.parse_quoted_segment(opening_offset)
252 }
253
254 fn parse_quoted_segment(&mut self, opening_offset: usize) -> Result<String, PathParseError> {
256 let mut segment = String::new();
257 loop {
258 let Some((offset, character)) = self.characters.next() else {
259 return Err(PathParseError::UnterminatedQuote {
260 offset: opening_offset,
261 });
262 };
263 let is_closing_quote = character == QUOTE;
264 if is_closing_quote {
265 return Ok(segment);
266 }
267 let is_escape = character == ESCAPE;
268 if is_escape {
269 let escaped = self.parse_escaped_character(offset)?;
270 segment.push(escaped);
271 continue;
272 }
273 segment.push(character);
274 }
275 }
276
277 fn parse_escaped_character(&mut self, escape_offset: usize) -> Result<char, PathParseError> {
279 let Some((_offset, character)) = self.characters.next() else {
280 return Err(PathParseError::UnterminatedQuote {
281 offset: escape_offset,
282 });
283 };
284 let is_valid_escape = ESCAPED_CHARACTERS.contains(&character);
285 if is_valid_escape {
286 return Ok(character);
287 }
288 Err(PathParseError::InvalidEscape {
289 character,
290 offset: escape_offset,
291 })
292 }
293
294 fn parse_bare_segment(&mut self) -> Result<String, PathParseError> {
296 let start_offset = self.peek_offset();
297 let mut segment = String::new();
298 while let Some(&(offset, character)) = self.characters.peek() {
299 let is_separator = character == SEPARATOR;
300 if is_separator {
301 break;
302 }
303 let is_reserved = ESCAPED_CHARACTERS.contains(&character);
304 if is_reserved {
305 return Err(PathParseError::UnexpectedCharacter { character, offset });
306 }
307 segment.push(character);
308 self.characters.next();
309 }
310 let is_empty = segment.is_empty();
311 if is_empty {
312 return Err(PathParseError::EmptySegment {
313 offset: start_offset,
314 });
315 }
316 Ok(segment)
317 }
318
319 fn peek_offset(&mut self) -> usize {
321 let next_offset = self.characters.peek().map(|&(offset, _character)| offset);
322 next_offset.unwrap_or(self.end_offset)
323 }
324}
325
326impl Serialize for ElementPath {
327 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
328 serializer.collect_str(self)
329 }
330}
331
332impl<'de> Deserialize<'de> for ElementPath {
333 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
334 deserializer.deserialize_str(ElementPathVisitor)
335 }
336}
337
338struct ElementPathVisitor;
340
341impl Visitor<'_> for ElementPathVisitor {
342 type Value = ElementPath;
343
344 fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
345 formatter.write_str("an element path such as `backend.api` or `backend.\"v1.2\"`")
346 }
347
348 fn visit_str<E: de::Error>(self, text: &str) -> Result<ElementPath, E> {
349 text.parse().map_err(E::custom)
350 }
351}