use crate::imp::core::bytes::Bytes32;
use crate::imp::core::codec::{Decode, DecodeError, Decoder, Encode, Encoder};
use alloc::string::String;
use alloc::vec::Vec;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
pub const PUBLIC_MANIFEST_SCHEMA_VERSION: u32 = 1;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PublicManifestEntry {
pub path: String,
pub latest_root: Bytes32,
pub generation_index: u64,
pub sha256_latest: Bytes32,
pub version_count: u32,
}
impl Encode for PublicManifestEntry {
fn encode(&self, enc: &mut Encoder) {
self.path.encode(enc);
self.latest_root.encode(enc);
self.generation_index.encode(enc);
self.sha256_latest.encode(enc);
self.version_count.encode(enc);
}
}
impl Decode for PublicManifestEntry {
fn decode(dec: &mut Decoder<'_>) -> Result<Self, DecodeError> {
Ok(PublicManifestEntry {
path: String::decode(dec)?,
latest_root: Bytes32::decode(dec)?,
generation_index: u64::decode(dec)?,
sha256_latest: Bytes32::decode(dec)?,
version_count: u32::decode(dec)?,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PublicManifest {
pub schema_version: u32,
pub entries: Vec<PublicManifestEntry>,
}
impl PublicManifest {
pub fn new(mut entries: Vec<PublicManifestEntry>) -> Self {
entries.sort_by(|a, b| a.path.cmp(&b.path));
PublicManifest {
schema_version: PUBLIC_MANIFEST_SCHEMA_VERSION,
entries,
}
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut enc = Encoder::new();
self.encode(&mut enc);
enc.finish()
}
pub fn from_bytes(body: &[u8]) -> Result<Self, DecodeError> {
let mut dec = Decoder::new(body);
Self::decode(&mut dec)
}
pub fn to_json(&self) -> String {
let entries: Vec<serde_json::Value> = self
.entries
.iter()
.map(|e| {
serde_json::json!({
"path": e.path,
"latest_root": e.latest_root.to_hex(),
"generation_index": e.generation_index,
"sha256_latest": e.sha256_latest.to_hex(),
"version_count": e.version_count,
})
})
.collect();
let v = serde_json::json!({
"schema_version": self.schema_version,
"entries": entries,
});
serde_json::to_string_pretty(&v).unwrap_or_else(|_| String::from("{}"))
}
}
impl Encode for PublicManifest {
fn encode(&self, enc: &mut Encoder) {
self.schema_version.encode(enc);
self.entries.encode(enc);
}
}
impl Decode for PublicManifest {
fn decode(dec: &mut Decoder<'_>) -> Result<Self, DecodeError> {
Ok(PublicManifest {
schema_version: u32::decode(dec)?,
entries: Vec::<PublicManifestEntry>::decode(dec)?,
})
}
}
#[cfg(all(test, not(target_arch = "wasm32")))]
mod tests {
use super::*;
use alloc::string::ToString;
use alloc::vec;
fn entry(path: &str, root: u8, gen: u64, sha: u8, vc: u32) -> PublicManifestEntry {
PublicManifestEntry {
path: path.to_string(),
latest_root: Bytes32([root; 32]),
generation_index: gen,
sha256_latest: Bytes32([sha; 32]),
version_count: vc,
}
}
#[test]
fn new_sorts_entries_by_path() {
let m = PublicManifest::new(vec![entry("b.txt", 1, 0, 1, 1), entry("a.txt", 2, 1, 2, 1)]);
assert_eq!(m.schema_version, PUBLIC_MANIFEST_SCHEMA_VERSION);
assert_eq!(m.entries[0].path, "a.txt");
assert_eq!(m.entries[1].path, "b.txt");
}
#[test]
fn bytes_round_trip() {
let m = PublicManifest::new(vec![
entry("index.html", 0xab, 3, 0xcd, 2),
entry("assets/app.js", 0x11, 5, 0x22, 4),
]);
let bytes = m.to_bytes();
let back = PublicManifest::from_bytes(&bytes).unwrap();
assert_eq!(back, m);
}
#[test]
fn empty_round_trips() {
let m = PublicManifest::new(vec![]);
let back = PublicManifest::from_bytes(&m.to_bytes()).unwrap();
assert_eq!(back, m);
assert!(back.entries.is_empty());
}
#[test]
fn json_uses_hex_and_exact_keys() {
let m = PublicManifest::new(vec![entry("index.html", 0xab, 3, 0xcd, 2)]);
let json = m.to_json();
assert!(json.contains("\"schema_version\""));
assert!(json.contains("\"path\""));
assert!(json.contains("\"index.html\""));
assert!(json.contains("\"latest_root\""));
assert!(json.contains(&"ab".repeat(32)));
assert!(json.contains("\"generation_index\""));
assert!(json.contains("\"sha256_latest\""));
assert!(json.contains(&"cd".repeat(32)));
assert!(json.contains("\"version_count\""));
}
#[test]
fn decode_rejects_truncated_body() {
assert!(PublicManifest::from_bytes(&[0u8, 0, 0]).is_err());
}
#[cfg(feature = "serde")]
#[test]
fn serde_json_round_trips_with_hex_hashes() {
let m = PublicManifest::new(vec![entry("a", 0x01, 7, 0x02, 3)]);
let s = serde_json::to_string(&m).unwrap();
assert!(s.contains(&"01".repeat(32)));
let back: PublicManifest = serde_json::from_str(&s).unwrap();
assert_eq!(back, m);
}
}