Skip to main content

co_primitives/types/
path.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2// Copyright (C) 2026 1io BRANDGUARDIAN GmbH
3
4use core::str;
5use serde::{Deserialize, Serialize};
6use std::{borrow::Borrow, fmt::Display, ops::Deref};
7
8/// Path.
9/// Can be relative or absolute.
10#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
11#[repr(transparent)]
12pub struct Path(str);
13impl Path {
14	pub fn new(s: &str) -> Result<&Self, PathError> {
15		Self::from_str(s)
16	}
17
18	pub fn new_unchecked(s: &str) -> &Self {
19		Self::from_str_unchecked(s)
20	}
21}
22impl PathExt for Path {
23	type PathOwned = PathOwned;
24	type Path = Path;
25
26	fn validate(buf: &str) -> Result<(), PathError> {
27		if buf.is_empty() {
28			return Err(PathError::InvalidArgument);
29		}
30		Ok(())
31	}
32
33	fn from_owned_unchecked(buf: String) -> Self::PathOwned {
34		PathOwned(buf)
35	}
36
37	/// See: [`std::path::Path`]
38	fn from_str_unchecked(s: &str) -> &Self::Path {
39		unsafe { &*(s as *const str as *const Path) }
40	}
41
42	fn as_str(&self) -> &'_ str {
43		&self.0
44	}
45
46	fn has_root(&self) -> bool {
47		matches!(self.as_str().as_bytes().first(), Some(b'/'))
48	}
49}
50impl From<&Path> for String {
51	fn from(val: &Path) -> Self {
52		val.0.to_owned()
53	}
54}
55impl From<&Path> for PathOwned {
56	fn from(val: &Path) -> Self {
57		PathOwned(val.0.to_owned())
58	}
59}
60impl<'a> IntoIterator for &'a Path {
61	type Item = Component<'a>;
62	type IntoIter = Components<'a>;
63
64	fn into_iter(self) -> Self::IntoIter {
65		self.components()
66	}
67}
68impl AsRef<str> for Path {
69	fn as_ref(&self) -> &str {
70		&self.0
71	}
72}
73impl PartialEq<str> for Path {
74	fn eq(&self, other: &str) -> bool {
75		Self::from_str_unchecked(other) == self.as_path()
76	}
77}
78impl Display for Path {
79	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80		f.write_str(self.as_str())
81	}
82}
83impl ToOwned for Path {
84	type Owned = PathOwned;
85
86	fn to_owned(&self) -> Self::Owned {
87		PathOwned::from_owned_unchecked(self.as_str().to_owned())
88	}
89}
90impl PartialEq<PathOwned> for Path {
91	fn eq(&self, other: &PathOwned) -> bool {
92		other.as_path() == self.as_path()
93	}
94}
95impl AsRef<AbsolutePath> for Path {
96	fn as_ref(&self) -> &AbsolutePath {
97		AbsolutePath::from_str_unchecked(&self.0)
98	}
99}
100impl AsRef<RelativePath> for Path {
101	fn as_ref(&self) -> &RelativePath {
102		RelativePath::from_str_unchecked(&self.0)
103	}
104}
105impl AsRef<Path> for str {
106	fn as_ref(&self) -> &Path {
107		Path::from_str_unchecked(self)
108	}
109}
110impl PartialEq<AbsolutePath> for Path {
111	fn eq(&self, other: &AbsolutePath) -> bool {
112		AsRef::<AbsolutePath>::as_ref(&self) == other
113	}
114}
115impl PartialEq<RelativePath> for Path {
116	fn eq(&self, other: &RelativePath) -> bool {
117		AsRef::<RelativePath>::as_ref(&self) == other
118	}
119}
120
121/// Owned  Path.
122#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
123#[repr(transparent)]
124pub struct PathOwned(String);
125impl PathOwned {
126	pub fn new(s: String) -> Result<Self, PathError> {
127		Self::from_owned(s)
128	}
129
130	pub fn new_unchecked(s: String) -> Self {
131		Self::from_owned_unchecked(s)
132	}
133}
134impl PathExt for PathOwned {
135	type PathOwned = PathOwned;
136	type Path = Path;
137
138	fn validate(buf: &str) -> Result<(), PathError> {
139		Self::Path::validate(buf)
140	}
141
142	fn from_owned_unchecked(buf: String) -> Self::PathOwned {
143		PathOwned(buf)
144	}
145
146	fn from_str_unchecked(buf: &str) -> &Self::Path {
147		Self::Path::from_str_unchecked(buf)
148	}
149
150	fn as_str(&self) -> &'_ str {
151		&self.0
152	}
153
154	fn has_root(&self) -> bool {
155		self.as_path().has_root()
156	}
157}
158impl Deref for PathOwned {
159	type Target = Path;
160
161	fn deref(&self) -> &Self::Target {
162		Path::from_str_unchecked(&self.0)
163	}
164}
165impl AsRef<Path> for PathOwned {
166	fn as_ref(&self) -> &Path {
167		Path::from_str_unchecked(&self.0)
168	}
169}
170impl PartialEq<Path> for PathOwned {
171	fn eq(&self, other: &Path) -> bool {
172		other.as_path() == self.as_path()
173	}
174}
175impl Borrow<Path> for PathOwned {
176	fn borrow(&self) -> &Path {
177		self
178	}
179}
180impl AsRef<str> for PathOwned {
181	fn as_ref(&self) -> &str {
182		&self.0
183	}
184}
185impl PartialEq<str> for PathOwned {
186	fn eq(&self, other: &str) -> bool {
187		Self::from_str_unchecked(other) == self.as_path()
188	}
189}
190impl From<PathOwned> for String {
191	fn from(val: PathOwned) -> Self {
192		val.0
193	}
194}
195impl<'a> IntoIterator for &'a PathOwned {
196	type Item = Component<'a>;
197	type IntoIter = Components<'a>;
198
199	fn into_iter(self) -> Self::IntoIter {
200		self.components()
201	}
202}
203impl Display for PathOwned {
204	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
205		f.write_str(self.as_str())
206	}
207}
208impl From<&AbsolutePath> for PathOwned {
209	fn from(value: &AbsolutePath) -> Self {
210		Self::new_unchecked(value.to_string())
211	}
212}
213impl From<AbsolutePathOwned> for PathOwned {
214	fn from(value: AbsolutePathOwned) -> Self {
215		Self::new_unchecked(value.0)
216	}
217}
218impl From<&RelativePath> for PathOwned {
219	fn from(value: &RelativePath) -> Self {
220		Self::new_unchecked(value.to_string())
221	}
222}
223impl From<RelativePathOwned> for PathOwned {
224	fn from(value: RelativePathOwned) -> Self {
225		Self::new_unchecked(value.0)
226	}
227}
228
229/// Absolute Path.
230#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
231#[repr(transparent)]
232pub struct AbsolutePath(str);
233impl AbsolutePath {
234	pub fn new(s: &str) -> Result<&Self, PathError> {
235		Self::from_str(s)
236	}
237
238	pub fn new_unchecked(s: &str) -> &Self {
239		Self::from_str_unchecked(s)
240	}
241}
242impl PathExt for AbsolutePath {
243	type PathOwned = AbsolutePathOwned;
244	type Path = AbsolutePath;
245
246	fn validate(buf: &str) -> Result<(), PathError> {
247		if buf.is_empty() {
248			return Err(PathError::InvalidArgument);
249		}
250		if !matches!(buf.as_bytes().first(), Some(b'/')) {
251			return Err(PathError::InvalidArgument);
252		}
253		Ok(())
254	}
255
256	fn from_owned_unchecked(buf: String) -> Self::PathOwned {
257		AbsolutePathOwned(buf)
258	}
259
260	/// See: [`std::path::Path`]
261	fn from_str_unchecked(s: &str) -> &Self::Path {
262		unsafe { &*(s as *const str as *const Self::Path) }
263	}
264
265	fn as_str(&self) -> &'_ str {
266		&self.0
267	}
268
269	fn has_root(&self) -> bool {
270		true
271	}
272}
273impl From<&AbsolutePath> for String {
274	fn from(val: &AbsolutePath) -> Self {
275		val.0.to_owned()
276	}
277}
278impl From<&AbsolutePath> for AbsolutePathOwned {
279	fn from(val: &AbsolutePath) -> Self {
280		val.to_path()
281	}
282}
283impl<'a> IntoIterator for &'a AbsolutePath {
284	type Item = Component<'a>;
285	type IntoIter = Components<'a>;
286
287	fn into_iter(self) -> Self::IntoIter {
288		self.components()
289	}
290}
291impl ToOwned for AbsolutePath {
292	type Owned = AbsolutePathOwned;
293
294	fn to_owned(&self) -> Self::Owned {
295		AbsolutePathOwned::from_owned_unchecked(self.as_str().to_owned())
296	}
297}
298impl AsRef<str> for AbsolutePath {
299	fn as_ref(&self) -> &str {
300		self.as_str()
301	}
302}
303impl PartialEq<str> for AbsolutePath {
304	fn eq(&self, other: &str) -> bool {
305		Self::from_str_unchecked(other) == self.as_path()
306	}
307}
308impl Display for AbsolutePath {
309	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
310		f.write_str(self.as_str())
311	}
312}
313impl PartialEq<AbsolutePathOwned> for AbsolutePath {
314	fn eq(&self, other: &AbsolutePathOwned) -> bool {
315		other.as_path() == self.as_path()
316	}
317}
318impl AsRef<AbsolutePath> for str {
319	fn as_ref(&self) -> &AbsolutePath {
320		AbsolutePath::from_str_unchecked(self)
321	}
322}
323
324/// Owned Absolute Path.
325#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
326#[repr(transparent)]
327pub struct AbsolutePathOwned(String);
328impl AbsolutePathOwned {
329	pub fn new(s: String) -> Result<Self, PathError> {
330		Self::from_owned(s)
331	}
332
333	pub fn new_unchecked(s: String) -> Self {
334		Self::from_owned_unchecked(s)
335	}
336}
337impl PathExt for AbsolutePathOwned {
338	type PathOwned = AbsolutePathOwned;
339	type Path = AbsolutePath;
340
341	fn validate(buf: &str) -> Result<(), PathError> {
342		Self::Path::validate(buf)
343	}
344
345	fn from_owned_unchecked(buf: String) -> Self::PathOwned {
346		AbsolutePathOwned(buf)
347	}
348
349	/// See: [`std::path::Path`]
350	fn from_str_unchecked(buf: &str) -> &Self::Path {
351		Self::Path::from_str_unchecked(buf)
352	}
353
354	fn as_str(&self) -> &'_ str {
355		&self.0
356	}
357
358	fn has_root(&self) -> bool {
359		self.as_path().has_root()
360	}
361}
362impl Deref for AbsolutePathOwned {
363	type Target = AbsolutePath;
364
365	fn deref(&self) -> &Self::Target {
366		AbsolutePath::from_str_unchecked(&self.0)
367	}
368}
369impl AsRef<AbsolutePath> for AbsolutePathOwned {
370	fn as_ref(&self) -> &AbsolutePath {
371		AbsolutePath::from_str_unchecked(&self.0)
372	}
373}
374impl PartialEq<AbsolutePath> for AbsolutePathOwned {
375	fn eq(&self, other: &AbsolutePath) -> bool {
376		self.0 == other.0
377	}
378}
379impl Borrow<AbsolutePath> for AbsolutePathOwned {
380	fn borrow(&self) -> &AbsolutePath {
381		self
382	}
383}
384impl TryFrom<String> for AbsolutePathOwned {
385	type Error = PathError;
386
387	fn try_from(value: String) -> Result<Self, Self::Error> {
388		Self::from_owned(value)
389	}
390}
391impl TryFrom<&str> for AbsolutePathOwned {
392	type Error = PathError;
393
394	fn try_from(value: &str) -> Result<Self, Self::Error> {
395		Self::from_str(value).map(|s| s.to_owned())
396	}
397}
398impl From<AbsolutePathOwned> for String {
399	fn from(val: AbsolutePathOwned) -> Self {
400		val.0
401	}
402}
403impl<'a> IntoIterator for &'a AbsolutePathOwned {
404	type Item = Component<'a>;
405	type IntoIter = Components<'a>;
406
407	fn into_iter(self) -> Self::IntoIter {
408		self.components()
409	}
410}
411impl AsRef<str> for AbsolutePathOwned {
412	fn as_ref(&self) -> &str {
413		&self.0
414	}
415}
416impl PartialEq<str> for AbsolutePathOwned {
417	fn eq(&self, other: &str) -> bool {
418		Self::from_str_unchecked(other) == self.as_path()
419	}
420}
421impl Display for AbsolutePathOwned {
422	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
423		f.write_str(self.as_str())
424	}
425}
426
427/// Relative Path.
428#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
429#[repr(transparent)]
430pub struct RelativePath(str);
431impl PathExt for RelativePath {
432	type PathOwned = RelativePathOwned;
433	type Path = RelativePath;
434
435	fn validate(buf: &str) -> Result<(), PathError> {
436		if buf.is_empty() {
437			return Err(PathError::InvalidArgument);
438		}
439		if matches!(buf.as_bytes().first(), Some(b'/')) {
440			return Err(PathError::InvalidArgument);
441		}
442		Ok(())
443	}
444
445	fn from_owned_unchecked(buf: String) -> Self::PathOwned {
446		RelativePathOwned(buf)
447	}
448
449	/// See: [`std::path::Path`]
450	fn from_str_unchecked(s: &str) -> &Self::Path {
451		unsafe { &*(s as *const str as *const Self::Path) }
452	}
453
454	fn as_str(&self) -> &'_ str {
455		&self.0
456	}
457
458	fn has_root(&self) -> bool {
459		false
460	}
461}
462impl From<&RelativePath> for String {
463	fn from(val: &RelativePath) -> Self {
464		val.0.to_owned()
465	}
466}
467impl From<&RelativePath> for RelativePathOwned {
468	fn from(val: &RelativePath) -> Self {
469		val.to_path()
470	}
471}
472impl<'a> IntoIterator for &'a RelativePath {
473	type Item = Component<'a>;
474	type IntoIter = Components<'a>;
475
476	fn into_iter(self) -> Self::IntoIter {
477		self.components()
478	}
479}
480impl AsRef<str> for RelativePath {
481	fn as_ref(&self) -> &str {
482		&self.0
483	}
484}
485impl PartialEq<str> for RelativePath {
486	fn eq(&self, other: &str) -> bool {
487		Self::from_str_unchecked(other) == self.as_path()
488	}
489}
490impl Display for RelativePath {
491	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
492		f.write_str(self.as_str())
493	}
494}
495impl ToOwned for RelativePath {
496	type Owned = RelativePathOwned;
497
498	fn to_owned(&self) -> Self::Owned {
499		RelativePathOwned::from_owned_unchecked(self.as_str().to_owned())
500	}
501}
502impl PartialEq<RelativePathOwned> for RelativePath {
503	fn eq(&self, other: &RelativePathOwned) -> bool {
504		other.as_path() == self.as_path()
505	}
506}
507impl AsRef<RelativePath> for str {
508	fn as_ref(&self) -> &RelativePath {
509		RelativePath::from_str_unchecked(self)
510	}
511}
512
513/// OWned Relative Path.
514#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
515#[repr(transparent)]
516pub struct RelativePathOwned(String);
517impl PathExt for RelativePathOwned {
518	type PathOwned = RelativePathOwned;
519	type Path = RelativePath;
520
521	fn validate(buf: &str) -> Result<(), PathError> {
522		Self::Path::validate(buf)
523	}
524
525	fn from_owned_unchecked(buf: String) -> Self::PathOwned {
526		RelativePathOwned(buf)
527	}
528
529	fn from_str_unchecked(buf: &str) -> &Self::Path {
530		Self::Path::from_str_unchecked(buf)
531	}
532
533	fn as_str(&self) -> &'_ str {
534		&self.0
535	}
536
537	fn has_root(&self) -> bool {
538		self.as_path().has_root()
539	}
540}
541impl Deref for RelativePathOwned {
542	type Target = RelativePath;
543
544	fn deref(&self) -> &Self::Target {
545		RelativePath::from_str_unchecked(&self.0)
546	}
547}
548impl AsRef<RelativePath> for RelativePathOwned {
549	fn as_ref(&self) -> &RelativePath {
550		RelativePath::from_str_unchecked(&self.0)
551	}
552}
553impl PartialEq<RelativePath> for RelativePathOwned {
554	fn eq(&self, other: &RelativePath) -> bool {
555		other.as_path() == self.as_path()
556	}
557}
558impl Borrow<RelativePath> for RelativePathOwned {
559	fn borrow(&self) -> &RelativePath {
560		self
561	}
562}
563impl From<RelativePathOwned> for String {
564	fn from(val: RelativePathOwned) -> Self {
565		val.0
566	}
567}
568impl<'a> IntoIterator for &'a RelativePathOwned {
569	type Item = Component<'a>;
570	type IntoIter = Components<'a>;
571
572	fn into_iter(self) -> Self::IntoIter {
573		self.components()
574	}
575}
576impl AsRef<str> for RelativePathOwned {
577	fn as_ref(&self) -> &str {
578		&self.0
579	}
580}
581impl PartialEq<str> for RelativePathOwned {
582	fn eq(&self, other: &str) -> bool {
583		Self::from_str_unchecked(other) == self.as_path()
584	}
585}
586impl Display for RelativePathOwned {
587	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
588		f.write_str(self.as_str())
589	}
590}
591
592/// Path components.
593pub struct Components<'a> {
594	path: &'a str,
595	has_root: bool,
596}
597impl<'a> Components<'a> {
598	fn parse_single_component<'b>(&self, comp: &'b str) -> Option<Component<'b>> {
599		match comp {
600			"." => Some(Component::CurDir),
601			".." => Some(Component::ParentDir),
602			"" if self.has_root => Some(Component::RootDir),
603			"" if !self.path.is_empty() => Some(Component::CurDir), // empty dir: `hello//world`
604			"" => None,
605			_ => Some(Component::Normal(comp)),
606		}
607	}
608
609	fn parse_next_component(&self) -> (usize, Option<Component<'a>>) {
610		let (extra, comp) = match self.path.as_bytes().iter().position(|b| is_sep_byte(*b)) {
611			None => (0, self.path),
612			Some(i) => (1, &self.path[..i]),
613		};
614		(comp.len() + extra, self.parse_single_component(comp))
615	}
616
617	/// Convert into vector of all names elements.
618	pub fn into_vec_normal(self) -> Vec<String> {
619		self.filter_map(|component| match component {
620			Component::Normal(name) => Some(name.to_owned()),
621			_ => None,
622		})
623		.collect()
624	}
625}
626impl<'a> Iterator for Components<'a> {
627	type Item = Component<'a>;
628
629	fn next(&mut self) -> Option<Self::Item> {
630		let (index, comp) = self.parse_next_component();
631		self.path = &self.path[index..];
632		self.has_root = false;
633		comp
634	}
635}
636
637/// Path component.
638#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
639pub enum Component<'a> {
640	RootDir,
641	CurDir,
642	ParentDir,
643	Normal(&'a str),
644}
645impl<'a> Component<'a> {
646	pub fn as_string(&self) -> &'a str {
647		match self {
648			Component::RootDir => "/",
649			Component::CurDir => ".",
650			Component::ParentDir => "..",
651			Component::Normal(s) => s,
652		}
653	}
654
655	/// Test if component is empty.
656	pub fn is_empty(&self) -> bool {
657		matches!(self, Component::Normal(s) if s.len() == 0)
658	}
659
660	/// Actual length of hte component (without separators).
661	pub fn len(&self) -> usize {
662		match self {
663			Component::RootDir => 1,
664			Component::CurDir => 1,
665			Component::ParentDir => 2,
666			Component::Normal(s) => s.len(),
667		}
668	}
669}
670
671#[derive(Debug, thiserror::Error)]
672pub enum PathError {
673	#[error("No root")]
674	NoRoot,
675
676	#[error("No parent: {0}")]
677	NoParent(PathOwned),
678
679	#[error("Invalid argument")]
680	InvalidArgument,
681}
682
683pub trait PathExt {
684	type PathOwned: PathExt;
685	type Path: PathExt + ?Sized;
686
687	fn validate(buf: &str) -> Result<(), PathError>;
688	fn from_owned_unchecked(buf: String) -> Self::PathOwned;
689	fn from_str_unchecked(buf: &str) -> &Self::Path;
690
691	fn from_owned(buf: String) -> Result<Self::PathOwned, PathError> {
692		Self::validate(&buf)?;
693		Ok(Self::from_owned_unchecked(buf))
694	}
695
696	fn from_str(buf: &str) -> Result<&Self::Path, PathError> {
697		Self::validate(buf)?;
698		Ok(Self::from_str_unchecked(buf))
699	}
700
701	fn as_path(&self) -> &Self::Path {
702		Self::from_str_unchecked(self.as_str())
703	}
704
705	fn to_path(&self) -> Self::PathOwned {
706		Self::from_owned_unchecked(self.as_str().to_owned())
707	}
708
709	fn as_str(&self) -> &str;
710
711	fn has_root(&self) -> bool;
712
713	/// Path components.
714	fn components(&self) -> Components<'_> {
715		Components { path: self.as_str(), has_root: self.has_root() }
716	}
717
718	/// Parent directory.
719	fn parent(&self) -> Option<&Self::Path> {
720		self.parent_and_file_name().map(|(p, _)| p)
721	}
722
723	/// Parent directory.
724	fn parent_result(&self) -> Result<&Self::Path, PathError> {
725		self.parent()
726			.ok_or_else(|| PathError::NoParent(PathOwned::new_unchecked(self.as_str().to_owned())))
727	}
728
729	/// Parent directories as full path starting at root.
730	///
731	/// Example:
732	/// ```rust
733	/// use co_primitives::{Path, PathExt};
734	/// let path = Path::from_str_unchecked("/hello/world/test.zip");
735	/// let mut parents = path.parents();
736	/// assert_eq!(Some(Path::from_str_unchecked("/")), parents.next());
737	/// assert_eq!(Some(Path::from_str_unchecked("/hello")), parents.next());
738	/// assert_eq!(Some(Path::from_str_unchecked("/hello/world")), parents.next());
739	/// assert_eq!(None, parents.next());
740	/// ```
741	fn parents(&self) -> impl Iterator<Item = &Self::Path> {
742		self.paths().take_while(|path| path.as_str().len() < self.as_str().len())
743	}
744
745	/// Components as full path starting at root.
746	///
747	/// Example:
748	/// ```rust
749	/// use co_primitives::{Path, PathExt};
750	/// let path = Path::from_str_unchecked("/hello/world/test.zip");
751	/// let mut paths = path.paths();
752	/// assert_eq!(Some(Path::from_str_unchecked("/")), paths.next());
753	/// assert_eq!(Some(Path::from_str_unchecked("/hello")), paths.next());
754	/// assert_eq!(Some(Path::from_str_unchecked("/hello/world")), paths.next());
755	/// assert_eq!(Some(Path::from_str_unchecked("/hello/world/test.zip")), paths.next());
756	/// assert_eq!(None, paths.next());
757	/// ```
758	fn paths(&self) -> impl Iterator<Item = &Self::Path> {
759		self.components().scan((0_usize, self.as_str()), |(index, path), component| {
760			let end = *index + component.len();
761			let result = &path[0..end];
762			*index = if *index > 0 { end + 1 } else { end };
763			Some(Self::from_str_unchecked(result))
764		})
765	}
766
767	/// Path and filename.
768	fn parent_and_file_name(&self) -> Option<(&Self::Path, &'_ str)> {
769		match self.components().last() {
770			Some(Component::Normal(name)) => {
771				let path = self.as_str();
772				let parent = match path.split_at(path.len() - name.len()) {
773					("/", _) => "/",
774					(p, _) if p.len() > 1 => &p[0..p.len() - 1],
775					(p, _) => p,
776				};
777				Some((Self::from_str_unchecked(parent), name))
778			},
779			_ => None,
780		}
781	}
782
783	/// Path and filename.
784	fn parent_and_file_name_result(&self) -> Result<(&Self::Path, &str), PathError> {
785		self.parent_and_file_name()
786			.ok_or_else(|| PathError::NoParent(PathOwned::new_unchecked(self.as_str().to_owned())))
787	}
788
789	/// File name.
790	fn file_name(&self) -> Option<&str> {
791		self.parent_and_file_name().map(|(_, f)| f)
792	}
793
794	/// File name.
795	fn file_name_result(&self) -> Result<&str, PathError> {
796		self.file_name()
797			.ok_or_else(|| PathError::NoParent(PathOwned::new_unchecked(self.as_str().to_owned())))
798	}
799
800	/// Normalize path to connonized form.
801	fn normalize(&self) -> Result<Self::PathOwned, PathError> {
802		Ok(Self::from_owned_unchecked(from_components(normalize_components(self.components())?)))
803	}
804
805	/// Join and normalize components into an path.
806	fn join<'a: 'b, 'b>(
807		&'a self,
808		other: impl IntoIterator<Item = Component<'b>>,
809	) -> Result<Self::PathOwned, PathError> {
810		Ok(Self::from_owned_unchecked(join(self.components(), other)?))
811	}
812
813	/// Join and normalize other path.
814	fn join_path(&self, other: &str) -> Result<Self::PathOwned, PathError> {
815		let other_path = Path::from_str_unchecked(other);
816		self.join(other_path)
817	}
818}
819
820fn join<'a: 'b, 'b: 'a>(
821	a: impl IntoIterator<Item = Component<'a>>,
822	b: impl IntoIterator<Item = Component<'b>>,
823) -> Result<String, PathError> {
824	let components = a.into_iter().chain(b);
825	let normalized = normalize_components(components)?;
826	Ok(from_components(normalized))
827}
828
829fn normalize_components<'a>(
830	components: impl IntoIterator<Item = Component<'a>>,
831) -> Result<Vec<Component<'a>>, PathError> {
832	let mut stack: Vec<_> = components.into_iter().filter(|c| !matches!(c, Component::CurDir)).collect();
833	let mut index = 0;
834	while index < stack.len() {
835		match stack[index] {
836			Component::CurDir => {
837				// remove
838				stack.remove(index);
839			},
840			Component::RootDir => {
841				// remove all elements before index
842				for _ in 0..index {
843					stack.remove(0);
844				}
845
846				// continue with elements after root
847				index = 1;
848			},
849			Component::ParentDir => {
850				if index > 0 {
851					// check component before
852					match stack[index - 1] {
853						// fail of we go beyound root
854						Component::RootDir => return Err(PathError::NoRoot),
855						// keep parent dir if previous is also an parent dir
856						Component::ParentDir => {
857							index += 1;
858							continue;
859						},
860						_ => {},
861					}
862
863					// remove dir and parentdir
864					stack.remove(index - 1);
865					stack.remove(index - 1);
866
867					// continue with next element
868					index -= 1;
869				} else {
870					// keep parent (..) when on start
871					index += 1;
872				}
873			},
874			_ => {
875				// keep
876				index += 1;
877			},
878		}
879	}
880	Ok(stack)
881}
882
883fn from_components<'a>(components: impl IntoIterator<Item = Component<'a>>) -> String {
884	let result: String = components
885		.into_iter()
886		.scan(false, |state, c| {
887			let result_state = *state;
888			match &c {
889				Component::RootDir => {
890					*state = false;
891				},
892				_ => {
893					*state = true;
894				},
895			}
896			Some((result_state, c))
897		})
898		.flat_map(|(separator, c)| match separator {
899			false => ["", c.as_string()],
900			true => ["/", c.as_string()],
901		})
902		.collect();
903	result
904}
905
906#[inline]
907pub fn is_sep_byte(b: u8) -> bool {
908	b == b'/'
909}
910
911#[cfg(test)]
912mod tests {
913	use crate::{AbsolutePath, Component, Path, PathExt, RelativePath};
914
915	#[test]
916	fn test_components() {
917		let path = Path::from_str("/hello/world").unwrap();
918		let mut components = path.components();
919		assert_eq!(Some(Component::RootDir), components.next());
920		assert_eq!(Some(Component::Normal("hello")), components.next());
921		assert_eq!(Some(Component::Normal("world")), components.next());
922		assert_eq!(None, components.next());
923	}
924
925	#[test]
926	fn test_components_empty_component() {
927		let path = Path::from_str("/hello//world").unwrap();
928		let mut components = path.components();
929		assert_eq!(Some(Component::RootDir), components.next());
930		assert_eq!(Some(Component::Normal("hello")), components.next());
931		assert_eq!(Some(Component::CurDir), components.next());
932		assert_eq!(Some(Component::Normal("world")), components.next());
933		assert_eq!(None, components.next());
934	}
935
936	#[test]
937	fn test_components_empty() {
938		let path = Path::from_owned_unchecked("".to_owned());
939		let mut components = path.components();
940		assert_eq!(None, components.next());
941	}
942
943	#[test]
944	fn test_relative_components() {
945		let path = RelativePath::from_str("./hello/world").unwrap();
946		let mut components = path.components();
947		assert_eq!(Some(Component::CurDir), components.next());
948		assert_eq!(Some(Component::Normal("hello")), components.next());
949		assert_eq!(Some(Component::Normal("world")), components.next());
950		assert_eq!(None, components.next());
951	}
952
953	#[test]
954	fn test_absolute_components() {
955		let path = AbsolutePath::from_str("/hello/world").unwrap();
956		let mut components = path.components();
957		assert_eq!(Some(Component::RootDir), components.next());
958		assert_eq!(Some(Component::Normal("hello")), components.next());
959		assert_eq!(Some(Component::Normal("world")), components.next());
960		assert_eq!(None, components.next());
961	}
962
963	#[test]
964	fn test_parents() {
965		let path = Path::from_str_unchecked("/hello/world/test.zip");
966		let mut parents = path.parents();
967		assert_eq!(Some(Path::from_str_unchecked("/")), parents.next());
968		assert_eq!(Some(Path::from_str_unchecked("/hello")), parents.next());
969		assert_eq!(Some(Path::from_str_unchecked("/hello/world")), parents.next());
970		assert_eq!(None, parents.next());
971	}
972
973	#[test]
974	fn test_paths() {
975		let path = Path::from_str_unchecked("/hello/world/test.zip");
976		let mut paths = path.paths();
977		assert_eq!(Some(Path::from_str_unchecked("/")), paths.next());
978		assert_eq!(Some(Path::from_str_unchecked("/hello")), paths.next());
979		assert_eq!(Some(Path::from_str_unchecked("/hello/world")), paths.next());
980		assert_eq!(Some(Path::from_str_unchecked("/hello/world/test.zip")), paths.next());
981		assert_eq!(None, paths.next());
982	}
983
984	#[test]
985	fn test_normalize() {
986		fn normalize(s: &str) -> String {
987			Path::from_str(s).unwrap().normalize().unwrap().into()
988		}
989		assert_eq!("/hello/test", normalize("/hello/test"));
990		assert_eq!("test", normalize("hello/.././test"));
991		assert_eq!("/test/hello", normalize("/test//hello"));
992		assert_eq!("../test", normalize("../test"));
993		assert_eq!("/", normalize("/"));
994		assert_eq!("/", normalize("//"));
995		assert_eq!("/test", normalize("/test/"));
996		assert_eq!("/test", normalize("/test//"));
997		assert_eq!("../test", normalize("./../test"));
998		assert_eq!("../../test", normalize("./../../test"));
999		assert_eq!("../../../test", normalize("./../../../test"));
1000	}
1001
1002	#[test]
1003	fn test_file_name() {
1004		assert_eq!(Some("test"), Path::from_str("/hello/test").unwrap().file_name());
1005		assert_eq!(Some("test.zip"), Path::from_str("hello/.././test.zip").unwrap().file_name());
1006		assert_eq!(None, Path::from_str("hello/.././test.zip/..").unwrap().file_name());
1007		assert_eq!(None, Path::from_str("/").unwrap().file_name());
1008	}
1009
1010	#[test]
1011	fn test_parent() {
1012		assert_eq!(Some(Path::from_str_unchecked("/hello")), Path::from_str("/hello/test").unwrap().parent());
1013		assert_eq!(Some(Path::from_str_unchecked("/")), Path::from_str("/hello").unwrap().parent());
1014		assert_eq!(None, Path::from_str("/").unwrap().parent());
1015	}
1016
1017	#[test]
1018	fn test_parent_and_file_name() {
1019		assert_eq!(
1020			Some((Path::from_str_unchecked("/hello"), "test")),
1021			Path::from_str("/hello/test").unwrap().parent_and_file_name()
1022		);
1023		assert_eq!(
1024			Some((Path::from_str_unchecked("hello/../."), "test.zip")),
1025			Path::from_str("hello/.././test.zip").unwrap().parent_and_file_name()
1026		);
1027		assert_eq!(None, Path::from_str("hello/.././test.zip/..").unwrap().parent_and_file_name());
1028		assert_eq!(None, Path::from_str("/").unwrap().parent_and_file_name());
1029		assert_eq!(
1030			Some((Path::from_str_unchecked("/"), "test")),
1031			Path::from_str("/test").unwrap().parent_and_file_name()
1032		);
1033	}
1034
1035	#[test]
1036	fn test_join() {
1037		assert_eq!(
1038			"/hello/test/world",
1039			Path::from_str("/hello/test")
1040				.unwrap()
1041				.join(Path::from_str("world").unwrap())
1042				.unwrap()
1043				.as_str()
1044		);
1045		assert_eq!(
1046			"/world",
1047			Path::from_str("/hello/test")
1048				.unwrap()
1049				.join(Path::from_str("/world").unwrap())
1050				.unwrap()
1051				.as_str()
1052		);
1053	}
1054}