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().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
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 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 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}