use crate::address::ContentAddress;
use crate::archive::Archive;
use crate::codec::{self, CodecError};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum LoadError {
Decode(CodecError),
RootMismatch {
expected: ContentAddress,
actual: ContentAddress,
},
}
impl core::fmt::Display for LoadError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
LoadError::Decode(e) => write!(f, "decode .prx: {e}"),
LoadError::RootMismatch { expected, actual } => write!(
f,
".prx root mismatch: expected {}, got {} — refused",
expected.to_hex(),
actual.to_hex()
),
}
}
}
impl std::error::Error for LoadError {}
pub fn emit(archive: &Archive) -> Result<Vec<u8>, CodecError> {
codec::canonical_encode(archive)
}
pub fn load(bytes: &[u8], trusted_root: ContentAddress) -> Result<Archive, LoadError> {
let archive: Archive = codec::canonical_decode(bytes).map_err(LoadError::Decode)?;
let actual = archive.root().map_err(LoadError::Decode)?;
if actual != trusted_root {
return Err(LoadError::RootMismatch {
expected: trusted_root,
actual,
});
}
Ok(archive)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::connection::{Connection, GeneratorAction};
use crate::definition::Definition;
fn sample() -> Archive {
Archive {
nodes: vec![Definition {
kind: "Concept".into(),
name: "Employer".into(),
edges: vec![("Subsumption".into(), "Agent".into())],
axioms: vec!["EmployerIsAgent".into()],
lexical: Some("employer".into()),
}],
connections: vec![Connection {
kind: "FullyFaithful".into(),
source: "A".into(),
target: "B".into(),
action: GeneratorAction::Functor {
map_object: vec![("A".into(), "B".into())],
map_morphism: vec![],
},
laws: vec!["PreservesComposition".into()],
}],
}
}
#[test]
fn emit_then_load_round_trips() {
let a = sample();
let bytes = emit(&a).unwrap();
let loaded = load(&bytes, a.root().unwrap()).unwrap();
assert_eq!(loaded, a);
}
#[test]
fn load_refuses_a_wrong_root() {
let a = sample();
let bytes = emit(&a).unwrap();
let err = load(&bytes, ContentAddress::of(b"not the root")).unwrap_err();
assert!(matches!(err, LoadError::RootMismatch { .. }));
}
#[test]
fn load_refuses_tampered_bytes() {
let a = sample();
let trusted = a.root().unwrap();
let mut bytes = emit(&a).unwrap();
*bytes.last_mut().unwrap() ^= 0xff;
assert!(load(&bytes, trusted).is_err());
}
#[test]
fn load_refuses_garbage() {
let err = load(b"not dag-cbor at all", ContentAddress::of(b"x")).unwrap_err();
assert!(matches!(err, LoadError::Decode(_)));
}
}