use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use crate::contracts::decode_contract;
use crate::contracts::kinds::RweLibraryManifestContract;
use crate::platform::error::PlatformError;
use crate::platform::web::embedded::{PLATFORM_LIBRARY_ASSETS, PLATFORM_SOURCE_LIBRARY_ASSETS};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RweLibraryManifestSpec {
pub name: String,
pub description: String,
pub exports: Vec<String>,
pub versions: BTreeMap<String, RweLibraryVersionSpec>,
pub license: RweLibraryLicense,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RweLibraryLicense {
pub spdx: String,
pub holders: Vec<String>,
pub notices: Vec<String>,
#[serde(default)]
pub modified: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub modifications: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub election: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct RweLibraryVersionSpec {
pub entry: String,
pub source: String,
pub package_version: String,
pub size_bytes: u64,
pub integrity: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub note: Option<String>,
}
#[derive(Debug, Clone)]
pub struct LibraryVersion {
pub key: String,
pub entry: String,
pub source: String,
pub package_version: String,
pub size_bytes: u64,
pub integrity: String,
}
#[derive(Debug, Clone)]
pub struct RweLibraryManifest {
pub name: String,
pub description: String,
pub exports: Vec<String>,
pub versions: Vec<LibraryVersion>,
}
impl RweLibraryManifest {
pub fn default_offline_version(&self) -> Option<&LibraryVersion> {
self.versions.iter().find(|v| v.source == "offline")
}
pub fn version(&self, key: &str) -> Option<&LibraryVersion> {
self.versions.iter().find(|v| v.key == key)
}
pub fn packed_version(&self) -> &str {
self.default_offline_version()
.or_else(|| self.versions.first())
.map(|v| v.key.as_str())
.unwrap_or("unknown")
}
pub fn packed_kind(&self) -> &str {
if self.versions.iter().any(|v| v.source == "offline") {
"full"
} else {
"hub"
}
}
}
pub struct LibraryService {
manifests: Vec<RweLibraryManifest>,
source_roots: std::sync::OnceLock<MaterializedSources>,
}
struct MaterializedSources {
dir: Option<std::path::PathBuf>,
roots: BTreeMap<String, std::path::PathBuf>,
}
impl Drop for MaterializedSources {
fn drop(&mut self) {
if let Some(dir) = &self.dir {
let _ = std::fs::remove_dir_all(dir);
}
}
}
impl LibraryService {
pub fn from_embedded() -> Result<Self, PlatformError> {
let mut manifests = Vec::new();
for asset in PLATFORM_LIBRARY_ASSETS {
if !asset.path.ends_with("/manifest.json") {
continue;
}
let document =
decode_contract::<RweLibraryManifestContract>(asset.bytes).map_err(|err| {
PlatformError::new(
"PLATFORM_LIBRARY_MANIFEST",
format!("invalid embedded library manifest '{}': {err}", asset.path),
)
})?;
let spec = document.spec;
let versions = spec
.versions
.into_iter()
.map(|(key, version)| LibraryVersion {
key,
entry: version.entry,
source: version.source,
package_version: version.package_version,
size_bytes: version.size_bytes,
integrity: version.integrity,
})
.collect();
manifests.push(RweLibraryManifest {
name: spec.name,
description: spec.description,
exports: spec.exports,
versions,
});
}
Ok(Self { manifests, source_roots: std::sync::OnceLock::new() })
}
pub fn source_roots(&self) -> BTreeMap<String, std::path::PathBuf> {
self.source_roots
.get_or_init(|| match Self::materialize_source_libraries() {
Ok(dir) => dir,
Err(err) => {
eprintln!("source libraries not materialized: {err}");
MaterializedSources { dir: None, roots: BTreeMap::new() }
}
})
.roots
.clone()
}
fn materialize_source_libraries() -> Result<MaterializedSources, PlatformError> {
static SEQ: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
let nonce = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.subsec_nanos())
.unwrap_or(0)
^ SEQ.fetch_add(1, std::sync::atomic::Ordering::Relaxed).rotate_left(20);
let dir = std::env::temp_dir().join(format!(
"zebflow-source-libraries-{}-{}-{nonce:08x}",
env!("CARGO_PKG_VERSION"),
std::process::id()
));
let staging = std::path::PathBuf::from(format!("{}.staging", dir.display()));
let _ = std::fs::remove_dir_all(&staging);
std::fs::create_dir_all(&staging).map_err(|e| {
PlatformError::new("PLATFORM_LIBRARY_MATERIALIZE", format!("{}: {e}", staging.display()))
})?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&staging, std::fs::Permissions::from_mode(0o700)).map_err(|e| {
PlatformError::new("PLATFORM_LIBRARY_MATERIALIZE", format!("chmod {}: {e}", staging.display()))
})?;
}
let mut roots = BTreeMap::new();
for asset in PLATFORM_SOURCE_LIBRARY_ASSETS {
let mut parts = asset.path.splitn(5, '/');
let (Some("zeb"), Some(name), Some(version), Some("src"), Some(rest)) =
(parts.next(), parts.next(), parts.next(), parts.next(), parts.next())
else {
continue;
};
let src_dir = staging.join("zeb").join(name).join(version).join("src");
let target = src_dir.join(rest);
if let Some(parent) = target.parent() {
std::fs::create_dir_all(parent).map_err(|e| {
PlatformError::new("PLATFORM_LIBRARY_MATERIALIZE", format!("{}: {e}", parent.display()))
})?;
}
std::fs::write(&target, asset.bytes).map_err(|e| {
PlatformError::new("PLATFORM_LIBRARY_MATERIALIZE", format!("{}: {e}", target.display()))
})?;
roots.insert(
format!("zeb/{name}"),
dir.join("zeb").join(name).join(version).join("src"),
);
}
std::fs::rename(&staging, &dir).map_err(|e| {
PlatformError::new("PLATFORM_LIBRARY_MATERIALIZE", format!("publish {}: {e}", dir.display()))
})?;
Ok(MaterializedSources { dir: Some(dir), roots })
}
pub fn list(&self) -> impl Iterator<Item = &RweLibraryManifest> {
self.manifests.iter()
}
pub fn get(&self, name: &str) -> Option<&RweLibraryManifest> {
self.manifests.iter().find(|m| m.name == name)
}
pub fn find_library_for_symbol(&self, symbol: &str) -> Option<&RweLibraryManifest> {
self.manifests
.iter()
.find(|m| m.exports.iter().any(|e| e == symbol))
}
}
#[cfg(test)]
mod tests {
use sha2::{Digest, Sha256};
use super::*;
#[test]
fn every_embedded_library_uses_the_canonical_contract() {
let service = LibraryService::from_embedded().expect("embedded library contracts");
assert_eq!(service.list().count(), 11);
assert!(service.get("zeb/deckgl").is_some());
}
#[test]
fn legacy_library_manifest_shape_is_rejected() {
let raw = br#"{"name":"zeb/example","description":"example","exports":[],"versions":{}}"#;
let err = decode_contract::<RweLibraryManifestContract>(raw).unwrap_err();
assert_eq!(err.category(), "invalid");
}
#[test]
fn every_embedded_manifest_declares_the_digest_and_size_of_its_real_bytes() {
let service = LibraryService::from_embedded().expect("embedded library contracts");
let mut releases = 0usize;
for manifest in service.list() {
for version in &manifest.versions {
let asset_path = format!("{}/{}", manifest.name, version.entry);
let bytes = crate::platform::web::embedded::hub_catalogue_asset(&asset_path)
.unwrap_or_else(|| panic!("embedded bundle '{asset_path}' exists"));
assert_eq!(
version.integrity,
format!("sha256:{:x}", Sha256::digest(bytes)),
"{} '{}' integrity matches its embedded bytes",
manifest.name,
version.key
);
assert_eq!(
version.size_bytes,
bytes.len() as u64,
"{} '{}' size_bytes matches its embedded bytes",
manifest.name,
version.key
);
releases += 1;
}
}
assert!(releases >= 11, "every library carries at least one release");
}
#[test]
fn every_embedded_manifest_is_canonical_byte_for_byte() {
for asset in PLATFORM_LIBRARY_ASSETS {
if !asset.path.ends_with("/manifest.json") {
continue;
}
let document = decode_contract::<RweLibraryManifestContract>(asset.bytes)
.unwrap_or_else(|err| panic!("'{}' decodes: {err}", asset.path));
let encoded = crate::contracts::encode_contract::<RweLibraryManifestContract>(
document.metadata,
document.spec,
)
.expect("re-encode");
assert_eq!(
encoded, asset.bytes,
"'{}' is written in canonical form",
asset.path
);
}
}
}
#[cfg(test)]
mod license_tests {
use super::*;
use crate::contracts::decode_contract;
#[test]
fn every_shipped_library_carries_the_notice_it_names() {
let mut checked = 0;
for entry in std::fs::read_dir("blessed/rwe-libraries").expect("read libraries") {
let dir = entry.expect("entry").path();
let manifest = dir.join("manifest.json");
if !manifest.is_file() {
continue;
}
let bytes = std::fs::read(&manifest).expect("read manifest");
let document = decode_contract::<RweLibraryManifestContract>(&bytes)
.unwrap_or_else(|e| panic!("{} decodes: {e}", manifest.display()));
let license = &document.spec.license;
let name = &document.spec.name;
assert!(!license.spdx.trim().is_empty(), "{name} declares no SPDX id");
assert!(!license.holders.is_empty(), "{name} names no copyright holder");
assert!(!license.notices.is_empty(), "{name} lists no notice file");
for notice in &license.notices {
let path = dir.join(notice);
assert!(path.is_file(), "{name} names '{notice}', which is not in the package");
let text = std::fs::read_to_string(&path).expect("read notice");
assert!(
text.len() > 200,
"{name}'s '{notice}' is {} bytes — too short to be a licence",
text.len()
);
}
if license.modified {
let path = license.modifications.as_ref().unwrap_or_else(|| {
panic!("{name} is modified but points at no record of what changed")
});
assert!(
dir.join(path).is_file(),
"{name} names modifications '{path}', which is not in the package"
);
}
if license.spdx.contains(" OR ") {
assert!(
license.election.is_some(),
"{name} is offered under a choice of licences and Zebflow has made none"
);
}
checked += 1;
}
assert!(checked >= 11, "only {checked} libraries checked — the scan lost the directory");
}
}
#[cfg(test)]
mod canonicalize_helper {
use super::*;
use crate::contracts::{decode_contract, encode_contract};
#[test]
#[ignore]
fn rewrite_blessed_manifests_canonically() {
for entry in std::fs::read_dir("blessed/rwe-libraries").expect("read libraries") {
let dir = entry.expect("entry").path();
let manifest = dir.join("manifest.json");
if !manifest.is_file() {
continue;
}
let bytes = std::fs::read(&manifest).expect("read manifest");
let document = decode_contract::<RweLibraryManifestContract>(&bytes)
.unwrap_or_else(|e| panic!("{} decodes: {e}", manifest.display()));
let encoded = encode_contract::<RweLibraryManifestContract>(
document.metadata,
document.spec,
)
.expect("encode");
if encoded != bytes {
std::fs::write(&manifest, &encoded).expect("write");
println!("canonicalized {}", manifest.display());
}
}
}
}