#![allow(missing_docs)]
#![deny(missing_debug_implementations)]
pub mod annotations;
use std::{collections::BTreeMap, str::FromStr};
use crate::indexmap::IndexMap;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use url::Url;
use wasmer_toml::ModuleReference;
use crate::{
metadata::annotations::{FileSystemMappings, Wapm},
v1::Error,
};
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Manifest {
#[serde(skip, default)]
pub origin: Option<String>,
#[serde(default, rename = "use", skip_serializing_if = "IndexMap::is_empty")]
pub use_map: IndexMap<String, UrlOrManifest>,
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
pub package: IndexMap<String, Annotation>,
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
pub atoms: IndexMap<String, Atom>,
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
pub commands: IndexMap<String, Command>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub bindings: Vec<Binding>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub entrypoint: Option<String>,
}
impl Manifest {
pub fn package_annotation<T>(&self, name: &str) -> Result<Option<T>, serde_cbor::Error>
where
T: DeserializeOwned,
{
if let Some(value) = self.package.get(name) {
let annotation = serde_cbor::value::from_value(value.clone())?;
return Ok(Some(annotation));
}
Ok(None)
}
}
impl Manifest {
pub fn wapm(&self) -> Result<Option<Wapm>, serde_cbor::Error> {
self.package_annotation(Wapm::KEY)
}
pub fn filesystem(&self) -> Result<Option<FileSystemMappings>, serde_cbor::Error> {
self.package_annotation(FileSystemMappings::KEY)
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum BindingsExtended {
Wit(WitBindings),
Wai(WaiBindings),
}
impl BindingsExtended {
pub fn metadata_paths(&self) -> Vec<&str> {
match self {
BindingsExtended::Wit(w) => w.metadata_paths(),
BindingsExtended::Wai(w) => w.metadata_paths(),
}
}
pub fn module(&self) -> &str {
match self {
BindingsExtended::Wit(wit) => &wit.module,
BindingsExtended::Wai(wai) => &wai.module,
}
}
pub fn exports(&self) -> Option<&str> {
match self {
BindingsExtended::Wit(wit) => Some(&wit.exports),
BindingsExtended::Wai(wai) => wai.exports.as_deref(),
}
}
pub fn imports(&self) -> Vec<String> {
match self {
BindingsExtended::Wit(_) => Vec::new(),
BindingsExtended::Wai(wai) => wai.imports.clone(),
}
}
}
#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct WitBindings {
pub exports: String,
pub module: String,
}
impl WitBindings {
pub fn metadata_paths(&self) -> Vec<&str> {
vec![&self.exports]
}
}
#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct WaiBindings {
pub exports: Option<String>,
pub module: String,
pub imports: Vec<String>,
}
impl WaiBindings {
pub fn metadata_paths(&self) -> Vec<&str> {
let mut paths: Vec<&str> = Vec::new();
if let Some(export) = &self.exports {
paths.push(export);
}
for import in &self.imports {
paths.push(import);
}
paths
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Binding {
pub name: String,
pub kind: String,
pub annotations: serde_cbor::Value,
}
impl Binding {
pub fn new_wit(name: String, kind: String, wit: WitBindings) -> Self {
Self {
name,
kind,
annotations: serde_cbor::from_slice(
&serde_cbor::to_vec(&BindingsExtended::Wit(wit)).unwrap(),
)
.unwrap(),
}
}
pub fn get_bindings(&self) -> Option<BindingsExtended> {
serde_cbor::from_slice(&serde_cbor::to_vec(&self.annotations).ok()?).ok()
}
pub fn get_wai_bindings(&self) -> Option<WaiBindings> {
match self.get_bindings() {
Some(BindingsExtended::Wai(wai)) => Some(wai),
_ => None,
}
}
pub fn get_wit_bindings(&self) -> Option<WitBindings> {
match self.get_bindings() {
Some(BindingsExtended::Wit(wit)) => Some(wit),
_ => None,
}
}
}
#[derive(Default, Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct ManifestWithoutAtomSignatures {
#[serde(skip, default)]
pub origin: Option<String>,
#[serde(default, rename = "use", skip_serializing_if = "IndexMap::is_empty")]
pub use_map: IndexMap<String, UrlOrManifest>,
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
pub package: IndexMap<String, Annotation>,
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
pub atoms: IndexMap<String, AtomWithoutSignature>,
#[serde(default, skip_serializing_if = "IndexMap::is_empty")]
pub commands: IndexMap<String, Command>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub bindings: Vec<Binding>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub entrypoint: Option<String>,
}
impl ManifestWithoutAtomSignatures {
pub fn to_manifest(
&self,
atom_signatures: &BTreeMap<String, String>,
) -> Result<Manifest, Error> {
let mut atoms = IndexMap::new();
for (k, v) in self.atoms.iter() {
let signature = atom_signatures
.get(k)
.ok_or(Error(format!("Could not find signature for atom {k:?}")))?;
atoms.insert(
k.clone(),
Atom {
kind: v.kind.clone(),
signature: signature.clone(),
},
);
}
Ok(Manifest {
origin: self.origin.clone(),
use_map: self.use_map.clone(),
package: self.package.clone(),
atoms,
bindings: self.bindings.clone(),
commands: self.commands.clone(),
entrypoint: self.entrypoint.clone(),
})
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
pub enum UrlOrManifest {
Url(Url),
Manifest(Manifest),
RegistryDependentUrl(String),
}
impl UrlOrManifest {
pub fn is_manifest(&self) -> bool {
matches!(self, UrlOrManifest::Manifest(_))
}
pub fn is_url(&self) -> bool {
matches!(self, UrlOrManifest::Url(_))
}
}
pub type Annotation = serde_cbor::Value;
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct AtomWithoutSignature {
pub kind: Url,
}
#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Atom {
pub kind: Url,
pub signature: String,
}
#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Command {
pub runner: String,
pub annotations: IndexMap<String, Annotation>,
}
impl Command {
pub fn annotation<T>(&self, name: &str) -> Result<Option<T>, serde_cbor::Error>
where
T: DeserializeOwned,
{
if let Some(value) = self.annotations.get(name) {
let annotation = serde_cbor::value::from_value(value.clone())?;
return Ok(Some(annotation));
}
Ok(None)
}
}
impl Command {
pub fn wasi(&self) -> Result<Option<annotations::Wasi>, serde_cbor::Error> {
self.annotation(annotations::Wasi::KEY)
}
pub fn wcgi(&self) -> Result<Option<annotations::Wcgi>, serde_cbor::Error> {
self.annotation(annotations::Wcgi::KEY)
}
pub fn emscripten(&self) -> Result<Option<annotations::Emscripten>, serde_cbor::Error> {
self.annotation(annotations::Emscripten::KEY)
}
pub fn atom(&self) -> Result<Option<annotations::Atom>, serde_cbor::Error> {
if let Some(annotations) = self.annotation(annotations::Atom::KEY)? {
return Ok(Some(annotations));
}
#[allow(deprecated)]
let atom = if let Ok(Some(annotations::Wasi { atom, .. })) = self.wasi() {
Some(atom)
} else if let Ok(Some(annotations::Emscripten { atom, .. })) = self.emscripten() {
atom
} else {
None
};
if let Some(atom) = atom {
match ModuleReference::from_str(&atom) {
Ok(ModuleReference::CurrentPackage { module }) => {
return Ok(Some(annotations::Atom::new(module, None)))
}
Ok(ModuleReference::Dependency { dependency, module }) => {
return Ok(Some(annotations::Atom::new(module, dependency)))
}
Err(_) => {}
}
}
Ok(None)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn deserialize_extended_wai_bindings() {
let json = serde_json::json!({
"wai": {
"exports": "interface.wai",
"module": "my-module",
"imports": ["browser.wai", "fs.wai"],
}
});
let bindings = BindingsExtended::deserialize(json).unwrap();
assert_eq!(
bindings,
BindingsExtended::Wai(WaiBindings {
exports: Some("interface.wai".to_string()),
module: "my-module".to_string(),
imports: vec!["browser.wai".to_string(), "fs.wai".to_string(),]
})
);
}
#[test]
fn deserialize_extended_wit_bindings() {
let json = serde_json::json!({
"wit": {
"exports": "interface.wit",
"module": "my-module",
}
});
let bindings = BindingsExtended::deserialize(json).unwrap();
assert_eq!(
bindings,
BindingsExtended::Wit(WitBindings {
exports: "interface.wit".to_string(),
module: "my-module".to_string(),
})
);
}
}