use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FlatBundle {
#[serde(default)]
pub manifest: Value,
#[serde(default)]
pub persons: BTreeMap<String, Value>,
#[serde(default)]
pub families: BTreeMap<String, Value>,
#[serde(default)]
pub events: BTreeMap<String, Value>,
#[serde(default)]
pub links: BTreeMap<String, Value>,
#[serde(default)]
pub occupations: BTreeMap<String, Value>,
#[serde(default)]
pub sources: BTreeMap<String, Value>,
#[serde(default)]
pub places: BTreeMap<String, Value>,
#[serde(default)]
pub documents: BTreeMap<String, Value>,
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub attachments: BTreeMap<String, String>,
#[serde(flatten)]
pub extra: BTreeMap<String, Value>,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn empty_bundle_serializes_with_all_eight_collections_present() {
let b = FlatBundle::default();
let v = serde_json::to_value(&b).unwrap();
assert!(v["manifest"].is_null());
for key in [
"persons",
"families",
"events",
"links",
"occupations",
"sources",
"places",
"documents",
] {
assert!(
v[key].as_object().map(|o| o.is_empty()).unwrap_or(false),
"expected empty {key} object, got {:?}",
v[key]
);
}
assert!(v.get("attachments").is_none());
}
#[test]
fn bundle_round_trip_preserves_ordered_persons() {
let mut b = FlatBundle {
manifest: json!({"axgf": "1.0", "created_at": "2026-06-15T10:00:00Z"}),
..Default::default()
};
b.persons.insert("aaa".into(), json!({"name": "A"}));
b.persons.insert("bbb".into(), json!({"name": "B"}));
let wire = serde_json::to_string(&b).unwrap();
let parsed: FlatBundle = serde_json::from_str(&wire).unwrap();
assert_eq!(parsed.persons.len(), 2);
let keys: Vec<&String> = parsed.persons.keys().collect();
assert_eq!(keys, vec!["aaa", "bbb"]);
assert_eq!(parsed.manifest["axgf"], "1.0");
}
#[test]
fn unknown_top_level_fields_survive_round_trip() {
let input = json!({
"manifest": {"axgf": "2.0", "stats": {"persons": 0}, "future_field": 42},
"persons": {},
"future_collection": {"xyz": {"a": 1}},
"another_top_level": "hello"
});
let parsed: FlatBundle = serde_json::from_value(input.clone()).unwrap();
assert!(parsed.extra.contains_key("future_collection"));
assert_eq!(parsed.extra["another_top_level"], json!("hello"));
assert_eq!(parsed.manifest["future_field"], json!(42));
let out = serde_json::to_value(&parsed).unwrap();
assert_eq!(out["another_top_level"], json!("hello"));
assert_eq!(out["future_collection"]["xyz"]["a"], json!(1));
assert_eq!(out["manifest"]["future_field"], json!(42));
}
#[test]
fn missing_collections_deserialize_as_empty() {
let input = json!({"manifest": {"axgf": "1.0"}, "persons": {"p1": {"x": 1}}});
let parsed: FlatBundle = serde_json::from_value(input).unwrap();
assert_eq!(parsed.persons.len(), 1);
assert!(parsed.families.is_empty());
assert!(parsed.events.is_empty());
assert!(parsed.documents.is_empty());
}
}