use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct CreateAclBody {
pub did: String,
pub role: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
#[serde(default, alias = "allowed_contexts")]
pub allowed_contexts: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none", alias = "expires_at")]
pub expires_at: Option<u64>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "step_up_approver"
)]
pub step_up_approver: Option<String>,
#[serde(
default,
skip_serializing_if = "Option::is_none",
alias = "step_up_require"
)]
pub step_up_require: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct CreateAclResultBody {
pub did: String,
pub role: String,
pub label: Option<String>,
pub allowed_contexts: Vec<String>,
pub created_at: u64,
pub created_by: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_at: Option<u64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub step_up_approver: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub step_up_require: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn camelcase_payload_populates_scope_and_expiry() {
let json = serde_json::json!({
"did": "did:key:z6MkSubject",
"role": "admin",
"allowedContexts": ["ctx-a", "ctx-b"],
"expiresAt": 1_800_000_000u64,
"stepUpApprover": "did:key:z6MkApprover",
"stepUpRequire": "delegated",
});
let body: CreateAclBody = serde_json::from_value(json).unwrap();
assert_eq!(body.allowed_contexts, vec!["ctx-a", "ctx-b"]);
assert_eq!(body.expires_at, Some(1_800_000_000));
assert_eq!(
body.step_up_approver.as_deref(),
Some("did:key:z6MkApprover")
);
assert_eq!(body.step_up_require.as_deref(), Some("delegated"));
}
#[test]
fn snakecase_payload_still_accepted_via_alias() {
let json = serde_json::json!({
"did": "did:key:z6MkSubject",
"role": "admin",
"allowed_contexts": ["ctx-a"],
"expires_at": 1_800_000_000u64,
"step_up_approver": "did:key:z6MkApprover",
});
let body: CreateAclBody = serde_json::from_value(json).unwrap();
assert_eq!(body.allowed_contexts, vec!["ctx-a"]);
assert_eq!(body.expires_at, Some(1_800_000_000));
assert_eq!(
body.step_up_approver.as_deref(),
Some("did:key:z6MkApprover")
);
}
#[test]
fn unknown_field_is_rejected() {
let json = serde_json::json!({
"did": "did:key:z6MkSubject",
"role": "admin",
"allowedContext": ["ctx-a"], });
assert!(serde_json::from_value::<CreateAclBody>(json).is_err());
}
#[test]
fn serializes_as_camelcase() {
let body = CreateAclBody {
did: "did:key:z6MkSubject".into(),
role: "admin".into(),
label: None,
allowed_contexts: vec!["ctx-a".into()],
expires_at: Some(1_800_000_000),
step_up_approver: None,
step_up_require: None,
};
let v = serde_json::to_value(&body).unwrap();
assert!(v.get("allowedContexts").is_some());
assert!(v.get("expiresAt").is_some());
assert!(v.get("allowed_contexts").is_none());
}
}