use crate::errors::AtomicResult;
use serde::{Deserialize, Serialize};
pub const PACK_FORMAT: u8 = 1;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PackEntry {
pub subject: String,
pub update: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Pack {
pub format: u8,
pub entries: Vec<PackEntry>,
pub tombstones: Vec<String>,
}
impl Pack {
pub fn new(entries: Vec<PackEntry>, tombstones: Vec<String>) -> Self {
Self {
format: PACK_FORMAT,
entries,
tombstones,
}
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty() && self.tombstones.is_empty()
}
pub fn encode(&self) -> AtomicResult<Vec<u8>> {
rmp_serde::to_vec_named(self)
.map_err(|e| format!("failed to encode vault pack: {e}").into())
}
pub fn decode(bytes: &[u8]) -> AtomicResult<Self> {
let pack: Pack = rmp_serde::from_slice(bytes)
.map_err(|e| format!("failed to decode vault pack: {e}"))?;
if pack.format != PACK_FORMAT {
return Err(format!(
"unsupported vault pack format {}, this build understands {PACK_FORMAT}",
pack.format
)
.into());
}
Ok(pack)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample() -> Pack {
Pack::new(
vec![
PackEntry {
subject: "did:ad:drive/resource-a".to_string(),
update: vec![1, 2, 3, 4],
},
PackEntry {
subject: "did:ad:drive/resource-b".to_string(),
update: vec![5, 6],
},
],
vec!["did:ad:drive/deleted".to_string()],
)
}
#[test]
fn round_trips() {
let pack = sample();
let decoded = Pack::decode(&pack.encode().unwrap()).unwrap();
assert_eq!(decoded, pack);
}
#[test]
fn preserves_update_bytes_exactly() {
let update: Vec<u8> = (0u8..=255).collect();
let pack = Pack::new(
vec![PackEntry {
subject: "s".into(),
update: update.clone(),
}],
vec![],
);
let decoded = Pack::decode(&pack.encode().unwrap()).unwrap();
assert_eq!(decoded.entries[0].update, update);
}
#[test]
fn an_unknown_format_is_refused() {
let mut pack = sample();
pack.format = 99;
let encoded = pack.encode().unwrap();
let err = Pack::decode(&encoded).unwrap_err().to_string();
assert!(err.contains("format"), "{err}");
}
#[test]
fn garbage_errors_rather_than_panics() {
assert!(Pack::decode(b"not a pack").is_err());
assert!(Pack::decode(&[]).is_err());
}
#[test]
fn empty_is_detected() {
assert!(Pack::new(vec![], vec![]).is_empty());
assert!(!sample().is_empty());
assert!(!Pack::new(vec![], vec!["gone".into()]).is_empty());
}
}