feat: improve legacy metadata and admin compatibility (#2202)

This commit is contained in:
weisd
2026-03-18 21:05:09 +08:00
committed by GitHub
parent 84077adf17
commit b9b7d86ae4
133 changed files with 11707 additions and 1945 deletions
+58
View File
@@ -19,9 +19,27 @@ use super::Policy;
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct PolicyDoc {
/// Version (omitempty), legacy: version.
#[serde(rename = "Version", alias = "version", default)]
pub version: i64,
/// Policy, legacy: policy. Serialize as Policy.
#[serde(rename = "Policy", alias = "policy")]
pub policy: Policy,
/// CreateDate (RFC3339), legacy: create_date. Serialize as CreateDate.
#[serde(
rename = "CreateDate",
alias = "create_date",
default,
with = "crate::serde_datetime::option"
)]
pub create_date: Option<OffsetDateTime>,
/// UpdateDate (RFC3339), legacy: update_date. Serialize as UpdateDate.
#[serde(
rename = "UpdateDate",
alias = "update_date",
default,
with = "crate::serde_datetime::option"
)]
pub update_date: Option<OffsetDateTime>,
}
@@ -73,3 +91,43 @@ impl TryFrom<Vec<u8>> for PolicyDoc {
.map_err(|_| serde_json::Error::custom("Failed to parse as PolicyDoc or Policy".to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::policy::Policy;
#[test]
fn test_policy_doc_timestamps_serialize_as_rfc3339() {
let policy = Policy::default();
let doc = PolicyDoc::new(policy);
let json = serde_json::to_string(&doc).expect("serialize");
// RFC3339 uses 'T' between date and time and 'Z' or offset for UTC
assert!(json.contains('T'), "PolicyDoc timestamps should be RFC3339 (contain 'T'); got: {}", json);
assert!(
json.contains('Z') || json.contains("+00:00"),
"PolicyDoc timestamps should be RFC3339 (contain 'Z' or +00:00); got: {}",
json
);
}
#[test]
fn test_policy_doc_deserialize_minio_style_rfc3339_timestamps() {
let minio_style = r#"{"Version":1,"Policy":{"Version":"2012-10-17","Statement":[]},"CreateDate":"2025-03-07T12:00:00Z","UpdateDate":"2025-03-07T12:00:00Z"}"#;
let doc: PolicyDoc = serde_json::from_str(minio_style).expect("deserialize MinIO-style JSON");
assert_eq!(doc.version, 1);
assert!(doc.create_date.is_some());
assert!(doc.update_date.is_some());
}
/// Round-trip: serialize then deserialize PolicyDoc; timestamps must match.
#[test]
fn test_policy_doc_timestamp_roundtrip() {
let policy = Policy::default();
let doc = PolicyDoc::new(policy);
let json = serde_json::to_string(&doc).expect("serialize");
let restored: PolicyDoc = serde_json::from_str(&json).expect("deserialize");
assert_eq!(doc.create_date, restored.create_date);
assert_eq!(doc.update_date, restored.update_date);
}
}
+10 -2
View File
@@ -55,9 +55,9 @@ impl Args<'_> {
pub struct Policy {
#[serde(default, rename = "ID")]
pub id: ID,
#[serde(rename = "Version")]
#[serde(default, rename = "Version")]
pub version: String,
#[serde(rename = "Statement")]
#[serde(default, rename = "Statement")]
pub statements: Vec<Statement>,
}
@@ -1084,6 +1084,14 @@ mod test {
);
}
#[test]
fn test_parse_empty_policy_object_as_implied_policy() {
let policy = Policy::parse_config(b"{}").expect("empty JSON object should parse");
assert!(policy.version.is_empty());
assert!(policy.statements.is_empty());
}
#[test]
fn test_statement_with_both_action_and_notaction_is_invalid() {
// Test: A statement with both Action and NotAction returns BothActionAndNotAction error