Skip to main content

urna_format/sections/
provenance.rs

1//! `provenance` section (`SECTION_PROVENANCE = 0x05`). A single
2//! versioned JSON blob carrying ingestion metadata. v1 keeps it
3//! schema-free so callers can include whatever they need (legacy
4//! source, ingestion timestamps, per-document labels). The reader
5//! only enforces that it parses as JSON.
6
7use super::codec::Cursor;
8use crate::error::UrnaError;
9use crate::layout::{SECTION_PAYLOAD_PREFIX_SIZE, SECTION_PAYLOAD_VERSION, SECTION_PROVENANCE};
10
11pub fn encode_provenance(value: &serde_json::Value) -> crate::Result<Vec<u8>> {
12    let json = serde_json::to_vec(value)?;
13    let json_len = u32::try_from(json.len())
14        .map_err(|_| UrnaError::InvalidInput("provenance JSON too large".into()))?;
15    let mut buf = Vec::with_capacity(SECTION_PAYLOAD_PREFIX_SIZE + json.len());
16    buf.extend_from_slice(&SECTION_PAYLOAD_VERSION.to_le_bytes());
17    buf.extend_from_slice(&(json_len as u64).to_le_bytes());
18    buf.extend_from_slice(&json);
19    Ok(buf)
20}
21
22pub fn decode_provenance(data: &[u8]) -> crate::Result<serde_json::Value> {
23    let mut c = Cursor::new(data, SECTION_PROVENANCE);
24    if c.data.len() < SECTION_PAYLOAD_PREFIX_SIZE {
25        return Err(c.malformed("payload shorter than prefix"));
26    }
27    let version = c.read_u32()?;
28    if version != SECTION_PAYLOAD_VERSION {
29        return Err(UrnaError::UnsupportedSectionVersion {
30            section_id: SECTION_PROVENANCE,
31            version,
32        });
33    }
34    let json_len = c.read_u64()? as usize;
35    let json = c.read_bytes(json_len)?;
36    let value: serde_json::Value = serde_json::from_slice(json).map_err(UrnaError::Json)?;
37    c.finish()?;
38    Ok(value)
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    #[test]
46    fn roundtrip() {
47        let v = serde_json::json!({"source": "demo", "ingested_at": "2026-01-01"});
48        let bytes = encode_provenance(&v).unwrap();
49        let back = decode_provenance(&bytes).unwrap();
50        assert_eq!(v, back);
51    }
52}