Skip to main content

miden_assembly_syntax/ast/path/
path.rs

1use alloc::{
2    borrow::{Borrow, Cow, ToOwned},
3    string::ToString,
4};
5use core::fmt;
6
7use super::{Iter, Join, PathBuf, PathComponent, PathError, StartsWith};
8use crate::ast::Ident;
9
10/// A borrowed reference to a subset of a path, e.g. another [Path] or a [PathBuf]
11#[derive(PartialEq, Eq, PartialOrd, Ord, Hash)]
12#[repr(transparent)]
13pub struct Path {
14    /// A view into the selected components of the path, i.e. the parts delimited by `::`
15    inner: str,
16}
17
18impl fmt::Debug for Path {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        fmt::Debug::fmt(&self.inner, f)
21    }
22}
23
24#[cfg(feature = "serde")]
25impl serde::Serialize for Path {
26    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
27    where
28        S: serde::Serializer,
29    {
30        serializer.serialize_str(&self.inner)
31    }
32}
33
34#[cfg(feature = "serde")]
35impl<'de> serde::Deserialize<'de> for &'de Path {
36    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
37    where
38        D: serde::Deserializer<'de>,
39    {
40        use serde::de::Visitor;
41
42        struct PathVisitor;
43
44        impl<'de> Visitor<'de> for PathVisitor {
45            type Value = &'de Path;
46
47            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
48                formatter.write_str("a borrowed Path")
49            }
50
51            fn visit_borrowed_str<E>(self, v: &'de str) -> Result<Self::Value, E>
52            where
53                E: serde::de::Error,
54            {
55                Path::validate(v).map_err(serde::de::Error::custom)
56            }
57        }
58
59        deserializer.deserialize_any(PathVisitor)
60    }
61}
62
63impl ToOwned for Path {
64    type Owned = PathBuf;
65    #[inline]
66    fn to_owned(&self) -> PathBuf {
67        self.to_path_buf()
68    }
69    #[inline]
70    fn clone_into(&self, target: &mut Self::Owned) {
71        self.inner.clone_into(&mut target.inner)
72    }
73}
74
75impl Borrow<Path> for PathBuf {
76    fn borrow(&self) -> &Path {
77        Path::new(self)
78    }
79}
80
81impl AsRef<str> for Path {
82    #[inline]
83    fn as_ref(&self) -> &str {
84        &self.inner
85    }
86}
87
88impl AsRef<Path> for str {
89    #[inline(always)]
90    fn as_ref(&self) -> &Path {
91        unsafe { &*(self as *const str as *const Path) }
92    }
93}
94
95impl AsRef<Path> for Ident {
96    #[inline(always)]
97    fn as_ref(&self) -> &Path {
98        self.as_str().as_ref()
99    }
100}
101
102impl AsRef<Path> for crate::ast::ProcedureName {
103    #[inline(always)]
104    fn as_ref(&self) -> &Path {
105        let ident: &Ident = self.as_ref();
106        ident.as_str().as_ref()
107    }
108}
109
110impl AsRef<Path> for crate::ast::QualifiedProcedureName {
111    #[inline(always)]
112    fn as_ref(&self) -> &Path {
113        self.as_path()
114    }
115}
116
117impl AsRef<Path> for Path {
118    #[inline(always)]
119    fn as_ref(&self) -> &Path {
120        self
121    }
122}
123
124impl From<&Path> for alloc::sync::Arc<Path> {
125    fn from(path: &Path) -> Self {
126        path.to_path_buf().into()
127    }
128}
129
130/// Conversions
131impl Path {
132    /// Path components must be no larger than the maximum valid path length, which is u16::MAX
133    /// bytes, minus 2 bytes for the optional absolute path prefix `::`.
134    ///
135    /// While path components of this size are unlikely, it keeps the `Path` API consistent in cases
136    /// where a path is long simply due to a single component.
137    pub const MAX_COMPONENT_LENGTH: usize = (u16::MAX as usize) - 2;
138
139    /// An empty path for use as a default value, placeholder, comparisons, etc.
140    pub const EMPTY: &Path = unsafe { &*("" as *const str as *const Path) };
141
142    /// Base kernel path.
143    pub const KERNEL_PATH: &str = "$kernel";
144    pub const ABSOLUTE_KERNEL_PATH: &str = "::$kernel";
145    pub const KERNEL: &Path =
146        unsafe { &*(Self::ABSOLUTE_KERNEL_PATH as *const str as *const Path) };
147
148    /// Path for an executable module.
149    pub const EXEC_PATH: &str = "$exec";
150    pub const ABSOLUTE_EXEC_PATH: &str = "::$exec";
151    pub const EXEC: &Path = unsafe { &*(Self::ABSOLUTE_EXEC_PATH as *const str as *const Path) };
152
153    pub fn new<S: AsRef<str> + ?Sized>(path: &S) -> &Path {
154        // SAFETY: The representation of Path is equivalent to str
155        unsafe { &*(path.as_ref() as *const str as *const Path) }
156    }
157
158    pub fn from_mut(path: &mut str) -> &mut Path {
159        // SAFETY: The representation of Path is equivalent to str
160        unsafe { &mut *(path as *mut str as *mut Path) }
161    }
162
163    /// Verify that `path` meets all the requirements for a valid [Path]
164    pub fn validate(path: &str) -> Result<&Path, PathError> {
165        match path {
166            "" | "\"\"" => return Err(PathError::Empty),
167            "::" => return Err(PathError::EmptyComponent),
168            _ => (),
169        }
170
171        if path.len() > u16::MAX as usize {
172            return Err(PathError::TooLong { max: u16::MAX as usize });
173        }
174
175        for result in Iter::new(path) {
176            result?;
177        }
178
179        Ok(Path::new(path))
180    }
181
182    /// Get a [Path] corresponding to [Self::KERNEL_PATH]
183    pub const fn kernel_path() -> &'static Path {
184        Path::KERNEL
185    }
186
187    /// Get a [Path] corresponding to [Self::EXEC_PATH]
188    pub const fn exec_path() -> &'static Path {
189        Path::EXEC
190    }
191
192    #[inline]
193    pub const fn as_str(&self) -> &str {
194        &self.inner
195    }
196
197    #[inline]
198    pub fn as_mut_str(&mut self) -> &mut str {
199        &mut self.inner
200    }
201
202    /// Get an [Ident] that is equivalent to this [Path], so long as the path has only a single
203    /// component.
204    ///
205    /// Returns `None` if the path cannot be losslessly represented as a single component.
206    pub fn as_ident(&self) -> Option<Ident> {
207        let mut components = self.components().filter_map(Result::ok);
208        match components.next()? {
209            component @ PathComponent::Normal(_) => {
210                if components.next().is_none() {
211                    component.to_ident()
212                } else {
213                    None
214                }
215            },
216            PathComponent::Root => None,
217        }
218    }
219
220    /// Convert this [Path] to an owned [PathBuf]
221    pub fn to_path_buf(&self) -> PathBuf {
222        PathBuf { inner: self.inner.to_string() }
223    }
224
225    /// Convert an [Ident] to an equivalent [Path] or [PathBuf], depending on whether the identifier
226    /// would require quoting as a path.
227    pub fn from_ident(ident: &Ident) -> Cow<'_, Path> {
228        let ident = ident.as_str();
229        if Ident::requires_quoting(ident) {
230            let mut buf = PathBuf::with_capacity(ident.len() + 2);
231            buf.push_component(ident);
232            Cow::Owned(buf)
233        } else {
234            Cow::Borrowed(Path::new(ident))
235        }
236    }
237}
238
239/// Accesssors
240impl Path {
241    /// Returns true if this path is empty (i.e. has no components)
242    pub fn is_empty(&self) -> bool {
243        matches!(&self.inner, "" | "::" | "\"\"")
244    }
245
246    /// Returns the number of components in the path
247    pub fn len(&self) -> usize {
248        self.components().count()
249    }
250
251    /// Return the size of the path in [char]s when displayed as a string
252    pub fn char_len(&self) -> usize {
253        self.inner.chars().count()
254    }
255
256    /// Return the size of the path in bytes when displayed as a string
257    #[inline]
258    pub fn byte_len(&self) -> usize {
259        self.inner.len()
260    }
261
262    /// Returns true if this path is an absolute path
263    pub fn is_absolute(&self) -> bool {
264        matches!(self.components().next(), Some(Ok(PathComponent::Root)))
265    }
266
267    /// Make this path absolute, if not already
268    ///
269    /// NOTE: This does not _resolve_ the path, it simply ensures the path has the root prefix
270    ///
271    /// # Errors
272    ///
273    /// Returns an error if the path contains invalid components (e.g., identifiers with
274    /// invalid characters or exceeding maximum length).
275    pub fn to_absolute(&self) -> Result<Cow<'_, Path>, PathError> {
276        if self.is_absolute() {
277            for component in self.components() {
278                component?;
279            }
280            if self.byte_len() > u16::MAX as usize {
281                return Err(PathError::TooLong { max: u16::MAX as usize });
282            }
283            Ok(Cow::Borrowed(self))
284        } else {
285            let mut buf = PathBuf::with_capacity(self.byte_len() + 2);
286            buf.push_component("::");
287            buf.extend_with_components(self.components())?;
288            if buf.byte_len() > u16::MAX as usize {
289                return Err(PathError::TooLong { max: u16::MAX as usize });
290            }
291            Ok(Cow::Owned(buf))
292        }
293    }
294
295    /// Strip the root prefix from this path, if it has one.
296    pub fn to_relative(&self) -> &Path {
297        match self.inner.strip_prefix("::") {
298            Some(rest) => Path::new(rest),
299            None => self,
300        }
301    }
302
303    /// Returns the [Path] without its final component, if there is one.
304    ///
305    /// This means it may return an empty [Path] for relative paths with a single component.
306    ///
307    /// Returns `None` if the path terminates with the root prefix, or if it is empty.
308    pub fn parent(&self) -> Option<&Path> {
309        let mut components = self.components();
310        match components.next_back()?.ok()? {
311            PathComponent::Root => None,
312            _ => Some(components.as_path()),
313        }
314    }
315
316    /// Returns an iterator over all components of the path.
317    pub fn components(&self) -> Iter<'_> {
318        Iter::new(&self.inner)
319    }
320
321    /// Get the first non-root component of this path as a `str`
322    ///
323    /// Returns `None` if the path is empty, or consists only of the root prefix.
324    pub fn first(&self) -> Option<&str> {
325        self.split_first().map(|(first, _)| first)
326    }
327
328    /// Get the last non-root component of this path as a `str`
329    ///
330    /// Returns `None` if the path is empty, or consists only of the root prefix.
331    pub fn last(&self) -> Option<&str> {
332        self.split_last().map(|(last, _)| last)
333    }
334
335    /// Get the last non-root component of this path as a [crate::ast::ProcedureName]
336    ///
337    /// Returns `Ok(None)` if the path is empty, or consists only of the root prefix.
338    pub fn procedure_name(&self) -> Result<Option<crate::ast::ProcedureName>, PathError> {
339        use crate::ast::ProcedureName;
340
341        let Some(last) = self.components().next_back() else {
342            return Ok(None);
343        };
344        let name = last?;
345        if name == PathComponent::Root {
346            return Ok(None);
347        }
348        if name.is_quoted() {
349            ProcedureName::new(format!("\"{}\"", name.as_str()))
350                .map(Some)
351                .map_err(PathError::InvalidComponent)
352        } else {
353            ProcedureName::new(name.as_str()).map(Some).map_err(PathError::InvalidComponent)
354        }
355    }
356
357    /// Splits this path on the first non-root component, returning it and a new [Path] of the
358    /// remaining components.
359    ///
360    /// Returns `None` if there are no components to split
361    pub fn split_first(&self) -> Option<(&str, &Path)> {
362        let mut components = self.components();
363        match components.next()?.ok()? {
364            PathComponent::Root => {
365                let first = components.next().and_then(Result::ok).map(|c| c.as_str())?;
366                Some((first, components.as_path()))
367            },
368            first @ PathComponent::Normal(_) => Some((first.as_str(), components.as_path())),
369        }
370    }
371
372    /// Splits this path on the last component, returning it and a new [Path] of the remaining
373    /// components.
374    ///
375    /// Returns `None` if there are no components to split
376    pub fn split_last(&self) -> Option<(&str, &Path)> {
377        let mut components = self.components();
378        match components.next_back()?.ok()? {
379            PathComponent::Root => None,
380            last @ PathComponent::Normal(_) => Some((last.as_str(), components.as_path())),
381        }
382    }
383
384    /// Returns true if this path is for the root kernel module.
385    pub fn is_kernel_path(&self) -> bool {
386        match self.inner.strip_prefix("::") {
387            Some(Self::KERNEL_PATH) => true,
388            Some(_) => false,
389            None => &self.inner == Self::KERNEL_PATH,
390        }
391    }
392
393    /// Returns true if this path is for the root kernel module or an item in it
394    pub fn is_in_kernel(&self) -> bool {
395        if self.is_kernel_path() {
396            return true;
397        }
398
399        match self.split_last() {
400            Some((_, prefix)) => prefix.is_kernel_path(),
401            None => false,
402        }
403    }
404
405    /// Returns true if this path is for an executable module.
406    pub fn is_exec_path(&self) -> bool {
407        match self.inner.strip_prefix("::") {
408            Some(Self::EXEC_PATH) => true,
409            Some(_) => false,
410            None => &self.inner == Self::EXEC_PATH,
411        }
412    }
413
414    /// Returns true if this path is for the executable module or an item in it
415    pub fn is_in_exec(&self) -> bool {
416        if self.is_exec_path() {
417            return true;
418        }
419
420        match self.split_last() {
421            Some((_, prefix)) => prefix.is_exec_path(),
422            None => false,
423        }
424    }
425
426    /// Returns true if the current path, sans root component, starts with `prefix`
427    ///
428    /// The matching semantics of `Prefix` depend on the implementation of [`StartsWith<Prefix>`],
429    /// in particular, if `Prefix` is `str`, then the prefix is matched against the first non-root
430    /// component of `self`, regardless of whether the string contains path delimiters (i.e. `::`).
431    ///
432    /// See the [StartsWith] trait for more details.
433    #[inline]
434    pub fn starts_with<Prefix>(&self, prefix: &Prefix) -> bool
435    where
436        Prefix: ?Sized,
437        Self: StartsWith<Prefix>,
438    {
439        <Self as StartsWith<Prefix>>::starts_with(self, prefix)
440    }
441
442    /// Returns true if the current path, including root component, starts with `prefix`
443    ///
444    /// The matching semantics of `Prefix` depend on the implementation of [`StartsWith<Prefix>`],
445    /// in particular, if `Prefix` is `str`, then the prefix is matched against the first component
446    /// of `self`, regardless of whether the string contains path delimiters (i.e. `::`).
447    ///
448    /// See the [StartsWith] trait for more details.
449    #[inline]
450    pub fn starts_with_exactly<Prefix>(&self, prefix: &Prefix) -> bool
451    where
452        Prefix: ?Sized,
453        Self: StartsWith<Prefix>,
454    {
455        <Self as StartsWith<Prefix>>::starts_with_exactly(self, prefix)
456    }
457
458    /// Strips `prefix` from `self`, or returns `None` if `self` does not start with `prefix`.
459    ///
460    /// NOTE: Prefixes must be exact, i.e. if you call `path.strip_prefix(prefix)` and `path` is
461    /// relative but `prefix` is absolute, then this will return `None`. The same is true if `path`
462    /// is absolute and `prefix` is relative.
463    pub fn strip_prefix<'a>(&'a self, prefix: &Self) -> Option<&'a Self> {
464        let mut components = self.components();
465        for prefix_component in prefix.components() {
466            // All `Path` APIs assume that a `Path` is valid upon construction, though this is not
467            // actually enforced currently. We assert here if iterating over the components of the
468            // path finds an invalid component, because we expected the caller to have already
469            // validated the path
470            //
471            // In the future, we will likely enforce validity at construction so that iterating
472            // over its components is infallible, but that will require a breaking change to some
473            // APIs
474            let prefix_component = prefix_component.expect("invalid prefix path");
475            match (components.next(), prefix_component) {
476                (Some(Ok(PathComponent::Root)), PathComponent::Root) => (),
477                (Some(Ok(c @ PathComponent::Normal(_))), pc @ PathComponent::Normal(_)) => {
478                    if c.as_str() != pc.as_str() {
479                        return None;
480                    }
481                },
482                (Some(Ok(_) | Err(_)) | None, _) => return None,
483            }
484        }
485        Some(components.as_path())
486    }
487
488    /// Create an owned [PathBuf] with `path` adjoined to `self`.
489    ///
490    /// If `path` is absolute, it replaces the current path.
491    ///
492    /// The semantics of how `other` is joined to `self` in the resulting path depends on the
493    /// implementation of [Join] used. The implementation for [Path] and [PathBuf] joins all
494    /// components of `other` to self`; while the implementation for [prim@str], string-like values,
495    /// and identifiers/symbols joins just a single component. You must be careful to ensure that
496    /// if you are passing a string here, that you specifically want to join it as a single
497    /// component, or the resulting path may be different than you expect. It is recommended that
498    /// you use `Path::new(&string)` if you want to be explicit about treating a string-like value
499    /// as a multi-component path.
500    #[inline]
501    pub fn join<P>(&self, other: &P) -> PathBuf
502    where
503        P: ?Sized,
504        Path: Join<P>,
505    {
506        <Path as Join<P>>::join(self, other)
507    }
508
509    /// Canonicalize this path by ensuring that all components are in canonical form.
510    ///
511    /// Canonical form dictates that:
512    ///
513    /// * A component is quoted only if it requires quoting, and unquoted otherwise
514    /// * Is made absolute if relative and the first component is $kernel or $exec
515    ///
516    /// Returns `Err` if the path is invalid
517    pub fn canonicalize(&self) -> Result<PathBuf, PathError> {
518        let mut buf = PathBuf::with_capacity(self.byte_len());
519        buf.extend_with_components(self.components())?;
520        if buf.byte_len() > u16::MAX as usize {
521            return Err(PathError::TooLong { max: u16::MAX as usize });
522        }
523        Ok(buf)
524    }
525}
526
527impl StartsWith<str> for Path {
528    fn starts_with(&self, prefix: &str) -> bool {
529        let this = self.to_relative();
530        <Path as StartsWith<str>>::starts_with_exactly(this, prefix)
531    }
532
533    #[inline]
534    fn starts_with_exactly(&self, prefix: &str) -> bool {
535        match prefix {
536            "" => true,
537            "::" => self.is_absolute(),
538            prefix => {
539                let mut components = self.components();
540                let prefix = if let Some(prefix) = prefix.strip_prefix("::") {
541                    let is_absolute =
542                        components.next().is_some_and(|c| matches!(c, Ok(PathComponent::Root)));
543                    if !is_absolute {
544                        return false;
545                    }
546                    prefix
547                } else {
548                    prefix
549                };
550                components.next().is_some_and(
551                    |c| matches!(c, Ok(c @ PathComponent::Normal(_)) if c.as_str() == prefix),
552                )
553            },
554        }
555    }
556}
557
558impl StartsWith<Path> for Path {
559    fn starts_with(&self, prefix: &Path) -> bool {
560        let this = self.to_relative();
561        let prefix = prefix.to_relative();
562        <Path as StartsWith<Path>>::starts_with_exactly(this, prefix)
563    }
564
565    #[inline]
566    fn starts_with_exactly(&self, prefix: &Path) -> bool {
567        let mut components = self.components();
568        for prefix_component in prefix.components() {
569            // All `Path` APIs assume that a `Path` is valid upon construction, though this is not
570            // actually enforced currently. We assert here if iterating over the components of the
571            // path finds an invalid component, because we expected the caller to have already
572            // validated the path
573            //
574            // In the future, we will likely enforce validity at construction so that iterating
575            // over its components is infallible, but that will require a breaking change to some
576            // APIs
577            let prefix_component = prefix_component.expect("invalid prefix path");
578            match (components.next(), prefix_component) {
579                (Some(Ok(PathComponent::Root)), PathComponent::Root) => {},
580                (Some(Ok(c @ PathComponent::Normal(_))), pc @ PathComponent::Normal(_)) => {
581                    if c.as_str() != pc.as_str() {
582                        return false;
583                    }
584                },
585                (Some(Ok(_) | Err(_)) | None, _) => return false,
586            }
587        }
588        true
589    }
590}
591
592impl PartialEq<str> for Path {
593    fn eq(&self, other: &str) -> bool {
594        &self.inner == other
595    }
596}
597
598impl PartialEq<PathBuf> for Path {
599    fn eq(&self, other: &PathBuf) -> bool {
600        &self.inner == other.inner.as_str()
601    }
602}
603
604impl PartialEq<&PathBuf> for Path {
605    fn eq(&self, other: &&PathBuf) -> bool {
606        &self.inner == other.inner.as_str()
607    }
608}
609
610impl PartialEq<Path> for PathBuf {
611    fn eq(&self, other: &Path) -> bool {
612        self.inner.as_str() == &other.inner
613    }
614}
615
616impl PartialEq<&Path> for Path {
617    fn eq(&self, other: &&Path) -> bool {
618        self.inner == other.inner
619    }
620}
621
622impl PartialEq<alloc::boxed::Box<Path>> for Path {
623    fn eq(&self, other: &alloc::boxed::Box<Path>) -> bool {
624        self.inner == other.inner
625    }
626}
627
628impl PartialEq<alloc::rc::Rc<Path>> for Path {
629    fn eq(&self, other: &alloc::rc::Rc<Path>) -> bool {
630        self.inner == other.inner
631    }
632}
633
634impl PartialEq<alloc::sync::Arc<Path>> for Path {
635    fn eq(&self, other: &alloc::sync::Arc<Path>) -> bool {
636        self.inner == other.inner
637    }
638}
639
640impl PartialEq<Cow<'_, Path>> for Path {
641    fn eq(&self, other: &Cow<'_, Path>) -> bool {
642        self.inner == other.as_ref().inner
643    }
644}
645
646impl fmt::Display for Path {
647    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
648        f.write_str(&self.inner)
649    }
650}
651
652#[cfg(test)]
653mod tests {
654    use super::*;
655
656    #[test]
657    fn test_path_split_with_quotes_and_large_components() -> Result<(), PathError> {
658        let path = Path::new(
659            "::\"miden:counter-contract/counter-contract@0.1.0\"::counter_contract::_RNvXsg_NtNtCseaniv3neBPi_10miden_base5types7storageINtB5_10StorageMapNtNtCscxjsWiPtnzN_11miden_field4word4WordNtNtB19_10wasm_miden4FeltEINtNtCs3NSMmx5ugmE_4core7convert4FromNtNtNtCs52sTGLXFK3W_14miden_base_sys8bindings5types13StorageSlotIdE4fromCsanZ5gV8dMQ0_16counter_contract",
660        );
661        let canonicalized = path.canonicalize()?;
662
663        assert_eq!(canonicalized.as_path(), path);
664        Ok(())
665    }
666
667    #[test]
668    fn test_canonicalize_path_identity() -> Result<(), PathError> {
669        let path = Path::new("foo::bar");
670        let canonicalized = path.canonicalize()?;
671
672        assert_eq!(canonicalized.as_path(), path);
673        Ok(())
674    }
675
676    #[test]
677    fn test_canonicalize_path_kernel_is_absolute() -> Result<(), PathError> {
678        let path = Path::new("$kernel::bar");
679        let canonicalized = path.canonicalize()?;
680
681        let expected = Path::new("::$kernel::bar");
682        assert_eq!(canonicalized.as_path(), expected);
683        Ok(())
684    }
685
686    #[test]
687    fn test_canonicalize_path_exec_is_absolute() -> Result<(), PathError> {
688        let path = Path::new("$exec::$main");
689        let canonicalized = path.canonicalize()?;
690
691        let expected = Path::new("::$exec::$main");
692        assert_eq!(canonicalized.as_path(), expected);
693        Ok(())
694    }
695
696    #[test]
697    fn test_to_absolute_rejects_invalid_absolute_path_component() {
698        let source = alloc::format!("::{}", "a".repeat(Path::MAX_COMPONENT_LENGTH + 1));
699        let err = Path::new(&source)
700            .to_absolute()
701            .expect_err("absolute paths must still validate their components");
702
703        assert!(matches!(
704            err,
705            PathError::InvalidComponent(crate::ast::IdentError::InvalidLength { .. })
706        ));
707    }
708
709    #[test]
710    fn test_to_absolute_rejects_oversized_absolute_result() {
711        let source = alloc::format!("{}aa", "a::".repeat(21_844));
712        assert_eq!(source.len(), (u16::MAX as usize) - 1);
713
714        let err = Path::new(&source)
715            .to_absolute()
716            .expect_err("adding the absolute path prefix must preserve the path length bound");
717
718        assert!(matches!(err, PathError::TooLong { max } if max == u16::MAX as usize));
719    }
720
721    #[test]
722    fn test_canonicalize_path_remove_unnecessary_quoting() -> Result<(), PathError> {
723        let path = Path::new("foo::\"bar\"");
724        let canonicalized = path.canonicalize()?;
725
726        let expected = Path::new("foo::bar");
727        assert_eq!(canonicalized.as_path(), expected);
728        Ok(())
729    }
730
731    #[test]
732    fn test_canonicalize_path_preserve_necessary_quoting() -> Result<(), PathError> {
733        let path = Path::new("foo::\"bar::baz\"");
734        let canonicalized = path.canonicalize()?;
735
736        assert_eq!(canonicalized.as_path(), path);
737        Ok(())
738    }
739
740    #[test]
741    fn test_canonicalize_path_add_required_quoting_to_components_without_delimiter()
742    -> Result<(), PathError> {
743        let path = Path::new("foo::$bar");
744        let canonicalized = path.canonicalize()?;
745
746        let expected = Path::new("foo::\"$bar\"");
747        assert_eq!(canonicalized.as_path(), expected);
748        Ok(())
749    }
750
751    #[test]
752    fn test_canonicalize_path_rejects_canonical_result_longer_than_u16_max() {
753        let component = alloc::format!("{}-", "a".repeat(254));
754        let mut source = alloc::string::String::new();
755        for i in 0..255 {
756            if i > 0 {
757                source.push_str("::");
758            }
759            source.push_str(&component);
760        }
761
762        let path = Path::validate(&source).expect("source path must be pre-canonicalization valid");
763        let err = path.canonicalize().expect_err(
764            "canonicalization must reject paths that exceed the serialization length bound",
765        );
766
767        assert!(matches!(err, PathError::TooLong { max } if max == u16::MAX as usize));
768    }
769}