1use alloc::{
2 borrow::Cow,
3 string::{String, ToString},
4 sync::Arc,
5 vec::Vec,
6};
7use core::{
8 fmt,
9 str::{self, FromStr},
10};
11
12use smallvec::smallvec;
13
14use crate::{
15 ByteReader, ByteWriter, Deserializable, DeserializationError, LibraryNamespace, Serializable,
16 Span,
17 ast::{Ident, IdentError},
18};
19
20#[derive(Debug, thiserror::Error)]
22pub enum PathError {
23 #[error("invalid library path: cannot be empty")]
24 Empty,
25 #[error("invalid library path component: cannot be empty")]
26 EmptyComponent,
27 #[error("invalid library path component: {0}")]
28 InvalidComponent(crate::ast::IdentError),
29 #[error("invalid library path: contains invalid utf8 byte sequences")]
30 InvalidUtf8,
31 #[error(transparent)]
32 InvalidNamespace(crate::library::LibraryNamespaceError),
33 #[error("cannot join a path with reserved name to other paths")]
34 UnsupportedJoin,
35}
36
37pub enum LibraryPathComponent<'a> {
42 Namespace(&'a LibraryNamespace),
44 Normal(&'a Ident),
46}
47
48impl<'a> LibraryPathComponent<'a> {
49 #[inline(always)]
51 pub fn as_str(&self) -> &'a str {
52 match self {
53 Self::Namespace(ns) => ns.as_str(),
54 Self::Normal(id) => id.as_str(),
55 }
56 }
57
58 #[inline]
60 pub fn to_ident(&self) -> Ident {
61 match self {
62 Self::Namespace(ns) => ns.to_ident(),
63 Self::Normal(id) => Ident::clone(id),
64 }
65 }
66}
67
68impl Eq for LibraryPathComponent<'_> {}
69
70impl PartialEq for LibraryPathComponent<'_> {
71 fn eq(&self, other: &Self) -> bool {
72 match (self, other) {
73 (Self::Namespace(a), Self::Namespace(b)) => a == b,
74 (Self::Normal(a), Self::Normal(b)) => a == b,
75 _ => false,
76 }
77 }
78}
79
80impl PartialEq<str> for LibraryPathComponent<'_> {
81 fn eq(&self, other: &str) -> bool {
82 self.as_ref().eq(other)
83 }
84}
85
86impl AsRef<str> for LibraryPathComponent<'_> {
87 fn as_ref(&self) -> &str {
88 match self {
89 Self::Namespace(ns) => ns.as_str(),
90 Self::Normal(ident) => ident.as_str(),
91 }
92 }
93}
94
95impl fmt::Display for LibraryPathComponent<'_> {
96 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
97 f.write_str(self.as_ref())
98 }
99}
100
101impl From<LibraryPathComponent<'_>> for Ident {
102 #[inline]
103 fn from(component: LibraryPathComponent<'_>) -> Self {
104 component.to_ident()
105 }
106}
107
108type Components = smallvec::SmallVec<[Ident; 1]>;
110
111#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
116pub struct LibraryPath {
117 inner: Arc<LibraryPathInner>,
118}
119
120#[derive(Default, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
123struct LibraryPathInner {
124 ns: LibraryNamespace,
126 components: Components,
128}
129
130impl LibraryPath {
131 pub fn new(source: impl AsRef<str>) -> Result<Self, PathError> {
147 let source = source.as_ref();
148 if source.is_empty() {
149 return Err(PathError::Empty);
150 }
151
152 let mut parts = source.split("::");
154 let ns = parts
155 .next()
156 .ok_or(PathError::Empty)
157 .and_then(|part| LibraryNamespace::new(part).map_err(PathError::InvalidNamespace))?;
158
159 let mut components = Components::default();
161 parts.map(Ident::new).try_for_each(|part| {
162 part.map_err(PathError::InvalidComponent).map(|c| components.push(c))
163 })?;
164
165 Ok(Self::make(ns, components))
166 }
167
168 pub fn new_from_components<I>(ns: LibraryNamespace, components: I) -> Self
170 where
171 I: IntoIterator<Item = Ident>,
172 {
173 Self::make(ns, components.into_iter().collect())
174 }
175
176 #[inline]
177 fn make(ns: LibraryNamespace, components: Components) -> Self {
178 Self {
179 inner: Arc::new(LibraryPathInner { ns, components }),
180 }
181 }
182}
183
184impl LibraryPath {
186 #[allow(clippy::len_without_is_empty)]
188 pub fn len(&self) -> usize {
189 self.inner.components.iter().map(|c| c.len()).sum::<usize>()
190 + self.inner.ns.as_str().len()
191 + (self.inner.components.len() * 2)
192 }
193
194 pub fn byte_len(&self) -> usize {
196 self.inner.components.iter().map(|c| c.len()).sum::<usize>()
197 + self.inner.ns.as_str().len()
198 + (self.inner.components.len() * 2)
199 }
200
201 pub fn path(&self) -> Cow<'_, str> {
203 if self.inner.components.is_empty() {
204 Cow::Borrowed(self.inner.ns.as_str())
205 } else {
206 Cow::Owned(self.to_string())
207 }
208 }
209
210 pub fn namespace(&self) -> &LibraryNamespace {
212 &self.inner.ns
213 }
214
215 pub fn last(&self) -> &str {
217 self.last_component().as_str()
218 }
219
220 pub fn last_component(&self) -> LibraryPathComponent<'_> {
222 self.inner
223 .components
224 .last()
225 .map(LibraryPathComponent::Normal)
226 .unwrap_or_else(|| LibraryPathComponent::Namespace(&self.inner.ns))
227 }
228
229 pub fn num_components(&self) -> usize {
233 self.inner.components.len() + 1
234 }
235
236 pub fn components(&self) -> impl Iterator<Item = LibraryPathComponent> + '_ {
238 core::iter::once(LibraryPathComponent::Namespace(&self.inner.ns))
239 .chain(self.inner.components.iter().map(LibraryPathComponent::Normal))
240 }
241
242 pub fn is_kernel_path(&self) -> bool {
244 matches!(self.inner.ns, LibraryNamespace::Kernel)
245 }
246
247 pub fn is_exec_path(&self) -> bool {
249 matches!(self.inner.ns, LibraryNamespace::Exec)
250 }
251
252 pub fn is_anon_path(&self) -> bool {
254 matches!(self.inner.ns, LibraryNamespace::Anon)
255 }
256
257 pub fn starts_with(&self, other: &LibraryPath) -> bool {
259 let mut a = self.components();
260 let mut b = other.components();
261 loop {
262 match (a.next(), b.next()) {
263 (_, None) => break true,
265 (None, _) => break false,
267 (Some(a), Some(b)) => {
268 if a != b {
270 break false;
271 }
272 },
273 }
274 }
275 }
276}
277
278impl LibraryPath {
280 pub fn set_namespace(&mut self, ns: LibraryNamespace) {
282 let inner = Arc::make_mut(&mut self.inner);
283 inner.ns = ns;
284 }
285
286 pub fn join(&self, other: &Self) -> Result<Self, PathError> {
293 if other.inner.ns.is_reserved() {
294 return Err(PathError::UnsupportedJoin);
295 }
296
297 let mut path = self.clone();
298 {
299 let inner = Arc::make_mut(&mut path.inner);
300 inner.components.push(other.inner.ns.to_ident());
301 inner.components.extend(other.inner.components.iter().cloned());
302 }
303
304 Ok(path)
305 }
306
307 pub fn push(&mut self, component: impl AsRef<str>) -> Result<(), PathError> {
311 let component = component.as_ref().parse::<Ident>().map_err(PathError::InvalidComponent)?;
312 self.push_ident(component);
313 Ok(())
314 }
315
316 pub fn push_ident(&mut self, component: Ident) {
318 let inner = Arc::make_mut(&mut self.inner);
319 inner.components.push(component);
320 }
321
322 pub fn append<S>(&self, component: S) -> Result<Self, PathError>
326 where
327 S: AsRef<str>,
328 {
329 let mut path = self.clone();
330 path.push(component)?;
331 Ok(path)
332 }
333
334 pub fn append_ident(&self, component: Ident) -> Result<Self, PathError> {
338 let mut path = self.clone();
339 path.push_ident(component);
340 Ok(path)
341 }
342
343 pub fn prepend<S>(&self, component: S) -> Result<Self, PathError>
352 where
353 S: AsRef<str>,
354 {
355 let ns = component
356 .as_ref()
357 .parse::<LibraryNamespace>()
358 .map_err(PathError::InvalidNamespace)?;
359 let component = self.inner.ns.to_ident();
360 let mut components = smallvec![component];
361 components.extend(self.inner.components.iter().cloned());
362 Ok(Self::make(ns, components))
363 }
364
365 pub fn pop(&mut self) -> Option<Ident> {
367 let inner = Arc::make_mut(&mut self.inner);
368 inner.components.pop()
369 }
370
371 pub fn strip_last(&self) -> Option<Self> {
374 match self.inner.components.len() {
375 0 => None,
376 1 => Some(Self::make(self.inner.ns.clone(), smallvec![])),
377 _ => {
378 let ns = self.inner.ns.clone();
379 let mut components = self.inner.components.clone();
380 components.pop();
381 Some(Self::make(ns, components))
382 },
383 }
384 }
385
386 pub fn validate<S>(source: S) -> Result<usize, PathError>
391 where
392 S: AsRef<str>,
393 {
394 let source = source.as_ref();
395
396 let mut count = 0;
397 let mut components = source.split("::");
398
399 let ns = components.next().ok_or(PathError::Empty)?;
400 LibraryNamespace::validate(ns).map_err(PathError::InvalidNamespace)?;
401 count += 1;
402
403 for component in components {
404 validate_component(component)?;
405 count += 1;
406 }
407
408 Ok(count)
409 }
410
411 pub fn append_unchecked<S>(&self, component: S) -> Self
415 where
416 S: AsRef<str>,
417 {
418 let component = component.as_ref().to_string().into_boxed_str();
419 let component = Ident::from_raw_parts(Span::unknown(Arc::from(component)));
420 let mut path = self.clone();
421 path.push_ident(component);
422 path
423 }
424}
425
426impl<'a> TryFrom<Vec<LibraryPathComponent<'a>>> for LibraryPath {
427 type Error = PathError;
428 fn try_from(iter: Vec<LibraryPathComponent<'a>>) -> Result<Self, Self::Error> {
429 let mut iter = iter.into_iter();
430 let ns = match iter.next() {
431 None => return Err(PathError::Empty),
432 Some(LibraryPathComponent::Namespace(ns)) => ns.clone(),
433 Some(LibraryPathComponent::Normal(ident)) => {
434 LibraryNamespace::try_from(ident.clone()).map_err(PathError::InvalidNamespace)?
435 },
436 };
437 let mut components = Components::default();
438 for component in iter {
439 match component {
440 LibraryPathComponent::Normal(ident) => components.push(ident.clone()),
441 LibraryPathComponent::Namespace(LibraryNamespace::User(name)) => {
442 components.push(Ident::from_raw_parts(Span::unknown(name.clone())));
443 },
444 LibraryPathComponent::Namespace(_) => return Err(PathError::UnsupportedJoin),
445 }
446 }
447 Ok(Self::make(ns, components))
448 }
449}
450
451impl From<LibraryNamespace> for LibraryPath {
452 fn from(ns: LibraryNamespace) -> Self {
453 Self::make(ns, smallvec![])
454 }
455}
456
457impl From<LibraryPath> for String {
458 fn from(path: LibraryPath) -> Self {
459 path.to_string()
460 }
461}
462
463impl TryFrom<String> for LibraryPath {
464 type Error = PathError;
465
466 #[inline]
467 fn try_from(value: String) -> Result<Self, Self::Error> {
468 Self::new(value)
469 }
470}
471
472impl<'a> TryFrom<&'a str> for LibraryPath {
473 type Error = PathError;
474
475 #[inline]
476 fn try_from(value: &'a str) -> Result<Self, Self::Error> {
477 Self::new(value)
478 }
479}
480
481impl FromStr for LibraryPath {
482 type Err = PathError;
483
484 #[inline]
485 fn from_str(value: &str) -> Result<Self, Self::Err> {
486 Self::new(value)
487 }
488}
489
490impl Serializable for LibraryPath {
491 fn write_into<W: ByteWriter>(&self, target: &mut W) {
492 let len = self.byte_len();
493
494 target.write_u16(len as u16);
495 target.write_bytes(self.inner.ns.as_str().as_bytes());
496 for component in self.inner.components.iter() {
497 target.write_bytes(b"::");
498 target.write_bytes(component.as_str().as_bytes());
499 }
500 }
501}
502
503impl Deserializable for LibraryPath {
504 fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
505 let len = source.read_u16()? as usize;
506 let path = source.read_slice(len)?;
507 let path =
508 str::from_utf8(path).map_err(|e| DeserializationError::InvalidValue(e.to_string()))?;
509 Self::new(path).map_err(|e| DeserializationError::InvalidValue(e.to_string()))
510 }
511}
512
513impl fmt::Display for LibraryPath {
514 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
515 write!(f, "{}", self.inner.ns)?;
516 for component in self.inner.components.iter() {
517 write!(f, "::{component}")?;
518 }
519 Ok(())
520 }
521}
522
523fn validate_component(component: &str) -> Result<(), PathError> {
524 if component.is_empty() {
525 Err(PathError::EmptyComponent)
526 } else if component.len() > LibraryNamespace::MAX_LENGTH {
527 Err(PathError::InvalidComponent(IdentError::InvalidLength {
528 max: LibraryNamespace::MAX_LENGTH,
529 }))
530 } else {
531 Ident::validate(component).map_err(PathError::InvalidComponent)
532 }
533}
534
535#[cfg(feature = "testing")]
539impl proptest::prelude::Arbitrary for LibraryPath {
540 type Parameters = ();
541
542 fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
543 use proptest::prelude::*;
544
545 let wasm_cm_style = LibraryPath::new_from_components(
546 LibraryNamespace::Anon,
547 [Ident::new("namespace-kebab:package-kebab/interface-kebab@1.0.0").unwrap()],
548 );
549 let path_len_2 = LibraryPath::new_from_components(
550 LibraryNamespace::User("user_ns".into()),
551 [Ident::new("user_module").unwrap()],
552 );
553 let path_len_3 = LibraryPath::new_from_components(
554 LibraryNamespace::User("userns".into()),
555 [Ident::new("user_path1").unwrap(), Ident::new("user_module").unwrap()],
556 );
557 prop_oneof![Just(wasm_cm_style), Just(path_len_2), Just(path_len_3)].boxed()
558 }
559
560 type Strategy = proptest::prelude::BoxedStrategy<Self>;
561}
562
563#[cfg(test)]
568mod tests {
569
570 use proptest::prelude::*;
571 use vm_core::{
572 assert_matches,
573 utils::{Deserializable, Serializable},
574 };
575
576 use super::{super::LibraryNamespaceError, IdentError, LibraryPath, PathError};
577
578 #[test]
579 fn new_path() {
580 let path = LibraryPath::new("foo").unwrap();
581 assert_eq!(path.num_components(), 1);
582
583 let path = LibraryPath::new("foo::bar").unwrap();
584 assert_eq!(path.num_components(), 2);
585
586 let path = LibraryPath::new("foo::bar::baz").unwrap();
587 assert_eq!(path.num_components(), 3);
588
589 let path = LibraryPath::new("miden:base/account@0.1.0").unwrap();
590 assert_eq!(path.num_components(), 1);
591
592 let path = LibraryPath::new("#exec::bar::baz").unwrap();
593 assert_eq!(path.num_components(), 3);
594
595 let path = LibraryPath::new("#sys::bar::baz").unwrap();
596 assert_eq!(path.num_components(), 3);
597 }
598
599 #[test]
600 fn new_path_fail() {
601 let path = LibraryPath::new("");
602 assert_matches!(path, Err(PathError::Empty));
603
604 let path = LibraryPath::new("::");
605 assert_matches!(path, Err(PathError::InvalidNamespace(LibraryNamespaceError::Empty)));
606
607 let path = LibraryPath::new("foo::");
608 assert_matches!(path, Err(PathError::InvalidComponent(IdentError::Empty)));
609
610 let path = LibraryPath::new("::foo");
611 assert_matches!(path, Err(PathError::InvalidNamespace(LibraryNamespaceError::Empty)));
612
613 let path = LibraryPath::new("#foo::bar");
614 assert_matches!(
615 path,
616 Err(PathError::InvalidNamespace(LibraryNamespaceError::InvalidStart))
617 );
618 }
619
620 proptest! {
621 #[test]
622 fn path_serialization_roundtrip(path in any::<LibraryPath>()) {
623 let bytes = path.to_bytes();
624 let deserialized = LibraryPath::read_from_bytes(&bytes).unwrap();
625 assert_eq!(path, deserialized);
626 }
627 }
628}