mod signature;
pub use signature::{
canonicalize_manifest, compute_bundle_hash, compute_signing_payload, decode_public_key,
decode_signature, derive_signer_id_did_key, format_bundle_hash, sign_manifest_json,
verify_ed25519, verify_manifest_signature, ManifestVerification,
};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BundleArtifact {
pub path: String,
pub hash: Option<String>,
pub size: u64,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BundleMetadata {
pub name: String,
pub description: Option<String>,
pub icon: Option<String>,
#[serde(default)]
pub tags: Vec<String>,
pub license: Option<String>,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BundleInterfaces {
#[serde(default)]
pub exports: Vec<String>,
#[serde(default)]
pub uses: Vec<String>,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BundleLinks {
pub frontend: Option<String>,
pub github: Option<String>,
pub docs: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BundleSignature {
pub algorithm: String,
pub public_key: String,
pub signature: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub signed_at: Option<String>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BundleService {
pub name: String,
pub wasm: BundleArtifact,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub abi: Option<BundleArtifact>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BundleManifest {
pub version: String,
pub package: String,
pub app_version: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub signer_id: Option<String>,
pub min_runtime_version: String,
#[serde(default)]
pub metadata: Option<BundleMetadata>,
#[serde(default)]
pub interfaces: Option<BundleInterfaces>,
pub wasm: Option<BundleArtifact>,
pub abi: Option<BundleArtifact>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub services: Option<Vec<BundleService>>,
#[serde(default)]
pub migrations: Vec<BundleArtifact>,
#[serde(default)]
pub links: Option<BundleLinks>,
#[serde(default)]
pub signature: Option<BundleSignature>,
}
pub struct WasmArtifact<'a> {
pub name: Option<&'a str>,
pub wasm: &'a BundleArtifact,
pub abi: Option<&'a BundleArtifact>,
}
impl BundleManifest {
pub fn service_names(&self) -> Vec<&str> {
match &self.services {
Some(svcs) if !svcs.is_empty() => svcs.iter().map(|s| s.name.as_str()).collect(),
_ => vec![],
}
}
pub fn is_multi_service(&self) -> bool {
matches!(&self.services, Some(svcs) if svcs.len() > 1)
}
pub fn wasm_artifacts(&self) -> Vec<WasmArtifact<'_>> {
match &self.services {
Some(svcs) if !svcs.is_empty() => svcs
.iter()
.map(|s| WasmArtifact {
name: Some(&s.name),
wasm: &s.wasm,
abi: s.abi.as_ref(),
})
.collect(),
_ => self
.wasm
.as_ref()
.map(|w| {
vec![WasmArtifact {
name: None,
wasm: w,
abi: self.abi.as_ref(),
}]
})
.unwrap_or_default(),
}
}
pub fn to_metadata_json(&self) -> eyre::Result<Vec<u8>> {
let mut obj = serde_json::Map::new();
obj.insert(
"package".into(),
serde_json::Value::String(self.package.clone()),
);
obj.insert(
"version".into(),
serde_json::Value::String(self.app_version.clone()),
);
if let Some(ref m) = self.metadata {
obj.insert("name".into(), serde_json::Value::String(m.name.clone()));
if let Some(ref v) = m.description {
obj.insert("description".into(), serde_json::Value::String(v.clone()));
}
if let Some(ref v) = m.icon {
obj.insert("icon".into(), serde_json::Value::String(v.clone()));
}
if !m.tags.is_empty() {
obj.insert(
"tags".into(),
serde_json::Value::Array(
m.tags
.iter()
.map(|t| serde_json::Value::String(t.clone()))
.collect(),
),
);
}
if let Some(ref v) = m.license {
obj.insert("license".into(), serde_json::Value::String(v.clone()));
}
}
if let Some(ref l) = self.links {
let mut links_obj = serde_json::Map::new();
if let Some(ref v) = l.frontend {
links_obj.insert("frontend".into(), serde_json::Value::String(v.clone()));
}
if let Some(ref v) = l.github {
links_obj.insert("github".into(), serde_json::Value::String(v.clone()));
}
if let Some(ref v) = l.docs {
links_obj.insert("docs".into(), serde_json::Value::String(v.clone()));
}
if !links_obj.is_empty() {
obj.insert("links".into(), serde_json::Value::Object(links_obj));
}
}
Ok(serde_json::to_vec(&serde_json::Value::Object(obj))?)
}
}