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().unwrap().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    use core::assert_matches;
455
456    use super::{PathBuf, PathError};
457    use crate::{Path, PathComponent, ast::IdentError};
458
459    #[test]
460    fn single_component_path() {
461        let path = PathBuf::new("foo").unwrap();
462        assert!(!path.is_absolute());
463        assert_eq!(path.components().count(), 1);
464        assert_eq!(path.last(), Some("foo"));
465        assert_eq!(path.first(), Some("foo"));
466    }
467
468    #[test]
469    fn relative_path_two_components() {
470        let path = PathBuf::new("foo::bar").unwrap();
471        assert!(!path.is_absolute());
472        assert_eq!(path.components().count(), 2);
473        assert_eq!(path.last(), Some("bar"));
474        assert_eq!(path.first(), Some("foo"));
475    }
476
477    #[test]
478    fn relative_path_three_components() {
479        let path = PathBuf::new("foo::bar::baz").unwrap();
480        assert!(!path.is_absolute());
481        assert_eq!(path.components().count(), 3);
482        assert_eq!(path.last(), Some("baz"));
483        assert_eq!(path.first(), Some("foo"));
484        assert_eq!(path.parent().map(Path::as_str), Some("foo::bar"));
485    }
486
487    #[test]
488    fn single_quoted_component() {
489        let path = PathBuf::new("\"miden::base/account@0.1.0\"").unwrap();
490        assert!(!path.is_absolute());
491        assert_eq!(path.components().count(), 1);
492        assert_eq!(path.last(), Some("miden::base/account@0.1.0"));
493        assert_eq!(path.first(), Some("miden::base/account@0.1.0"));
494    }
495
496    #[test]
497    fn trailing_quoted_component() {
498        let path = PathBuf::new("foo::\"miden::base/account@0.1.0\"").unwrap();
499        assert!(!path.is_absolute());
500        assert_eq!(path.components().count(), 2);
501        assert_eq!(path.last(), Some("miden::base/account@0.1.0"));
502        assert_eq!(path.first(), Some("foo"));
503    }
504
505    #[test]
506    fn interspersed_quoted_component() {
507        let path = PathBuf::new("foo::\"miden::base/account@0.1.0\"::item").unwrap();
508        assert!(!path.is_absolute());
509        assert_eq!(path.components().count(), 3);
510        assert_eq!(path.last(), Some("item"));
511        assert_eq!(path.first(), Some("foo"));
512        assert_eq!(path.parent().map(Path::as_str), Some("foo::\"miden::base/account@0.1.0\""));
513    }
514
515    #[test]
516    fn mixed_quoted_components_regression() {
517        let component = "::root_ns:root@1.0.0";
518        let module = "abi_transform_tx_kernel_get_inputs_4";
519        let function = "miden::protocol::active_note::get_inputs";
520        let quoted_function = "\"miden::protocol::active_note::get_inputs\"";
521
522        let p1 = PathBuf::new(&format!("{component}::{module}::\"{function}\"")).unwrap();
523        let mut p2 = PathBuf::new(component).unwrap();
524        p2.push_component(module);
525        p2.push_component(function);
526
527        assert_eq!(p1, p2);
528
529        // Forward iteration of path components
530
531        let mut p1components = p1.components();
532        let mut p2components = p2.components();
533
534        let p1root = p1components.next().unwrap().unwrap();
535        let p2root = p2components.next().unwrap().unwrap();
536
537        assert_eq!(p1root, PathComponent::Root);
538        assert_eq!(p1root, p2root);
539
540        let p1component = p1components.next().unwrap().unwrap();
541        let p2component = p2components.next().unwrap().unwrap();
542
543        assert_eq!(p1component, PathComponent::Normal("\"root_ns:root@1.0.0\""));
544        assert_eq!(p1component, p2component);
545
546        let p1module = p1components.next().unwrap().unwrap();
547        let p2module = p2components.next().unwrap().unwrap();
548
549        assert_eq!(p1module, PathComponent::Normal(module));
550        assert_eq!(p1module, p2module);
551
552        let p1function = p1components.next().unwrap().unwrap();
553        let p2function = p2components.next().unwrap().unwrap();
554
555        assert_eq!(p1function, PathComponent::Normal(quoted_function));
556        assert_eq!(p1function, p2function);
557
558        // Backward iteration of path components
559
560        let mut p1components = p1.components();
561        let mut p2components = p2.components();
562
563        let p1function = p1components.next_back().unwrap().unwrap();
564        let p2function = p2components.next_back().unwrap().unwrap();
565
566        assert_eq!(p1function, PathComponent::Normal(quoted_function));
567        assert_eq!(p1function, p2function);
568
569        let p1module = p1components.next_back().unwrap().unwrap();
570        let p2module = p2components.next_back().unwrap().unwrap();
571
572        assert_eq!(p1module, PathComponent::Normal(module));
573        assert_eq!(p1module, p2module);
574
575        let p1component = p1components.next_back().unwrap().unwrap();
576        let p2component = p2components.next_back().unwrap().unwrap();
577
578        assert_eq!(p1component, PathComponent::Normal("\"root_ns:root@1.0.0\""));
579        assert_eq!(p1component, p2component);
580
581        let p1root = p1components.next_back().unwrap().unwrap();
582        let p2root = p2components.next_back().unwrap().unwrap();
583
584        assert_eq!(p1root, PathComponent::Root);
585        assert_eq!(p1root, p2root);
586
587        assert!(p1.is_absolute());
588        assert_eq!(p1.components().count(), 4);
589        assert_eq!(p1.last(), Some(function));
590        assert_eq!(p1.first(), Some("root_ns:root@1.0.0"));
591        let parent = p1.parent().unwrap();
592        assert_eq!(
593            parent.as_str(),
594            "::\"root_ns:root@1.0.0\"::abi_transform_tx_kernel_get_inputs_4"
595        );
596        assert_eq!(parent.parent().map(Path::as_str), Some("::\"root_ns:root@1.0.0\""));
597
598        assert!(p2.is_absolute());
599        assert_eq!(p2.components().count(), 4);
600        assert_eq!(p2.last(), Some(function));
601        assert_eq!(p2.first(), Some("root_ns:root@1.0.0"));
602        let parent = p2.parent().unwrap();
603        assert_eq!(
604            parent.as_str(),
605            "::\"root_ns:root@1.0.0\"::abi_transform_tx_kernel_get_inputs_4"
606        );
607        assert_eq!(parent.parent().map(Path::as_str), Some("::\"root_ns:root@1.0.0\""));
608    }
609
610    #[test]
611    fn try_from_string_canonicalizes_like_str() {
612        let from_str = PathBuf::try_from("foo::\"bar\"").unwrap();
613        let from_string = PathBuf::try_from(String::from("foo::\"bar\"")).unwrap();
614
615        assert_eq!(from_string, from_str);
616        assert_eq!(from_string.as_str(), "foo::bar");
617    }
618
619    #[test]
620    fn try_from_string_reuses_canonical_allocation() {
621        let value = String::from("foo::bar");
622        let original_ptr = value.as_ptr();
623        let original_capacity = value.capacity();
624
625        let path = PathBuf::try_from(value).unwrap();
626
627        assert_eq!(path.as_str(), "foo::bar");
628        assert_eq!(path.inner.as_ptr(), original_ptr);
629        assert_eq!(path.inner.capacity(), original_capacity);
630    }
631
632    #[test]
633    fn exec_path() {
634        let path = PathBuf::new("$exec::bar::baz").unwrap();
635        assert!(path.is_absolute());
636        assert_eq!(path.components().count(), 4);
637        assert_eq!(path.last(), Some("baz"));
638        assert_eq!(path.first(), Some("$exec"));
639    }
640
641    #[test]
642    fn kernel_path() {
643        let path = PathBuf::new("$kernel::bar::baz").unwrap();
644        assert!(path.is_absolute());
645        assert_eq!(path.components().count(), 4);
646        assert_eq!(path.last(), Some("baz"));
647        assert_eq!(path.first(), Some("$kernel"));
648    }
649
650    #[test]
651    fn invalid_path_empty() {
652        let result = Path::validate("");
653        assert_matches!(result, Err(PathError::Empty));
654    }
655
656    #[test]
657    fn invalid_path_empty_component() {
658        let result = Path::validate("::");
659        assert_matches!(result, Err(PathError::EmptyComponent));
660    }
661
662    #[test]
663    fn invalid_path_trailing_delimiter() {
664        let result = Path::validate("foo::");
665        assert_matches!(result, Err(PathError::InvalidComponent(IdentError::Empty)));
666    }
667
668    #[test]
669    fn invalid_path_invalid_character() {
670        let result = Path::validate("#foo::bar");
671        assert_matches!(result, Err(PathError::InvalidComponent(IdentError::InvalidChars { .. })));
672    }
673
674    #[cfg(feature = "serde")]
675    #[test]
676    fn serde_deserialize_path_buf_canonicalizes_redundant_quotes() {
677        let path: PathBuf = serde_json::from_str(r#""\"foo\"::\"bar\"""#)
678            .expect("path deserialization must succeed");
679        assert_eq!(path.as_str(), "foo::bar");
680    }
681
682    #[cfg(feature = "serde")]
683    #[test]
684    fn serde_deserialize_path_buf_preserves_required_quotes() {
685        let path: PathBuf = serde_json::from_value(serde_json::Value::String(String::from(
686            "::foo::\"miden::base/account@0.1.0\"",
687        )))
688        .expect("path deserialization must succeed");
689        assert_eq!(path.as_str(), "::foo::\"miden::base/account@0.1.0\"");
690    }
691
692    #[cfg(feature = "serde")]
693    #[test]
694    fn serde_deserialize_path_buf_rejects_overflow_after_canonicalization() {
695        let component = format!("{}-", "a".repeat(254));
696        let mut source = String::new();
697        for i in 0..255 {
698            if i > 0 {
699                source.push_str("::");
700            }
701            source.push_str(&component);
702        }
703
704        let err = serde_json::from_value::<PathBuf>(serde_json::Value::String(source))
705            .expect_err("deserialization must fail when canonicalization exceeds u16::MAX bytes");
706        let message = format!("{err}");
707        assert!(message.contains("too long"), "unexpected error: {message}");
708    }
709}