use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct PolicyVersion {
pub hash: String,
pub loaded_at: String,
#[serde(default)]
pub label_set: Option<String>,
#[serde(default)]
pub generation: u64,
}
impl std::fmt::Display for PolicyVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} (loaded {})", self.hash, self.loaded_at)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Core {
pub version: String,
pub cedar: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct VersionInfo {
pub version: String,
pub core: Core,
pub policies: PolicyVersion,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub schema: Option<PolicyVersion>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn version_info_roundtrip() {
let json = serde_json::json!({
"version": "0.1.0",
"core": {
"version": "0.3.0",
"cedar": "0.11.0"
},
"policies": {
"hash": "abc123",
"loaded_at": "2025-01-01T00:00:00Z",
"label_set": "labels-v2",
"generation": 7
}
});
let info: VersionInfo = serde_json::from_value(json.clone()).unwrap();
assert_eq!(info.version, "0.1.0");
assert_eq!(info.core.cedar, "0.11.0");
assert_eq!(info.policies.hash, "abc123");
assert_eq!(info.policies.label_set.as_deref(), Some("labels-v2"));
assert_eq!(info.policies.generation, 7);
assert!(info.schema.is_none());
let reserialized = serde_json::to_value(&info).unwrap();
assert_eq!(json, reserialized);
}
#[test]
fn version_info_with_schema() {
let json = serde_json::json!({
"version": "0.1.0",
"core": {
"version": "0.3.0",
"cedar": "0.11.0"
},
"policies": {
"hash": "abc123",
"loaded_at": "2025-01-01T00:00:00Z"
},
"schema": {
"hash": "schema123",
"loaded_at": "2025-01-01T00:00:01Z"
}
});
let info: VersionInfo = serde_json::from_value(json).unwrap();
assert_eq!(
info.schema.as_ref().map(|v| v.hash.as_str()),
Some("schema123")
);
assert_eq!(info.policies.label_set, None);
assert_eq!(info.policies.generation, 0);
}
#[test]
fn generation_accepts_only_unsigned_integers() {
for generation in [
serde_json::json!(-1),
serde_json::json!(true),
serde_json::json!(1.5),
serde_json::json!("1"),
serde_json::json!(null),
] {
let value = serde_json::json!({
"hash": "hash", "loaded_at": "2026-09-05T00:00:00Z",
"label_set": null, "generation": generation,
});
assert!(serde_json::from_value::<PolicyVersion>(value).is_err());
}
let value = serde_json::json!({
"hash": "hash", "loaded_at": "2026-09-05T00:00:00Z",
"label_set": null, "generation": u64::MAX,
});
let version: PolicyVersion = serde_json::from_value(value.clone()).unwrap();
assert_eq!(version.generation, u64::MAX);
assert_eq!(serde_json::to_value(version).unwrap(), value);
}
}