use std::collections::HashMap;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct PersistedHostState {
#[serde(default)]
pub proven_winners: Vec<String>,
#[serde(default)]
pub blocklisted: Vec<String>,
#[serde(default)]
pub waf_name: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct PersistedGeneBank {
#[serde(default)]
pub schema: u32,
#[serde(default)]
pub hosts: HashMap<String, PersistedHostState>,
}
impl PersistedGeneBank {
pub const SCHEMA_VERSION: u32 = 1;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_bank_round_trips_through_json() {
let bank = PersistedGeneBank::default();
let s = serde_json::to_string(&bank).expect("serialize empty");
let back: PersistedGeneBank = serde_json::from_str(&s).expect("deserialize empty");
assert_eq!(bank, back);
}
#[test]
fn missing_fields_default_via_serde() {
let json = r#"{"hosts": {"a.example": {"proven_winners": ["t1"]}}}"#;
let bank: PersistedGeneBank = serde_json::from_str(json).expect("parse minimal");
assert_eq!(bank.schema, 0, "missing schema defaults to 0");
let host = bank.hosts.get("a.example").expect("host present");
assert_eq!(host.proven_winners, vec!["t1".to_string()]);
assert!(
host.blocklisted.is_empty(),
"missing blocklisted defaults to empty"
);
assert!(host.waf_name.is_none(), "missing waf_name defaults to None");
}
#[test]
fn full_field_set_round_trips() {
let mut hosts = HashMap::new();
hosts.insert(
"host.example".into(),
PersistedHostState {
proven_winners: vec!["url:percent_encode".into(), "sql:keyword_morph".into()],
blocklisted: vec!["heavy:naive".into()],
waf_name: Some("cloudflare".into()),
},
);
let bank = PersistedGeneBank {
schema: PersistedGeneBank::SCHEMA_VERSION,
hosts,
};
let s = serde_json::to_string(&bank).expect("serialize full");
let back: PersistedGeneBank = serde_json::from_str(&s).expect("deserialize full");
assert_eq!(bank, back);
}
#[test]
fn unknown_fields_are_ignored() {
let json = r#"{
"schema": 1,
"hosts": {},
"future_field": "doesn't exist yet",
"another_future": {"nested": true}
}"#;
let bank: PersistedGeneBank =
serde_json::from_str(json).expect("unknown top-level fields must be ignored");
assert_eq!(bank.schema, 1);
}
}