use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
pub const DERIVATION_SIDECAR_PATH: &str = ".memstead/derivations.json";
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DerivationBaseline {
pub rel_type: String,
pub target: String,
pub target_hash: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DerivationSidecar {
#[serde(default = "default_version")]
pub version: u32,
#[serde(default)]
pub baselines: BTreeMap<String, Vec<DerivationBaseline>>,
}
fn default_version() -> u32 {
1
}
impl Default for DerivationSidecar {
fn default() -> Self {
Self {
version: default_version(),
baselines: BTreeMap::new(),
}
}
}
impl DerivationSidecar {
pub fn from_bytes(bytes: &[u8]) -> Result<Self, serde_json::Error> {
serde_json::from_slice(bytes)
}
pub fn to_bytes(&self) -> Vec<u8> {
serde_json::to_vec_pretty(self).expect("derivation sidecar serialises")
}
pub fn set(&mut self, source: &str, rel_type: &str, target: &str, target_hash: &str) {
let list = self.baselines.entry(source.to_string()).or_default();
if let Some(existing) = list
.iter_mut()
.find(|b| b.rel_type == rel_type && b.target == target)
{
existing.target_hash = target_hash.to_string();
} else {
list.push(DerivationBaseline {
rel_type: rel_type.to_string(),
target: target.to_string(),
target_hash: target_hash.to_string(),
});
}
}
pub fn get(&self, source: &str, rel_type: &str, target: &str) -> Option<&str> {
self.baselines.get(source)?.iter().find_map(|b| {
(b.rel_type == rel_type && b.target == target).then_some(b.target_hash.as_str())
})
}
pub fn remove(&mut self, source: &str, rel_type: &str, target: &str) {
if let Some(list) = self.baselines.get_mut(source) {
list.retain(|b| !(b.rel_type == rel_type && b.target == target));
if list.is_empty() {
self.baselines.remove(source);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn set_get_refresh_remove_round_trip() {
let mut s = DerivationSidecar::default();
assert_eq!(s.get("m--a", "DERIVED_FROM", "m--b"), None);
s.set("m--a", "DERIVED_FROM", "m--b", "hash1");
assert_eq!(s.get("m--a", "DERIVED_FROM", "m--b"), Some("hash1"));
s.set("m--a", "DERIVED_FROM", "m--b", "hash2");
assert_eq!(s.get("m--a", "DERIVED_FROM", "m--b"), Some("hash2"));
assert_eq!(s.baselines["m--a"].len(), 1);
s.set("m--a", "SUMMARIZES", "m--b", "hash3");
assert_eq!(s.baselines["m--a"].len(), 2);
let bytes = s.to_bytes();
let back = DerivationSidecar::from_bytes(&bytes).unwrap();
assert_eq!(back.get("m--a", "SUMMARIZES", "m--b"), Some("hash3"));
let mut back = back;
back.remove("m--a", "DERIVED_FROM", "m--b");
assert_eq!(back.get("m--a", "DERIVED_FROM", "m--b"), None);
assert_eq!(back.get("m--a", "SUMMARIZES", "m--b"), Some("hash3"));
}
}