1use crate::address::ContentAddress;
17use crate::archive::Archive;
18use crate::codec::{self, CodecError};
19
20#[derive(Debug, Clone, PartialEq, Eq)]
22pub enum LoadError {
23 Decode(CodecError),
25 RootMismatch {
28 expected: ContentAddress,
29 actual: ContentAddress,
30 },
31}
32
33impl core::fmt::Display for LoadError {
34 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
35 match self {
36 LoadError::Decode(e) => write!(f, "decode .prx: {e}"),
37 LoadError::RootMismatch { expected, actual } => write!(
38 f,
39 ".prx root mismatch: expected {}, got {} — refused",
40 expected.to_hex(),
41 actual.to_hex()
42 ),
43 }
44 }
45}
46
47impl std::error::Error for LoadError {}
48
49pub fn emit(archive: &Archive) -> Result<Vec<u8>, CodecError> {
53 codec::canonical_encode(archive)
54}
55
56pub fn load(bytes: &[u8], trusted_root: ContentAddress) -> Result<Archive, LoadError> {
61 let archive: Archive = codec::canonical_decode(bytes).map_err(LoadError::Decode)?;
62 let actual = archive.root().map_err(LoadError::Decode)?;
63 if actual != trusted_root {
64 return Err(LoadError::RootMismatch {
65 expected: trusted_root,
66 actual,
67 });
68 }
69 Ok(archive)
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use crate::connection::{Connection, GeneratorAction};
76 use crate::definition::Definition;
77
78 fn sample() -> Archive {
79 Archive {
80 nodes: vec![Definition {
81 kind: "Concept".into(),
82 name: "Employer".into(),
83 edges: vec![("Subsumption".into(), "Agent".into())],
84 axioms: vec!["EmployerIsAgent".into()],
85 lexical: Some("employer".into()),
86 }],
87 connections: vec![Connection {
88 kind: "FullyFaithful".into(),
89 source: "A".into(),
90 target: "B".into(),
91 action: GeneratorAction::Functor {
92 map_object: vec![("A".into(), "B".into())],
93 map_morphism: vec![],
94 },
95 laws: vec!["PreservesComposition".into()],
96 }],
97 }
98 }
99
100 #[test]
101 fn emit_then_load_round_trips() {
102 let a = sample();
103 let bytes = emit(&a).unwrap();
104 let loaded = load(&bytes, a.root().unwrap()).unwrap();
105 assert_eq!(loaded, a);
106 }
107
108 #[test]
109 fn load_refuses_a_wrong_root() {
110 let a = sample();
111 let bytes = emit(&a).unwrap();
112 let err = load(&bytes, ContentAddress::of(b"not the root")).unwrap_err();
113 assert!(matches!(err, LoadError::RootMismatch { .. }));
114 }
115
116 #[test]
117 fn load_refuses_tampered_bytes() {
118 let a = sample();
119 let trusted = a.root().unwrap();
120 let mut bytes = emit(&a).unwrap();
121 *bytes.last_mut().unwrap() ^= 0xff;
124 assert!(load(&bytes, trusted).is_err());
125 }
126
127 #[test]
128 fn load_refuses_garbage() {
129 let err = load(b"not dag-cbor at all", ContentAddress::of(b"x")).unwrap_err();
130 assert!(matches!(err, LoadError::Decode(_)));
131 }
132}