refactor(time): migrate audit and notify timestamps to jiff (#5707)

* refactor(time): migrate audit and notify timestamps to jiff

Co-Authored-By: heihutu <heihutu@gmail.com>

* test(ecstore): initialize heal walk decode error

Co-Authored-By: heihutu <heihutu@gmail.com>

* refactor(targets): parse MySQL event time with jiff

Preserve MySQL DATETIME(6) wall-time formatting for RFC3339 eventTime values while removing the direct chrono dependency from rustfs-targets.

Co-Authored-By: heihutu <heihutu@gmail.com>

* chore(deps): prune unused workspace dependencies

Apply cargo shear --fix to remove unused path-clean and s3select-api tempfile entries after the scoped jiff migration.

Co-Authored-By: heihutu <heihutu@gmail.com>

* test(ecstore): remove duplicate heal walk decode error init

Remove the duplicate decode_error field from the heal walk test collector initializer so lib-test clippy compiles on CI.

Co-Authored-By: heihutu <heihutu@gmail.com>

* refactor(policy): emit OPA timestamps with jiff

Co-Authored-By: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
houseme
2026-08-04 23:20:44 +08:00
committed by GitHub
parent b14805af47
commit 510b0350d6
16 changed files with 206 additions and 55 deletions
+49 -3
View File
@@ -503,10 +503,35 @@ pub(crate) fn extract_event_time(body: &[u8]) -> Result<String, TargetError> {
.and_then(|v| v.as_str())
.ok_or_else(|| TargetError::Serialization("event_data is missing Records[0].eventTime".to_string()))?;
let dt = chrono::DateTime::parse_from_rfc3339(event_time)
let pieces = jiff::fmt::temporal::Pieces::parse(event_time)
.map_err(|e| TargetError::Serialization(format!("Failed to parse eventTime '{}': {}", event_time, e)))?;
let time = pieces
.time()
.ok_or_else(|| TargetError::Serialization(format!("Failed to parse eventTime '{}': missing RFC3339 time", event_time)))?;
if pieces.offset().is_none() {
return Err(TargetError::Serialization(format!(
"Failed to parse eventTime '{}': missing RFC3339 offset",
event_time
)));
}
if pieces.time_zone_annotation().is_some() {
return Err(TargetError::Serialization(format!(
"Failed to parse eventTime '{}': RFC3339 timestamp must not include a time zone annotation",
event_time
)));
}
let date = pieces.date();
Ok(dt.format("%Y-%m-%d %H:%M:%S%.6f").to_string())
Ok(format!(
"{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:06}",
date.year(),
date.month(),
date.day(),
time.hour(),
time.minute(),
time.second(),
time.subsec_nanosecond() / 1_000
))
}
/// Validates the required `event_time`/`event_data` columns and reports whether
@@ -1366,7 +1391,14 @@ mod tests {
let body =
br#"{"EventName":"s3:ObjectCreated:Put","Key":"bucket/obj.txt","Records":[{"eventTime":"2026-05-03T10:00:00Z"}]}"#;
let result = extract_event_time(body).expect("valid event_time");
assert!(result.starts_with("2026-05-03 10:00:00"));
assert_eq!(result, "2026-05-03 10:00:00.000000");
}
#[test]
fn extract_event_time_preserves_input_offset_wall_time() {
let body = br#"{"EventName":"s3:ObjectCreated:Put","Records":[{"eventTime":"2026-05-03T10:00:00.123456789+08:00"}]}"#;
let result = extract_event_time(body).expect("valid event_time");
assert_eq!(result, "2026-05-03 10:00:00.123456");
}
#[test]
@@ -1390,6 +1422,20 @@ mod tests {
assert!(err.to_string().contains("Failed to parse eventTime"));
}
#[test]
fn extract_event_time_without_offset_errors() {
let body = br#"{"Records":[{"eventTime":"2026-05-03T10:00:00"}]}"#;
let err = extract_event_time(body).expect_err("missing offset should fail");
assert!(err.to_string().contains("missing RFC3339 offset"));
}
#[test]
fn extract_event_time_with_time_zone_annotation_errors() {
let body = br#"{"Records":[{"eventTime":"2026-05-03T10:00:00+08:00[Asia/Shanghai]"}]}"#;
let err = extract_event_time(body).expect_err("time zone annotation should fail");
assert!(err.to_string().contains("must not include a time zone annotation"));
}
#[test]
fn extract_event_time_missing_records_errors() {
let body = br#"{"EventName":"s3:ObjectCreated:Put"}"#;