//! Definitions of name-related helpers and newtypes, primarily for the //! component model.
usecrate::prelude::*; usecrate::{Result, WasmFeatures}; use core::borrow::Borrow; use core::cmp::Ordering; use core::fmt; use core::hash::{Hash, Hasher}; use core::ops::Deref; use semver::Version;
/// Represents a kebab string slice used in validation. /// /// This is a wrapper around `str` that ensures the slice is /// a valid kebab case string according to the component model /// specification. /// /// It also provides an equality and hashing implementation /// that ignores ASCII case. #[derive(Debug, Eq)] #[repr(transparent)] pubstruct KebabStr(str);
impl KebabStr { /// Creates a new kebab string slice. /// /// Returns `None` if the given string is not a valid kebab string. pub fn new<'a>(s: impl AsRef<str> + 'a) -> Option<&'a Self> { let s = Self::new_unchecked(s); if s.is_kebab_case() { Some(s) } else { None }
}
pub(crate) fn new_unchecked<'a>(s: impl AsRef<str> + 'a) -> &'a Self { // Safety: `KebabStr` is a transparent wrapper around `str` // Therefore transmuting `&str` to `&KebabStr` is safe. #[allow(unsafe_code)] unsafe {
core::mem::transmute::<_, &Self>(s.as_ref())
}
}
/// Represents an owned kebab string for validation. /// /// This is a wrapper around `String` that ensures the string is /// a valid kebab case string according to the component model /// specification. /// /// It also provides an equality and hashing implementation /// that ignores ASCII case. #[derive(Debug, Clone, Eq)] pubstruct KebabString(String);
impl KebabString { /// Creates a new kebab string. /// /// Returns `None` if the given string is not a valid kebab string. pub fn new(s: impl Into<String>) -> Option<Self> { let s = s.into(); if KebabStr::new(&s).is_some() {
Some(Self(s))
} else {
None
}
}
/// An import or export name in the component model which is backed by `T`, /// which defaults to `String`. /// /// This name can be either: /// /// * a plain label or "kebab string": `a-b-c` /// * a plain method name : `[method]a-b.c-d` /// * a plain static method name : `[static]a-b.c-d` /// * a plain constructor: `[constructor]a-b` /// * an interface name: `wasi:cli/reactor@0.1.0` /// * a dependency name: `locked-dep=foo:bar/baz` /// * a URL name: `url=https://..` /// * a hash name: `integrity=sha256:...` /// /// # Equality and hashing /// /// Note that this type the `[method]...` and `[static]...` variants are /// considered equal and hash to the same value. This enables disallowing /// clashes between the two where method name overlap cannot happen. #[derive(Clone)] pubstruct ComponentName {
raw: String,
kind: ParsedComponentNameKind,
}
impl ComponentName { /// Attempts to parse `name` as a valid component name, returning `Err` if /// it's not valid. pub fn new(name: &str, offset: usize) -> Result<ComponentName> { Self::new_with_features(name, offset, WasmFeatures::default())
}
/// Attempts to parse `name` as a valid component name, returning `Err` if /// it's not valid. /// /// `features` can be used to enable or disable validation of certain forms /// of supported import names. pub fn new_with_features(name: &str, offset: usize, features: WasmFeatures) -> Result<Self> { letmut parser = ComponentNameParser {
next: name,
offset,
features,
}; let kind = parser.parse()?; if !parser.next.is_empty() {
bail!(offset, "trailing characters found: `{}`", parser.next);
}
Ok(ComponentName {
raw: name.to_string(),
kind,
})
}
/// Returns the [`ComponentNameKind`] corresponding to this name. pub fn kind(&self) -> ComponentNameKind<'_> { use ComponentNameKind::*; use ParsedComponentNameKind as PK; matchself.kind {
PK::Label => Label(KebabStr::new_unchecked(&self.raw)),
PK::Constructor => Constructor(KebabStr::new_unchecked(&self.raw[CONSTRUCTOR.len()..])),
PK::Method => Method(ResourceFunc(&self.raw[METHOD.len()..])),
PK::Static => Static(ResourceFunc(&self.raw[STATIC.len()..])),
PK::Interface => Interface(InterfaceName(&self.raw)),
PK::Dependency => Dependency(DependencyName(&self.raw)),
PK::Url => Url(UrlName(&self.raw)),
PK::Hash => Hash(HashName(&self.raw)),
}
}
/// Returns the raw underlying name as a string. pub fn as_str(&self) -> &str {
&self.raw
}
}
Method(name) | Static(name) => { // `l.l` hashes the same as `l` since they're equal above, // otherwise everything is hashed as `a.b` with a unique // prefix. if name.resource() == name.method() {
(0u8, name.resource()).hash(hasher)
} else {
(2u8, name).hash(hasher)
}
}
/// A resource name and its function, stored as `a.b`. #[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] pubstruct ResourceFunc<'a>(&'a str);
impl<'a> ResourceFunc<'a> { /// Returns the underlying string as `a.b` pub fn as_str(&self) -> &'a str { self.0
}
/// Returns the resource name or the `a` in `a.b` pub fn resource(&self) -> &'a KebabStr { let dot = self.0.find('.').unwrap();
KebabStr::new_unchecked(&self.0[..dot])
}
/// Returns the method name or the `b` in `a.b` pub fn method(&self) -> &'a KebabStr { let dot = self.0.find('.').unwrap();
KebabStr::new_unchecked(&self.0[dot + 1..])
}
}
/// An interface name, stored as `a:b/c@1.2.3` #[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] pubstruct InterfaceName<'a>(&'a str);
/// Returns the `a:b` in `a:b:c/d/e` pub fn namespace(&self) -> &'a KebabStr { let colon = self.0.rfind(':').unwrap();
KebabStr::new_unchecked(&self.0[..colon])
}
/// Returns the `c` in `a:b:c/d/e` pub fn package(&self) -> &'a KebabStr { let colon = self.0.rfind(':').unwrap(); let slash = self.0.find('/').unwrap();
KebabStr::new_unchecked(&self.0[colon + 1..slash])
}
/// Returns the `d` in `a:b:c/d/e`. pub fn interface(&self) -> &'a KebabStr { let projection = self.projection(); let slash = projection.find('/').unwrap_or(projection.len());
KebabStr::new_unchecked(&projection[..slash])
}
/// Returns the `d/e` in `a:b:c/d/e` pub fn projection(&self) -> &'a KebabStr { let slash = self.0.find('/').unwrap(); let at = self.0.find('@').unwrap_or(self.0.len());
KebabStr::new_unchecked(&self.0[slash + 1..at])
}
/// Returns the `1.2.3` in `a:b:c/d/e@1.2.3` pub fn version(&self) -> Option<Version> { let at = self.0.find('@')?;
Some(Version::parse(&self.0[at + 1..]).unwrap())
}
}
/// A dependency on an implementation either as `locked-dep=...` or /// `unlocked-dep=...` #[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] pubstruct DependencyName<'a>(&'a str);
/// A dependency on an implementation either as `url=...` #[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] pubstruct UrlName<'a>(&'a str);
/// A dependency on an implementation either as `integrity=...`. #[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd)] pubstruct HashName<'a>(&'a str);
// A small helper structure to parse `self.next` which is an import or export // name. // // Methods will update `self.next` as they go along and `self.offset` is used // for error messages. struct ComponentNameParser<'a> {
next: &'a str,
offset: usize,
features: WasmFeatures,
}
ifself.eat_str("@") { let version = matchself.eat_up_to('>') {
Some(version) => version,
None => self.take_rest(),
};
self.semver(version)?;
}
Ok(())
}
// pkgpath ::= <namespace>+ <label> <projection>*
fn pkg_path(&mutself, require_projection: bool) -> Result<()> { // There must be at least one package namespace self.take_lowercase_kebab()?; self.expect_str(":")?; self.take_lowercase_kebab()?;
ifself.features.cm_nested_names() { // Take the remaining package namespaces and name whileself.next.starts_with(':') { self.expect_str(":")?; self.take_lowercase_kebab()?;
}
}
// Take the projections ifself.next.starts_with('/') { self.expect_str("/")?; self.take_kebab()?;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.