webc 8.0.0

WebContainer implementation for wapm.io
Documentation
//! Strongly-typed definitions for the various free-form "annotations" fields
//! in a [`crate::metadata::Manifest`].

use std::path::PathBuf;

/// The base URI used by a [`Wasi`] runner.
pub const WASI_RUNNER_URI: &str = "https://webc.org/runner/wasi";

/// The base URI used by a [`Wcgi`] runner.
pub const WCGI_RUNNER_URI: &str = "https://webc.org/runner/wcgi";

/// The base URI used by an [`Emscripten`] runner.
pub const EMSCRIPTEN_RUNNER_URI: &str = "https://webc.org/runner/emscripten";

/// The base URI used by a [WASM4] runner.
///
/// [WASM4]: https://wasm4.org/
pub const WASM4_RUNNER_URI: &str = "https://webc.org/runner/wasm4";

/// Annotations used by WASI runners.
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub struct Wasi {
    #[deprecated(since = "5.5.0", note = "Use the explicit \"atom\" annotation instead")]
    pub atom: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub package: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub env: Option<Vec<String>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub main_args: Option<Vec<String>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub mount_atom_in_volume: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub cwd: Option<PathBuf>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub exec_name: Option<String>,
}

impl Wasi {
    /// The name commonly used when storing this in an annotations dictionary.
    pub const KEY: &'static str = "wasi";

    #[allow(deprecated)]
    pub fn new(atom: impl Into<String>) -> Self {
        Wasi {
            atom: atom.into(),
            package: None,
            env: None,
            main_args: None,
            mount_atom_in_volume: None,
            cwd: None,
            exec_name: None,
        }
    }

    pub fn with_env(&mut self, key: impl AsRef<str>, value: impl AsRef<str>) -> &mut Self {
        let key = key.as_ref();
        let value = value.as_ref();
        self.env
            .get_or_insert_with(Vec::new)
            .push(format!("{key}={value}"));
        self
    }
}

/// Annotations used by WCGI runners.
#[derive(Default, Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub struct Wcgi {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub dialect: Option<String>,
}

impl Wcgi {
    /// The name commonly used when storing this in an annotations dictionary.
    pub const KEY: &'static str = "wcgi";
}

/// Package-level annotations specific to the WebAssembly Package Manager.
#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
#[non_exhaustive]
#[serde(rename_all = "kebab-case")]
pub struct Wapm {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub version: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub license: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub license_file: Option<VolumeSpecificPath>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub readme: Option<VolumeSpecificPath>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub repository: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub homepage: Option<String>,
    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
    pub private: bool,
}

impl Wapm {
    /// The name commonly used when storing this in an annotations dictionary.
    pub const KEY: &'static str = "wapm";

    pub fn new(name: Option<String>, version: Option<String>, description: Option<String>) -> Self {
        Wapm {
            name,
            version,
            description,
            license: None,
            license_file: None,
            readme: None,
            repository: None,
            homepage: None,
            private: false,
        }
    }
}

/// The path for an item inside a particular volume.
#[derive(
    Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, serde::Deserialize, serde::Serialize,
)]
pub struct VolumeSpecificPath {
    pub volume: String,
    pub path: String,
}

/// Annotations used by Emscripten runners.
#[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Emscripten {
    #[deprecated(since = "5.5.0", note = "Use the explicit \"atom\" annotation")]
    #[serde(default)]
    pub atom: Option<String>,
    #[serde(default)]
    pub package: Option<String>,
    #[serde(default)]
    pub env: Option<Vec<String>>,
    #[serde(default)]
    pub main_args: Option<Vec<String>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub mount_atom_in_volume: Option<String>,
}

impl Emscripten {
    /// The name commonly used when storing this in an annotations dictionary.
    pub const KEY: &'static str = "emscripten";
}

/// A list of entries used when constructing a filesystem based on a package and
/// its dependency tree.
///
/// Filesystem mappings work by taking a directory from a volume, where the
/// volume may come from the current package or a dependency, and making it
/// available ("mounted") in a filesystem at a particular location.
///
/// The order of [`FileSystemMapping`] entries *is* important because it will
/// inform consumers how to deal with nested mounts.
#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct FileSystemMappings(pub Vec<FileSystemMapping>);

impl std::ops::Deref for FileSystemMappings {
    type Target = Vec<FileSystemMapping>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl<T> PartialEq<T> for FileSystemMappings
where
    Vec<FileSystemMapping>: PartialEq<T>,
{
    fn eq(&self, other: &T) -> bool {
        self.0 == *other
    }
}

impl IntoIterator for FileSystemMappings {
    type Item = FileSystemMapping;

    type IntoIter = <Vec<FileSystemMapping> as IntoIterator>::IntoIter;

    fn into_iter(self) -> Self::IntoIter {
        self.0.into_iter()
    }
}

impl FileSystemMappings {
    /// The name commonly used when storing this in an annotations dictionary.
    pub const KEY: &'static str = "fs";
}

/// An entry used when constructing a filesystem based on a package and its
/// dependency tree.
#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct FileSystemMapping {
    /// The alias of the dependency this filesystem mapping should come from.
    ///
    /// If not provided, the current package is used.
    pub from: Option<String>,
    /// The name of the volume.
    pub volume_name: String,
    /// The path of the mapped item within its original volume.
    #[serde(alias = "original_path")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub host_path: Option<String>,
    /// Where the volume should be mounted in the resulting filesystem.
    pub mount_path: String,
}

/// A command annotation which specifies which atom implements a command.
///
/// Historically, each runner would have its own annotations for tracking the
/// atom they use (see [`Wasi::atom`] and [`Emscripten::atom`]). As part of
/// fixing [#172](https://github.com/wasmerio/pirita/issues/172), the explicit
/// annotations were merged into a common [`Atom`] annotation.
#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
#[non_exhaustive]
pub struct Atom {
    /// The name of the atom (as passed to
    /// [`Container::get_atom()`][crate::Container::get_atom]).
    pub name: String,
    /// The name of the dependency referred to by the
    /// [`Manifest::use_map`][crate::metadata::Manifest::use_map].
    ///
    /// If this is `None`, it means the atom comes from the current package.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub dependency: Option<String>,
}

impl Atom {
    /// The name commonly used when storing this in an annotations dictionary.
    pub const KEY: &'static str = "atom";

    pub fn new(name: impl Into<String>, dependency: impl Into<Option<String>>) -> Self {
        Atom {
            name: name.into(),
            dependency: dependency.into(),
        }
    }
}