1use std::borrow::{Borrow, Cow};
4use std::fmt::{self, Debug, Display};
5use std::ops::Deref;
6use std::path::{Component, Path, PathBuf};
7use std::str::FromStr;
8
9use ecow::EcoString;
10use thiserror::Error;
11
12#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize)]
19pub struct Id(EcoString);
20
21impl Id {
22 pub const SEPARATOR: &'static str = "/";
24}
25
26impl Id {
27 pub fn new<S: Into<EcoString>>(string: S) -> Result<Self, ParseIdError> {
42 let id = string.into();
43 Self::validate(&id)?;
44
45 Ok(Self(id))
46 }
47
48 pub fn new_from_path<P: AsRef<Path>>(path: P) -> Result<Self, ParseIdError> {
62 fn inner(path: &Path) -> Result<Id, ParseIdError> {
63 let mut id = String::new();
64
65 for component in path.components() {
66 match component {
67 Component::Normal(comp) => {
68 if let Some(comp) = comp.to_str() {
69 Id::validate_component(comp)?;
70
71 if !id.is_empty() {
72 id.push_str(Id::SEPARATOR);
73 }
74
75 id.push_str(comp);
76 } else {
77 return Err(ParseIdError::InvalidFragment);
78 }
79 }
80 _ => return Err(ParseIdError::InvalidFragment),
81 }
82 }
83
84 Ok(Id(id.into()))
85 }
86
87 inner(path.as_ref())
88 }
89
90 pub unsafe fn new_unchecked(string: EcoString) -> Self {
95 debug_assert!(Self::is_valid(&string));
96 Self(string)
97 }
98}
99
100impl Id {
101 pub fn is_valid<S: AsRef<str>>(string: S) -> bool {
113 Self::validate(string).is_ok()
114 }
115
116 fn validate<S: AsRef<str>>(string: S) -> Result<(), ParseIdError> {
117 for fragment in string.as_ref().split(Self::SEPARATOR) {
118 Self::validate_component(fragment)?;
119 }
120
121 Ok(())
122 }
123
124 pub fn is_component_valid<S: AsRef<str>>(component: S) -> bool {
135 Self::validate_component(component).is_ok()
136 }
137
138 fn validate_component<S: AsRef<str>>(component: S) -> Result<(), ParseIdError> {
140 let component = component.as_ref();
141
142 if component.is_empty() {
143 return Err(ParseIdError::Empty);
144 }
145
146 let mut chars = component.chars().peekable();
147 if !chars.next().unwrap().is_ascii_alphabetic() {
148 return Err(ParseIdError::InvalidFragment);
149 }
150
151 if chars.peek().is_some()
152 && !chars.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
153 {
154 return Err(ParseIdError::InvalidFragment);
155 }
156
157 Ok(())
158 }
159}
160
161impl Id {
162 pub fn as_str(&self) -> &str {
164 self.0.as_str()
165 }
166
167 pub fn to_inner(&self) -> EcoString {
169 self.0.clone()
170 }
171
172 pub fn name(&self) -> &str {
183 self.components()
184 .next_back()
185 .expect("id is always non-empty")
186 }
187
188 pub fn module(&self) -> &str {
199 let mut c = self.components();
200 _ = c.next_back().expect("id is always non-empty");
201 c.rest
202 }
203
204 pub fn ancestors(&self) -> Ancestors<'_> {
219 Ancestors { rest: &self.0 }
220 }
221
222 pub fn components(&self) -> Components<'_> {
237 Components { rest: &self.0 }
238 }
239
240 pub fn to_path(&self) -> Cow<'_, Path> {
242 let s = self.as_str();
243
244 if Self::SEPARATOR == std::path::MAIN_SEPARATOR_STR {
245 Cow::Borrowed(Path::new(s))
246 } else {
247 Cow::Owned(PathBuf::from(
248 s.replace(Self::SEPARATOR, std::path::MAIN_SEPARATOR_STR),
249 ))
250 }
251 }
252}
253
254impl Id {
255 pub unsafe fn push_component_unchecked<S: AsRef<str>>(&mut self, component: S) {
260 let comp = component.as_ref();
261 self.0.push_str(Self::SEPARATOR);
262 self.0.push_str(comp);
263 }
264
265 pub fn push_component<S: AsRef<str>>(&mut self, component: S) -> Result<(), ParseIdError> {
267 let comp = component.as_ref();
268 Self::validate_component(comp)?;
269
270 unsafe {
272 self.push_component_unchecked(component);
273 }
274
275 Ok(())
276 }
277
278 pub fn push_path_component<P: AsRef<Path>>(
280 &mut self,
281 component: P,
282 ) -> Result<(), ParseIdError> {
283 self.push_component(
284 component
285 .as_ref()
286 .to_str()
287 .ok_or(ParseIdError::InvalidFragment)?,
288 )
289 }
290}
291
292impl Deref for Id {
293 type Target = str;
294
295 fn deref(&self) -> &Self::Target {
296 self.0.as_str()
297 }
298}
299
300impl AsRef<str> for Id {
301 fn as_ref(&self) -> &str {
302 self.0.as_str()
303 }
304}
305
306impl Borrow<str> for Id {
307 fn borrow(&self) -> &str {
308 self.0.as_str()
309 }
310}
311
312impl FromStr for Id {
313 type Err = ParseIdError;
314
315 fn from_str(s: &str) -> Result<Self, Self::Err> {
316 Self::new(s)
317 }
318}
319
320impl Debug for Id {
321 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
322 Debug::fmt(self.as_str(), f)
323 }
324}
325
326impl Display for Id {
327 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
328 Display::fmt(self.as_str(), f)
329 }
330}
331
332impl PartialEq<str> for Id {
333 fn eq(&self, other: &str) -> bool {
334 self.as_str() == other
335 }
336}
337
338impl PartialEq<Id> for str {
339 fn eq(&self, other: &Id) -> bool {
340 self == other.as_str()
341 }
342}
343
344impl PartialEq<String> for Id {
345 fn eq(&self, other: &String) -> bool {
346 self.as_str() == other
347 }
348}
349
350impl PartialEq<Id> for String {
351 fn eq(&self, other: &Id) -> bool {
352 self == other.as_str()
353 }
354}
355
356impl PartialEq<EcoString> for Id {
357 fn eq(&self, other: &EcoString) -> bool {
358 self.as_str() == other
359 }
360}
361
362impl PartialEq<Id> for EcoString {
363 fn eq(&self, other: &Id) -> bool {
364 self == other.as_str()
365 }
366}
367
368#[derive(Debug)]
370pub struct Ancestors<'id> {
371 rest: &'id str,
372}
373
374impl<'id> Iterator for Ancestors<'id> {
375 type Item = &'id str;
376
377 fn next(&mut self) -> Option<Self::Item> {
378 let ret = self.rest;
379 self.rest = self
380 .rest
381 .rsplit_once(Id::SEPARATOR)
382 .map(|(rest, _)| rest)
383 .unwrap_or("");
384
385 if ret.is_empty() {
386 return None;
387 }
388
389 Some(ret)
390 }
391}
392
393#[derive(Debug)]
395pub struct Components<'id> {
396 rest: &'id str,
397}
398
399impl<'id> Iterator for Components<'id> {
400 type Item = &'id str;
401
402 fn next(&mut self) -> Option<Self::Item> {
403 if self.rest.is_empty() {
404 return None;
405 }
406
407 let (c, rest) = self
408 .rest
409 .split_once(Id::SEPARATOR)
410 .unwrap_or((self.rest, ""));
411 self.rest = rest;
412
413 Some(c)
414 }
415}
416
417impl<'id> DoubleEndedIterator for Components<'id> {
418 fn next_back(&mut self) -> Option<Self::Item> {
419 if self.rest.is_empty() {
420 return None;
421 }
422
423 let (rest, c) = self
424 .rest
425 .rsplit_once(Id::SEPARATOR)
426 .unwrap_or(("", self.rest));
427 self.rest = rest;
428
429 Some(c)
430 }
431}
432
433#[derive(Debug, Error)]
438pub enum ParseIdError {
439 #[error("id contained an invalid fragment")]
441 InvalidFragment,
442
443 #[error("id contained empty or no fragments")]
445 Empty,
446}
447
448#[cfg(test)]
449mod tests {
450 use super::*;
451
452 #[test]
453 fn test_ancestors() {
454 assert_eq!(
455 Id::new("a/b/c").unwrap().ancestors().collect::<Vec<_>>(),
456 ["a/b/c", "a/b", "a"]
457 );
458 }
459
460 #[test]
461 fn test_components() {
462 assert_eq!(
463 Id::new("a/b/c").unwrap().components().collect::<Vec<_>>(),
464 ["a", "b", "c"]
465 );
466 assert_eq!(
467 Id::new("a/b/c")
468 .unwrap()
469 .components()
470 .rev()
471 .collect::<Vec<_>>(),
472 ["c", "b", "a"]
473 );
474 }
475
476 #[test]
477 fn test_name() {
478 let tests = [("a/b/c", "c"), ("a/b", "b"), ("a", "a")];
479
480 for (id, name) in tests {
481 assert_eq!(Id(id.into()).name(), name);
482 }
483 }
484
485 #[test]
486 fn test_module() {
487 let tests = [("a/b/c", "a/b"), ("a/b", "a"), ("a", "")];
488
489 for (id, name) in tests {
490 assert_eq!(Id(id.into()).module(), name);
491 }
492 }
493
494 #[test]
495 fn test_str_invalid() {
496 assert!(Id::new("/a").is_err());
497 assert!(Id::new("a/").is_err());
498 assert!(Id::new("a//b").is_err());
499
500 assert!(Id::new("a ").is_err());
501 assert!(Id::new("1a").is_err());
502 assert!(Id::new("").is_err());
503 }
504}