use serde::{Deserialize, Serialize};
use crate::acl::ApproveScope;
use super::create::CreateAclResultBody;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct UpdateAclBody {
pub did: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub allowed_contexts: Option<Vec<String>>,
#[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>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub approve_scope: Option<ApproveScope>,
#[serde(
rename = "allowedKeys",
default,
deserialize_with = "double_option",
skip_serializing_if = "Option::is_none"
)]
pub allowed_keys: Option<Option<Vec<String>>>,
}
fn double_option<'de, T, D>(de: D) -> Result<Option<Option<T>>, D::Error>
where
T: Deserialize<'de>,
D: serde::Deserializer<'de>,
{
Deserialize::deserialize(de).map(Some)
}
pub type UpdateAclResultBody = CreateAclResultBody;
#[cfg(test)]
mod tests {
use super::*;
fn body(json: serde_json::Value) -> UpdateAclBody {
serde_json::from_value(json).unwrap()
}
#[test]
fn allowed_keys_round_trips_as_camel_case() {
let b = UpdateAclBody {
did: "did:key:zA".into(),
label: None,
allowed_contexts: None,
step_up_approver: None,
step_up_require: None,
approve_scope: None,
allowed_keys: Some(Some(vec!["key-1".into(), "key-2".into()])),
};
let v = serde_json::to_value(&b).unwrap();
assert!(v.get("allowedKeys").is_some(), "{v}");
assert!(v.get("allowed_keys").is_none(), "one spelling only: {v}");
let back: UpdateAclBody = serde_json::from_value(v).unwrap();
assert_eq!(
back.allowed_keys,
Some(Some(vec!["key-1".to_string(), "key-2".to_string()]))
);
let b = body(serde_json::json!({
"did": "did:key:zA", "allowed_keys": ["key-1"]
}));
assert_eq!(b.allowed_keys, None);
}
#[test]
fn allowed_keys_distinguishes_absent_null_and_empty() {
let absent = body(serde_json::json!({ "did": "did:key:zA" }));
assert_eq!(absent.allowed_keys, None, "omitted = leave unchanged");
let null = body(serde_json::json!({ "did": "did:key:zA", "allowedKeys": null }));
assert_eq!(null.allowed_keys, Some(None), "null = clear the filter");
let empty = body(serde_json::json!({ "did": "did:key:zA", "allowedKeys": [] }));
assert_eq!(
empty.allowed_keys,
Some(Some(vec![])),
"empty array = authorized on NO keys — not a wildcard, not a clear"
);
}
#[test]
fn allowed_keys_clear_serializes_as_explicit_null() {
let b = UpdateAclBody {
did: "did:key:zA".into(),
label: None,
allowed_contexts: None,
step_up_approver: None,
step_up_require: None,
approve_scope: None,
allowed_keys: Some(None),
};
let v = serde_json::to_value(&b).unwrap();
assert!(v.get("allowedKeys").is_some_and(|k| k.is_null()), "{v}");
let untouched = UpdateAclBody {
allowed_keys: None,
..b
};
let v = serde_json::to_value(&untouched).unwrap();
assert!(v.get("allowedKeys").is_none(), "omitted stays omitted: {v}");
}
}