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_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
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<'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#[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 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 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}