Skip to main content

miden_assembly_syntax/ast/path/
path_buf.rs

1use alloc::string::{String, ToString};
2use core::{
3    fmt,
4    ops::Deref,
5    str::{self, FromStr},
6};
7
8use miden_core::serde::{
9    ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
10};
11
12use super::{Path, PathComponent, PathError};
13use crate::ast::Ident;
14
15// ITEM PATH
16// ================================================================================================
17
18/// Path to an item in a library, i.e. module, procedure, constant or type.
19#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
20#[cfg_attr(
21    all(feature = "arbitrary", test),
22    miden_test_serde_macros::serde_test(binary_serde(true))
23)]
24pub struct PathBuf {
25    pub(super) inner: String,
26}
27
28impl Deref for PathBuf {
29    type Target = Path;
30
31    #[inline(always)]
32    fn deref(&self) -> &Self::Target {
33        self.as_ref()
34    }
35}
36
37impl AsRef<Path> for PathBuf {
38    #[inline(always)]
39    fn as_ref(&self) -> &Path {
40        Path::new(&self.inner)
41    }
42}
43
44impl AsRef<str> for PathBuf {
45    #[inline(always)]
46    fn as_ref(&self) -> &str {
47        &self.inner
48    }
49}
50
51impl<'a> From<&'a Path> for PathBuf {
52    #[inline(always)]
53    fn from(path: &'a Path) -> Self {
54        path.to_path_buf()
55    }
56}
57
58/// Constructors
59impl PathBuf {
60    /// Get an empty [PathBuf] with `capacity` bytes allocated for the underlying path storage
61    pub fn with_capacity(capacity: usize) -> Self {
62        Self { inner: String::with_capacity(capacity) }
63    }
64
65    /// Returns a new path created from the provided source.
66    ///
67    /// A path consists of at list of components separated by `::` delimiter. A path must contain
68    /// at least one component.
69    ///
70    /// # Errors
71    ///
72    /// Returns an error if:
73    ///
74    /// * The path is empty.
75    /// * Any component of the path is empty.
76    /// * Any component is not a valid identifier (quoted or unquoted) in Miden Assembly syntax,
77    ///   i.e. starts with an ASCII alphabetic character, contains only printable ASCII characters,
78    ///   except for `::`, which must only be used as a path separator.
79    pub fn new<S>(source: &S) -> Result<Self, PathError>
80    where
81        S: AsRef<str> + ?Sized,
82    {
83        let source = source.as_ref();
84
85        let validated = Path::validate(source)?;
86
87        validated.canonicalize()
88    }
89
90    /// Extends this [PathBuf] from a collection of possibly-invalid path components
91    pub(super) fn extend_with_components<'a>(
92        &mut self,
93        components: impl IntoIterator<Item = Result<PathComponent<'a>, PathError>>,
94    ) -> Result<(), PathError> {
95        for component in components {
96            self.push_component(component?.as_str());
97        }
98        Ok(())
99    }
100
101    /// Create an absolute [Path] from a pre-validated string
102    pub fn absolute<S>(source: &S) -> Self
103    where
104        S: AsRef<str> + ?Sized,
105    {
106        let source = source.as_ref();
107        Path::new(source).to_absolute().into_owned()
108    }
109
110    /// Create a relative [Path] from a pre-validated string
111    pub fn relative<S>(source: &S) -> Self
112    where
113        S: AsRef<str> + ?Sized,
114    {
115        let source = source.as_ref();
116        match source.strip_prefix("::") {
117            Some(rest) => Self { inner: rest.to_string() },
118            None => Self { inner: source.to_string() },
119        }
120    }
121
122    /// Get a [Path] corresponding to the this [PathBuf]
123    #[inline]
124    pub fn as_path(&self) -> &Path {
125        self.as_ref()
126    }
127
128    /// Convert this mutable [PathBuf] into an owned, read-only [`alloc::boxed::Box<Path>`]
129    pub fn into_boxed_path(self) -> alloc::boxed::Box<Path> {
130        let inner = self.inner.into_boxed_str();
131        let inner = alloc::boxed::Box::into_raw(inner);
132        // SAFETY: This cast is safe because *mut Path is equivalent to *mut str
133        unsafe { alloc::boxed::Box::from_raw(inner as *mut Path) }
134    }
135}
136
137/// Mutation
138impl PathBuf {
139    /// Overrides the parent prefix of this path.
140    ///
141    /// The parent prefix is the part of the path consisting of all components but the last one.
142    ///
143    /// If there is only a single component in `self`, this function is equivalent to appending
144    /// `self` to `parent`.
145    pub fn set_parent<P>(&mut self, parent: &P)
146    where
147        P: AsRef<Path> + ?Sized,
148    {
149        let parent = parent.as_ref();
150        match self.split_last() {
151            Some((last, _)) => {
152                let reparented = parent.join(last);
153                let _ = core::mem::replace(self, reparented);
154            },
155            None => {
156                self.inner.clear();
157                self.inner.push_str(parent.as_str());
158            },
159        }
160    }
161
162    /// Extends `self` with `path`
163    ///
164    /// If `path` is absolute, it replaces the current path.
165    ///
166    /// This function ensures that the joined path correctly delimits each path component, and that
167    /// each component is in canonical form.
168    pub fn push<P>(&mut self, path: &P)
169    where
170        P: AsRef<Path> + ?Sized,
171    {
172        let path = path.as_ref();
173
174        self.extend_with_components(path.components()).expect("invalid path");
175    }
176
177    /// Extends `self` with `component`.
178    ///
179    /// Unlike [`Self::push`], which appends another `Path` to the buffer - this method appends the
180    /// given string as a single path component, ensuring that the content is quoted properly if
181    /// needed.
182    ///
183    /// If `::` is pushed, it is treated as a literal root component (i.e. it makes the path
184    /// absolute). On a non-empty [PathBuf], this has the effect of clearing the path, similar to
185    /// what happens if you call [`PathBuf::push`] with an absolute path.
186    ///
187    /// Pushing components using this method guarantees they are in canonical form.
188    pub fn push_component<S>(&mut self, component: &S)
189    where
190        S: AsRef<str> + ?Sized,
191    {
192        let component = component.as_ref();
193        match component {
194            "" | "\"\"" => (),
195            "::" if self.inner.is_empty() => {
196                self.inner.push_str("::");
197            },
198            "::" => {
199                // Pushing the root component on a non-empty path, resets it to an empty absolute
200                // path
201                self.inner.clear();
202                self.inner.push_str("::");
203            },
204            component => {
205                // Add a delimiter if the path is non-empty
206                if !self.is_empty() {
207                    self.inner.push_str("::");
208                }
209
210                let is_quoted = component.starts_with('"') && component.ends_with('"');
211                let is_special = component == Path::KERNEL_PATH || component == Path::EXEC_PATH;
212                let requires_quoting = !is_special && Ident::requires_quoting(component);
213
214                if is_special && self.inner.is_empty() {
215                    // Special namespaces are always absolute
216                    self.inner.push_str("::");
217                    self.inner.push_str(component);
218                } else if requires_quoting && !is_quoted {
219                    // Quote when necessary
220                    self.inner.push('"');
221                    self.inner.push_str(component);
222                    self.inner.push('"');
223                } else if !requires_quoting && is_quoted {
224                    // Unquote unnecessary quoting
225                    self.inner.push_str(&component[1..(component.len() - 1)]);
226                } else {
227                    // No quoting required, or already quoted
228                    self.inner.push_str(component);
229                }
230            },
231        }
232    }
233
234    /// Truncates `self` to [`Path::parent`].
235    ///
236    /// Returns `false` if `self.parent()` is `None`, otherwise `true`.
237    pub fn pop(&mut self) -> bool {
238        match self.parent() {
239            Some(parent) => {
240                let buf = parent.as_str().to_string();
241                self.inner = buf;
242                true
243            },
244            None => false,
245        }
246    }
247}
248
249impl<'a> core::ops::AddAssign<&'a Path> for PathBuf {
250    fn add_assign(&mut self, rhs: &'a Path) {
251        self.push(rhs);
252    }
253}
254
255impl<'a> core::ops::AddAssign<&'a str> for PathBuf {
256    fn add_assign(&mut self, rhs: &'a str) {
257        self.push_component(rhs);
258    }
259}
260
261impl<'a> core::ops::AddAssign<&'a Ident> for PathBuf {
262    fn add_assign(&mut self, rhs: &'a Ident) {
263        self.push_component(rhs.as_str());
264    }
265}
266
267impl<'a> core::ops::AddAssign<&'a crate::ast::ProcedureName> for PathBuf {
268    fn add_assign(&mut self, rhs: &'a crate::ast::ProcedureName) {
269        self.push_component(rhs.as_str());
270    }
271}
272
273impl<'a> TryFrom<&'a str> for PathBuf {
274    type Error = PathError;
275
276    fn try_from(value: &'a str) -> Result<Self, Self::Error> {
277        PathBuf::new(value)
278    }
279}
280
281fn is_canonical_path(path: &Path) -> bool {
282    let mut is_absolute = false;
283    let mut saw_normal_component = false;
284
285    for component in path.components() {
286        let component = match component {
287            Ok(component) => component,
288            Err(_) => return false,
289        };
290
291        match component {
292            PathComponent::Root => is_absolute = true,
293            component @ PathComponent::Normal(_) => {
294                let is_quoted = component.is_quoted();
295                let component = component.as_str();
296                let is_special = matches!(component, Path::KERNEL_PATH | Path::EXEC_PATH);
297                let requires_quoting = !is_special && Ident::requires_quoting(component);
298
299                if !saw_normal_component && !is_absolute && is_special {
300                    return false;
301                }
302
303                if requires_quoting != is_quoted {
304                    return false;
305                }
306
307                saw_normal_component = true;
308            },
309        }
310    }
311
312    true
313}
314
315impl TryFrom<String> for PathBuf {
316    type Error = PathError;
317
318    fn try_from(value: String) -> Result<Self, Self::Error> {
319        let path = Path::validate(&value)?;
320        if is_canonical_path(path) {
321            Ok(Self { inner: value })
322        } else {
323            path.canonicalize()
324        }
325    }
326}
327
328impl From<Ident> for PathBuf {
329    fn from(component: Ident) -> Self {
330        let mut buf = PathBuf::with_capacity(component.as_str().len());
331        buf.push_component(component.as_str());
332        buf
333    }
334}
335
336impl From<PathBuf> for String {
337    fn from(path: PathBuf) -> Self {
338        path.inner
339    }
340}
341
342impl From<PathBuf> for alloc::sync::Arc<Path> {
343    fn from(value: PathBuf) -> Self {
344        value.into_boxed_path().into()
345    }
346}
347
348impl From<alloc::borrow::Cow<'_, Path>> for PathBuf {
349    fn from(value: alloc::borrow::Cow<'_, Path>) -> Self {
350        value.into_owned()
351    }
352}
353
354impl FromStr for PathBuf {
355    type Err = PathError;
356
357    #[inline]
358    fn from_str(value: &str) -> Result<Self, Self::Err> {
359        Self::new(value)
360    }
361}
362
363impl Serializable for PathBuf {
364    fn write_into<W: ByteWriter>(&self, target: &mut W) {
365        self.as_path().write_into(target);
366    }
367}
368
369impl Serializable for Path {
370    fn write_into<W: ByteWriter>(&self, target: &mut W) {
371        target.write_u16(self.byte_len().try_into().expect("invalid path: too long"));
372        target.write_bytes(self.as_str().as_bytes());
373    }
374}
375
376impl Deserializable for PathBuf {
377    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
378        let len = source.read_u16()? as usize;
379        let path = source.read_slice(len)?;
380        let path =
381            str::from_utf8(path).map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
382        let path =
383            Path::validate(path).map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
384        path.canonicalize()
385            .map_err(|e| DeserializationError::InvalidValue(e.to_string()))
386    }
387}
388
389#[cfg(feature = "serde")]
390impl serde::Serialize for PathBuf {
391    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
392    where
393        S: serde::Serializer,
394    {
395        serializer.serialize_str(self.inner.as_str())
396    }
397}
398
399#[cfg(feature = "serde")]
400impl<'de> serde::Deserialize<'de> for PathBuf {
401    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
402    where
403        D: serde::Deserializer<'de>,
404    {
405        use serde::de::Visitor;
406
407        struct PathVisitor;
408
409        impl<'de> Visitor<'de> for PathVisitor {
410            type Value = PathBuf;
411
412            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
413                formatter.write_str("a valid Path/PathBuf")
414            }
415
416            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
417            where
418                E: serde::de::Error,
419            {
420                Path::validate(v)
421                    .map_err(serde::de::Error::custom)?
422                    .canonicalize()
423                    .map_err(serde::de::Error::custom)
424            }
425
426            fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
427            where
428                E: serde::de::Error,
429            {
430                Path::validate(&v)
431                    .map_err(serde::de::Error::custom)?
432                    .canonicalize()
433                    .map_err(serde::de::Error::custom)
434            }
435        }
436
437        deserializer.deserialize_any(PathVisitor)
438    }
439}
440
441impl fmt::Display for PathBuf {
442    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
443        fmt::Display::fmt(self.as_path(), f)
444    }
445}
446
447// TESTS
448// ================================================================================================
449
450/// Tests
451#[cfg(test)]
452mod tests {
453    use alloc::string::String;
454
455    use miden_core::assert_matches;
456
457    use super::{PathBuf, PathError};
458    use crate::{Path, PathComponent, ast::IdentError};
459
460    #[test]
461    fn single_component_path() {
462        let path = PathBuf::new("foo").unwrap();
463        assert!(!path.is_absolute());
464        assert_eq!(path.components().count(), 1);
465        assert_eq!(path.last(), Some("foo"));
466        assert_eq!(path.first(), Some("foo"));
467    }
468
469    #[test]
470    fn relative_path_two_components() {
471        let path = PathBuf::new("foo::bar").unwrap();
472        assert!(!path.is_absolute());
473        assert_eq!(path.components().count(), 2);
474        assert_eq!(path.last(), Some("bar"));
475        assert_eq!(path.first(), Some("foo"));
476    }
477
478    #[test]
479    fn relative_path_three_components() {
480        let path = PathBuf::new("foo::bar::baz").unwrap();
481        assert!(!path.is_absolute());
482        assert_eq!(path.components().count(), 3);
483        assert_eq!(path.last(), Some("baz"));
484        assert_eq!(path.first(), Some("foo"));
485        assert_eq!(path.parent().map(Path::as_str), Some("foo::bar"));
486    }
487
488    #[test]
489    fn single_quoted_component() {
490        let path = PathBuf::new("\"miden::base/account@0.1.0\"").unwrap();
491        assert!(!path.is_absolute());
492        assert_eq!(path.components().count(), 1);
493        assert_eq!(path.last(), Some("miden::base/account@0.1.0"));
494        assert_eq!(path.first(), Some("miden::base/account@0.1.0"));
495    }
496
497    #[test]
498    fn trailing_quoted_component() {
499        let path = PathBuf::new("foo::\"miden::base/account@0.1.0\"").unwrap();
500        assert!(!path.is_absolute());
501        assert_eq!(path.components().count(), 2);
502        assert_eq!(path.last(), Some("miden::base/account@0.1.0"));
503        assert_eq!(path.first(), Some("foo"));
504    }
505
506    #[test]
507    fn interspersed_quoted_component() {
508        let path = PathBuf::new("foo::\"miden::base/account@0.1.0\"::item").unwrap();
509        assert!(!path.is_absolute());
510        assert_eq!(path.components().count(), 3);
511        assert_eq!(path.last(), Some("item"));
512        assert_eq!(path.first(), Some("foo"));
513        assert_eq!(path.parent().map(Path::as_str), Some("foo::\"miden::base/account@0.1.0\""));
514    }
515
516    #[test]
517    fn mixed_quoted_components_regression() {
518        let component = "::root_ns:root@1.0.0";
519        let module = "abi_transform_tx_kernel_get_inputs_4";
520        let function = "miden::protocol::active_note::get_inputs";
521        let quoted_function = "\"miden::protocol::active_note::get_inputs\"";
522
523        let p1 = PathBuf::new(&format!("{component}::{module}::\"{function}\"")).unwrap();
524        let mut p2 = PathBuf::new(component).unwrap();
525        p2.push_component(module);
526        p2.push_component(function);
527
528        assert_eq!(p1, p2);
529
530        // Forward iteration of path components
531
532        let mut p1components = p1.components();
533        let mut p2components = p2.components();
534
535        let p1root = p1components.next().unwrap().unwrap();
536        let p2root = p2components.next().unwrap().unwrap();
537
538        assert_eq!(p1root, PathComponent::Root);
539        assert_eq!(p1root, p2root);
540
541        let p1component = p1components.next().unwrap().unwrap();
542        let p2component = p2components.next().unwrap().unwrap();
543
544        assert_eq!(p1component, PathComponent::Normal("\"root_ns:root@1.0.0\""));
545        assert_eq!(p1component, p2component);
546
547        let p1module = p1components.next().unwrap().unwrap();
548        let p2module = p2components.next().unwrap().unwrap();
549
550        assert_eq!(p1module, PathComponent::Normal(module));
551        assert_eq!(p1module, p2module);
552
553        let p1function = p1components.next().unwrap().unwrap();
554        let p2function = p2components.next().unwrap().unwrap();
555
556        assert_eq!(p1function, PathComponent::Normal(quoted_function));
557        assert_eq!(p1function, p2function);
558
559        // Backward iteration of path components
560
561        let mut p1components = p1.components();
562        let mut p2components = p2.components();
563
564        let p1function = p1components.next_back().unwrap().unwrap();
565        let p2function = p2components.next_back().unwrap().unwrap();
566
567        assert_eq!(p1function, PathComponent::Normal(quoted_function));
568        assert_eq!(p1function, p2function);
569
570        let p1module = p1components.next_back().unwrap().unwrap();
571        let p2module = p2components.next_back().unwrap().unwrap();
572
573        assert_eq!(p1module, PathComponent::Normal(module));
574        assert_eq!(p1module, p2module);
575
576        let p1component = p1components.next_back().unwrap().unwrap();
577        let p2component = p2components.next_back().unwrap().unwrap();
578
579        assert_eq!(p1component, PathComponent::Normal("\"root_ns:root@1.0.0\""));
580        assert_eq!(p1component, p2component);
581
582        let p1root = p1components.next_back().unwrap().unwrap();
583        let p2root = p2components.next_back().unwrap().unwrap();
584
585        assert_eq!(p1root, PathComponent::Root);
586        assert_eq!(p1root, p2root);
587
588        assert!(p1.is_absolute());
589        assert_eq!(p1.components().count(), 4);
590        assert_eq!(p1.last(), Some(function));
591        assert_eq!(p1.first(), Some("root_ns:root@1.0.0"));
592        let parent = p1.parent().unwrap();
593        assert_eq!(
594            parent.as_str(),
595            "::\"root_ns:root@1.0.0\"::abi_transform_tx_kernel_get_inputs_4"
596        );
597        assert_eq!(parent.parent().map(Path::as_str), Some("::\"root_ns:root@1.0.0\""));
598
599        assert!(p2.is_absolute());
600        assert_eq!(p2.components().count(), 4);
601        assert_eq!(p2.last(), Some(function));
602        assert_eq!(p2.first(), Some("root_ns:root@1.0.0"));
603        let parent = p2.parent().unwrap();
604        assert_eq!(
605            parent.as_str(),
606            "::\"root_ns:root@1.0.0\"::abi_transform_tx_kernel_get_inputs_4"
607        );
608        assert_eq!(parent.parent().map(Path::as_str), Some("::\"root_ns:root@1.0.0\""));
609    }
610
611    #[test]
612    fn try_from_string_canonicalizes_like_str() {
613        let from_str = PathBuf::try_from("foo::\"bar\"").unwrap();
614        let from_string = PathBuf::try_from(String::from("foo::\"bar\"")).unwrap();
615
616        assert_eq!(from_string, from_str);
617        assert_eq!(from_string.as_str(), "foo::bar");
618    }
619
620    #[test]
621    fn try_from_string_reuses_canonical_allocation() {
622        let value = String::from("foo::bar");
623        let original_ptr = value.as_ptr();
624        let original_capacity = value.capacity();
625
626        let path = PathBuf::try_from(value).unwrap();
627
628        assert_eq!(path.as_str(), "foo::bar");
629        assert_eq!(path.inner.as_ptr(), original_ptr);
630        assert_eq!(path.inner.capacity(), original_capacity);
631    }
632
633    #[test]
634    fn exec_path() {
635        let path = PathBuf::new("$exec::bar::baz").unwrap();
636        assert!(path.is_absolute());
637        assert_eq!(path.components().count(), 4);
638        assert_eq!(path.last(), Some("baz"));
639        assert_eq!(path.first(), Some("$exec"));
640    }
641
642    #[test]
643    fn kernel_path() {
644        let path = PathBuf::new("$kernel::bar::baz").unwrap();
645        assert!(path.is_absolute());
646        assert_eq!(path.components().count(), 4);
647        assert_eq!(path.last(), Some("baz"));
648        assert_eq!(path.first(), Some("$kernel"));
649    }
650
651    #[test]
652    fn invalid_path_empty() {
653        let result = Path::validate("");
654        assert_matches!(result, Err(PathError::Empty));
655    }
656
657    #[test]
658    fn invalid_path_empty_component() {
659        let result = Path::validate("::");
660        assert_matches!(result, Err(PathError::EmptyComponent));
661    }
662
663    #[test]
664    fn invalid_path_trailing_delimiter() {
665        let result = Path::validate("foo::");
666        assert_matches!(result, Err(PathError::InvalidComponent(IdentError::Empty)));
667    }
668
669    #[test]
670    fn invalid_path_invalid_character() {
671        let result = Path::validate("#foo::bar");
672        assert_matches!(result, Err(PathError::InvalidComponent(IdentError::InvalidChars { .. })));
673    }
674
675    #[cfg(feature = "serde")]
676    #[test]
677    fn serde_deserialize_path_buf_canonicalizes_redundant_quotes() {
678        let path: PathBuf = serde_json::from_str(r#""\"foo\"::\"bar\"""#)
679            .expect("path deserialization must succeed");
680        assert_eq!(path.as_str(), "foo::bar");
681    }
682
683    #[cfg(feature = "serde")]
684    #[test]
685    fn serde_deserialize_path_buf_preserves_required_quotes() {
686        let path: PathBuf = serde_json::from_value(serde_json::Value::String(String::from(
687            "::foo::\"miden::base/account@0.1.0\"",
688        )))
689        .expect("path deserialization must succeed");
690        assert_eq!(path.as_str(), "::foo::\"miden::base/account@0.1.0\"");
691    }
692
693    #[cfg(feature = "serde")]
694    #[test]
695    fn serde_deserialize_path_buf_rejects_overflow_after_canonicalization() {
696        let component = format!("{}-", "a".repeat(254));
697        let mut source = String::new();
698        for i in 0..255 {
699            if i > 0 {
700                source.push_str("::");
701            }
702            source.push_str(&component);
703        }
704
705        let err = serde_json::from_value::<PathBuf>(serde_json::Value::String(source))
706            .expect_err("deserialization must fail when canonicalization exceeds u16::MAX bytes");
707        let message = format!("{err}");
708        assert!(message.contains("too long"), "unexpected error: {message}");
709    }
710}