pub use indexmap::IndexMap;
pub use url::Url;
use std::{collections::BTreeMap, fmt};
use serde::{Deserialize, Serialize};
use crate::v1::Error;
#[derive(Default, 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>,
}
#[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, 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(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(_))
}
}
impl fmt::Debug for UrlOrManifest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
UrlOrManifest::Url(u) => write!(f, "{u}"),
UrlOrManifest::Manifest(m) => m.fmt(f),
UrlOrManifest::RegistryDependentUrl(s) => write!(f, "{s}"),
}
}
}
impl fmt::Debug for Manifest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let json = serde_json::to_string_pretty(self).unwrap_or_default();
write!(f, "{json}")
}
}
impl fmt::Debug for ManifestWithoutAtomSignatures {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let json = serde_json::to_string_pretty(self).unwrap_or_default();
write!(f, "{json}")
}
}
pub type Annotation = serde_cbor::Value;
#[derive(Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct AtomWithoutSignature {
pub kind: Url,
}
#[derive(Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Atom {
pub kind: Url,
pub signature: String,
}
impl fmt::Debug for Atom {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Atom {{\r\n\tkind: {},\r\n\tsignature:{}\r\n}}",
self.kind, self.signature
)
}
}
#[derive(Debug, Default, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Command {
pub runner: String,
pub annotations: IndexMap<String, Annotation>,
}
#[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(),
})
);
}
}