use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::error::{Error, Result};
pub const MANIFEST_SCHEMA: &str = "earthsciio/manifest/v1";
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct Manifest {
pub auth_realm: Option<String>,
pub bytes: u64,
pub etag: Option<String>,
pub fetched_at: String,
pub last_modified: Option<String>,
#[serde(default = "default_schema")]
pub schema: String,
pub sha256_content: String,
pub source_loader: Option<String>,
pub url: String,
}
fn default_schema() -> String {
MANIFEST_SCHEMA.to_string()
}
impl Manifest {
pub fn to_json_bytes(&self) -> Result<Vec<u8>> {
let mut s = serde_json::to_string_pretty(self).map_err(|e| Error::Manifest {
detail: e.to_string(),
})?;
s.push('\n');
Ok(s.into_bytes())
}
pub fn from_json_bytes(data: &[u8]) -> Result<Manifest> {
serde_json::from_slice(data).map_err(|e| Error::Manifest {
detail: e.to_string(),
})
}
pub fn read(path: &Path) -> Result<Manifest> {
let data = std::fs::read(path).map_err(|e| Error::io(Some(path.to_path_buf()), e))?;
Manifest::from_json_bytes(&data)
}
}
#[cfg(test)]
mod tests {
use super::*;
const ERA5_META: &str = "{\n \"auth_realm\": null,\n \"bytes\": 1012,\n \"etag\": null,\n \"fetched_at\": \"2026-06-26T00:00:00Z\",\n \"last_modified\": null,\n \"schema\": \"earthsciio/manifest/v1\",\n \"sha256_content\": \"fe1f8040ebd8688a4a16e80ef694a37ae2b1deb21b449eebfa90745cab95d3af\",\n \"source_loader\": \"era5\",\n \"url\": \"https://data.earthsci.dev/era5/2018/11/20181108.nc\"\n}\n";
#[test]
fn parses_corpus_manifest() {
let m = Manifest::from_json_bytes(ERA5_META.as_bytes()).unwrap();
assert_eq!(m.url, "https://data.earthsci.dev/era5/2018/11/20181108.nc");
assert_eq!(m.bytes, 1012);
assert_eq!(
m.sha256_content,
"fe1f8040ebd8688a4a16e80ef694a37ae2b1deb21b449eebfa90745cab95d3af"
);
assert_eq!(m.source_loader.as_deref(), Some("era5"));
assert_eq!(m.etag, None);
assert_eq!(m.auth_realm, None);
assert_eq!(m.schema, MANIFEST_SCHEMA);
}
#[test]
fn serializes_byte_identical_to_python() {
let m = Manifest::from_json_bytes(ERA5_META.as_bytes()).unwrap();
let bytes = m.to_json_bytes().unwrap();
assert_eq!(String::from_utf8(bytes).unwrap(), ERA5_META);
}
#[test]
fn schema_defaults_when_absent() {
let json = "{\"url\":\"u\",\"sha256_content\":\"ab\",\"bytes\":2,\"fetched_at\":\"t\",\"etag\":null,\"last_modified\":null,\"source_loader\":null,\"auth_realm\":null}";
let m = Manifest::from_json_bytes(json.as_bytes()).unwrap();
assert_eq!(m.schema, MANIFEST_SCHEMA);
}
}