use serde::{Deserialize, Serialize};
use super::status::Metadata;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PermitPolicy {
pub literal: String,
pub json: serde_json::Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotation_id: Option<String>,
pub cedar_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PoliciesDownload {
pub policies: Metadata,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SchemaDownload {
pub schema: Metadata,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum PolicyMatchReason {
PrincipalEq,
PrincipalIn,
PrincipalAny,
PrincipalIs,
PrincipalIsIn,
ActionEq,
ActionIn,
ActionAny,
ResourceEq,
ResourceIn,
ResourceAny,
ResourceIs,
ResourceIsIn,
#[serde(other)]
Unknown,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct PolicyMatch {
pub cedar_id: String,
pub reasons: Vec<PolicyMatchReason>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct UserPolicies {
pub user: String,
pub policies: Vec<serde_json::Value>,
#[serde(default)]
pub matches: Vec<PolicyMatch>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn permit_policy_deserialization() {
let json = serde_json::json!({
"literal": "permit(principal, action, resource);",
"json": {"effect": "permit"},
"annotation_id": "policy-1",
"cedar_id": "policy0"
});
let policy: PermitPolicy = serde_json::from_value(json).unwrap();
assert_eq!(policy.annotation_id.as_deref(), Some("policy-1"));
assert_eq!(policy.cedar_id, "policy0");
}
#[test]
fn user_policies_deserialization_v005() {
let json = serde_json::json!({
"user": "alice",
"policies": [
{"effect": "permit", "principal": {"op": "==", "entity": {"type": "User", "id": "alice"}}}
],
"matches": [
{"cedar_id": "policy0", "reasons": ["PrincipalEq"]}
]
});
let up: UserPolicies = serde_json::from_value(json).unwrap();
assert_eq!(up.user, "alice");
assert_eq!(up.policies.len(), 1);
assert_eq!(up.matches.len(), 1);
assert_eq!(up.matches[0].cedar_id, "policy0");
assert_eq!(up.matches[0].reasons, vec![PolicyMatchReason::PrincipalEq]);
}
#[test]
fn user_policies_backward_compat_no_matches() {
let json = serde_json::json!({
"user": "alice",
"policies": [{"effect": "permit"}]
});
let up: UserPolicies = serde_json::from_value(json).unwrap();
assert_eq!(up.user, "alice");
assert!(up.matches.is_empty());
}
#[test]
fn policy_match_reason_roundtrip() {
let reasons = vec![
PolicyMatchReason::PrincipalEq,
PolicyMatchReason::PrincipalIn,
PolicyMatchReason::PrincipalAny,
PolicyMatchReason::ActionEq,
PolicyMatchReason::ActionIn,
PolicyMatchReason::ActionAny,
PolicyMatchReason::ResourceAny,
];
let json = serde_json::to_value(&reasons).unwrap();
let deserialized: Vec<PolicyMatchReason> = serde_json::from_value(json).unwrap();
assert_eq!(reasons, deserialized);
}
#[test]
fn unknown_policy_match_reason_is_forward_compatible() {
let reason: PolicyMatchReason =
serde_json::from_value(serde_json::json!("FutureReason")).unwrap();
assert_eq!(reason, PolicyMatchReason::Unknown);
}
#[test]
fn schema_download_deserialization() {
let json = serde_json::json!({
"schema": {
"timestamp": "2026-01-01T00:00:00Z",
"sha256": "schema-hash",
"size": 128,
"entries": 1,
"content": "{\"\": {\"entityTypes\": {}, \"actions\": {}}}"
}
});
let download: SchemaDownload = serde_json::from_value(json).unwrap();
assert_eq!(download.schema.sha256, "schema-hash");
assert_eq!(download.schema.entries, 1);
}
}