miden_assembly_syntax/ast/path/
path_buf.rs1use 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#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
20#[cfg_attr(
21 all(feature = "arbitrary", test),
22 miden_test_serialization_macros::serialization_test
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
58impl PathBuf {
60 pub fn with_capacity(capacity: usize) -> Self {
62 Self { inner: String::with_capacity(capacity) }
63 }
64
65 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 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 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 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 #[inline]
124 pub fn as_path(&self) -> &Path {
125 self.as_ref()
126 }
127
128 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 unsafe { alloc::boxed::Box::from_raw(inner as *mut Path) }
134 }
135}
136
137impl PathBuf {
139 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 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 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 self.inner.clear();
202 self.inner.push_str("::");
203 },
204 component => {
205 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 self.inner.push_str("::");
217 self.inner.push_str(component);
218 } else if requires_quoting && !is_quoted {
219 self.inner.push('"');
221 self.inner.push_str(component);
222 self.inner.push('"');
223 } else if !requires_quoting && is_quoted {
224 self.inner.push_str(&component[1..(component.len() - 1)]);
226 } else {
227 self.inner.push_str(component);
229 }
230 },
231 }
232 }
233
234 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 Visitor<'_> 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, value: &str) -> Result<Self::Value, E>
417 where
418 E: serde::de::Error,
419 {
420 Path::validate(value)
421 .map_err(serde::de::Error::custom)?
422 .canonicalize()
423 .map_err(serde::de::Error::custom)
424 }
425
426 fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
427 where
428 E: serde::de::Error,
429 {
430 self.visit_str(&value)
431 }
432 }
433
434 deserializer.deserialize_any(PathVisitor)
435 }
436}
437
438impl fmt::Display for PathBuf {
439 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
440 fmt::Display::fmt(self.as_path(), f)
441 }
442}
443
444#[cfg(test)]
449mod tests {
450 use alloc::string::String;
451 use core::assert_matches;
452
453 use super::{PathBuf, PathError};
454 use crate::{Path, PathComponent, ast::IdentError};
455
456 #[test]
457 fn single_component_path() {
458 let path = PathBuf::new("foo").unwrap();
459 assert!(!path.is_absolute());
460 assert_eq!(path.components().count(), 1);
461 assert_eq!(path.last(), Some("foo"));
462 assert_eq!(path.first(), Some("foo"));
463 }
464
465 #[test]
466 fn relative_path_two_components() {
467 let path = PathBuf::new("foo::bar").unwrap();
468 assert!(!path.is_absolute());
469 assert_eq!(path.components().count(), 2);
470 assert_eq!(path.last(), Some("bar"));
471 assert_eq!(path.first(), Some("foo"));
472 }
473
474 #[test]
475 fn relative_path_three_components() {
476 let path = PathBuf::new("foo::bar::baz").unwrap();
477 assert!(!path.is_absolute());
478 assert_eq!(path.components().count(), 3);
479 assert_eq!(path.last(), Some("baz"));
480 assert_eq!(path.first(), Some("foo"));
481 assert_eq!(path.parent().map(Path::as_str), Some("foo::bar"));
482 }
483
484 #[test]
485 fn single_quoted_component() {
486 let path = PathBuf::new("\"miden::base/account@0.1.0\"").unwrap();
487 assert!(!path.is_absolute());
488 assert_eq!(path.components().count(), 1);
489 assert_eq!(path.last(), Some("miden::base/account@0.1.0"));
490 assert_eq!(path.first(), Some("miden::base/account@0.1.0"));
491 }
492
493 #[test]
494 fn trailing_quoted_component() {
495 let path = PathBuf::new("foo::\"miden::base/account@0.1.0\"").unwrap();
496 assert!(!path.is_absolute());
497 assert_eq!(path.components().count(), 2);
498 assert_eq!(path.last(), Some("miden::base/account@0.1.0"));
499 assert_eq!(path.first(), Some("foo"));
500 }
501
502 #[test]
503 fn interspersed_quoted_component() {
504 let path = PathBuf::new("foo::\"miden::base/account@0.1.0\"::item").unwrap();
505 assert!(!path.is_absolute());
506 assert_eq!(path.components().count(), 3);
507 assert_eq!(path.last(), Some("item"));
508 assert_eq!(path.first(), Some("foo"));
509 assert_eq!(path.parent().map(Path::as_str), Some("foo::\"miden::base/account@0.1.0\""));
510 }
511
512 #[test]
513 fn mixed_quoted_components_regression() {
514 let component = "::root_ns:root@1.0.0";
515 let module = "abi_transform_tx_kernel_get_inputs_4";
516 let function = "miden::protocol::active_note::get_inputs";
517 let quoted_function = "\"miden::protocol::active_note::get_inputs\"";
518
519 let p1 = PathBuf::new(&format!("{component}::{module}::\"{function}\"")).unwrap();
520 let mut p2 = PathBuf::new(component).unwrap();
521 p2.push_component(module);
522 p2.push_component(function);
523
524 assert_eq!(p1, p2);
525
526 let mut p1components = p1.components();
529 let mut p2components = p2.components();
530
531 let p1root = p1components.next().unwrap().unwrap();
532 let p2root = p2components.next().unwrap().unwrap();
533
534 assert_eq!(p1root, PathComponent::Root);
535 assert_eq!(p1root, p2root);
536
537 let p1component = p1components.next().unwrap().unwrap();
538 let p2component = p2components.next().unwrap().unwrap();
539
540 assert_eq!(p1component, PathComponent::Normal("\"root_ns:root@1.0.0\""));
541 assert_eq!(p1component, p2component);
542
543 let p1module = p1components.next().unwrap().unwrap();
544 let p2module = p2components.next().unwrap().unwrap();
545
546 assert_eq!(p1module, PathComponent::Normal(module));
547 assert_eq!(p1module, p2module);
548
549 let p1function = p1components.next().unwrap().unwrap();
550 let p2function = p2components.next().unwrap().unwrap();
551
552 assert_eq!(p1function, PathComponent::Normal(quoted_function));
553 assert_eq!(p1function, p2function);
554
555 let mut p1components = p1.components();
558 let mut p2components = p2.components();
559
560 let p1function = p1components.next_back().unwrap().unwrap();
561 let p2function = p2components.next_back().unwrap().unwrap();
562
563 assert_eq!(p1function, PathComponent::Normal(quoted_function));
564 assert_eq!(p1function, p2function);
565
566 let p1module = p1components.next_back().unwrap().unwrap();
567 let p2module = p2components.next_back().unwrap().unwrap();
568
569 assert_eq!(p1module, PathComponent::Normal(module));
570 assert_eq!(p1module, p2module);
571
572 let p1component = p1components.next_back().unwrap().unwrap();
573 let p2component = p2components.next_back().unwrap().unwrap();
574
575 assert_eq!(p1component, PathComponent::Normal("\"root_ns:root@1.0.0\""));
576 assert_eq!(p1component, p2component);
577
578 let p1root = p1components.next_back().unwrap().unwrap();
579 let p2root = p2components.next_back().unwrap().unwrap();
580
581 assert_eq!(p1root, PathComponent::Root);
582 assert_eq!(p1root, p2root);
583
584 assert!(p1.is_absolute());
585 assert_eq!(p1.components().count(), 4);
586 assert_eq!(p1.last(), Some(function));
587 assert_eq!(p1.first(), Some("root_ns:root@1.0.0"));
588 let parent = p1.parent().unwrap();
589 assert_eq!(
590 parent.as_str(),
591 "::\"root_ns:root@1.0.0\"::abi_transform_tx_kernel_get_inputs_4"
592 );
593 assert_eq!(parent.parent().map(Path::as_str), Some("::\"root_ns:root@1.0.0\""));
594
595 assert!(p2.is_absolute());
596 assert_eq!(p2.components().count(), 4);
597 assert_eq!(p2.last(), Some(function));
598 assert_eq!(p2.first(), Some("root_ns:root@1.0.0"));
599 let parent = p2.parent().unwrap();
600 assert_eq!(
601 parent.as_str(),
602 "::\"root_ns:root@1.0.0\"::abi_transform_tx_kernel_get_inputs_4"
603 );
604 assert_eq!(parent.parent().map(Path::as_str), Some("::\"root_ns:root@1.0.0\""));
605 }
606
607 #[test]
608 fn try_from_string_canonicalizes_like_str() {
609 let from_str = PathBuf::try_from("foo::\"bar\"").unwrap();
610 let from_string = PathBuf::try_from(String::from("foo::\"bar\"")).unwrap();
611
612 assert_eq!(from_string, from_str);
613 assert_eq!(from_string.as_str(), "foo::bar");
614 }
615
616 #[test]
617 fn try_from_string_reuses_canonical_allocation() {
618 let value = String::from("foo::bar");
619 let original_ptr = value.as_ptr();
620 let original_capacity = value.capacity();
621
622 let path = PathBuf::try_from(value).unwrap();
623
624 assert_eq!(path.as_str(), "foo::bar");
625 assert_eq!(path.inner.as_ptr(), original_ptr);
626 assert_eq!(path.inner.capacity(), original_capacity);
627 }
628
629 #[test]
630 fn exec_path() {
631 let path = PathBuf::new("$exec::bar::baz").unwrap();
632 assert!(path.is_absolute());
633 assert_eq!(path.components().count(), 4);
634 assert_eq!(path.last(), Some("baz"));
635 assert_eq!(path.first(), Some("$exec"));
636 }
637
638 #[test]
639 fn kernel_path() {
640 let path = PathBuf::new("$kernel::bar::baz").unwrap();
641 assert!(path.is_absolute());
642 assert_eq!(path.components().count(), 4);
643 assert_eq!(path.last(), Some("baz"));
644 assert_eq!(path.first(), Some("$kernel"));
645 }
646
647 #[test]
648 fn invalid_path_empty() {
649 let result = Path::validate("");
650 assert_matches!(result, Err(PathError::Empty));
651 }
652
653 #[test]
654 fn invalid_path_empty_component() {
655 let result = Path::validate("::");
656 assert_matches!(result, Err(PathError::EmptyComponent));
657 }
658
659 #[test]
660 fn invalid_path_trailing_delimiter() {
661 let result = Path::validate("foo::");
662 assert_matches!(result, Err(PathError::InvalidComponent(IdentError::Empty)));
663 }
664
665 #[test]
666 fn invalid_path_invalid_character() {
667 let result = Path::validate("#foo::bar");
668 assert_matches!(result, Err(PathError::InvalidComponent(IdentError::InvalidChars { .. })));
669 }
670
671 #[cfg(feature = "serde")]
672 #[test]
673 fn serde_deserialize_path_buf_canonicalizes_redundant_quotes() {
674 let path: PathBuf = serde_json::from_str(r#""\"foo\"::\"bar\"""#)
675 .expect("path deserialization must succeed");
676 assert_eq!(path.as_str(), "foo::bar");
677 }
678
679 #[cfg(feature = "serde")]
680 #[test]
681 fn serde_deserialize_path_buf_preserves_required_quotes() {
682 let path: PathBuf = serde_json::from_value(serde_json::Value::String(String::from(
683 "::foo::\"miden::base/account@0.1.0\"",
684 )))
685 .expect("path deserialization must succeed");
686 assert_eq!(path.as_str(), "::foo::\"miden::base/account@0.1.0\"");
687 }
688
689 #[cfg(feature = "serde")]
690 #[test]
691 fn serde_deserialize_path_buf_rejects_overflow_after_canonicalization() {
692 let component = format!("{}-", "a".repeat(254));
693 let mut source = String::new();
694 for i in 0..255 {
695 if i > 0 {
696 source.push_str("::");
697 }
698 source.push_str(&component);
699 }
700
701 let err = serde_json::from_value::<PathBuf>(serde_json::Value::String(source))
702 .expect_err("deserialization must fail when canonicalization exceeds u16::MAX bytes");
703 let message = format!("{err}");
704 assert!(message.contains("too long"), "unexpected error: {message}");
705 }
706}