@@ -20,20 +20,23 @@ pub struct Annotations {
2020 #[ serde( skip_serializing_if = "Option::is_none" ) ]
2121 pub priority : Option < f32 > ,
2222 #[ serde( skip_serializing_if = "Option::is_none" , rename = "lastModified" ) ]
23- pub last_modified : Option < DateTime < Utc > > ,
23+ pub last_modified : Option < String > ,
2424}
2525
2626impl Annotations {
27- /// Creates a new Annotations instance specifically for resources
28- /// optional priority, and a timestamp (defaults to now if None)
27+ /// Creates annotations for a resource with priority and an RFC 3339 timestamp.
28+ ///
29+ /// # Panics
30+ ///
31+ /// Panics if `priority` is not in the inclusive range `0.0..=1.0`.
2932 pub fn for_resource ( priority : f32 , timestamp : DateTime < Utc > ) -> Self {
3033 assert ! (
3134 ( 0.0 ..=1.0 ) . contains( & priority) ,
3235 "Priority {priority} must be between 0.0 and 1.0"
3336 ) ;
34- Annotations {
37+ Self {
3538 priority : Some ( priority) ,
36- last_modified : Some ( timestamp) ,
39+ last_modified : Some ( timestamp. to_rfc3339 ( ) ) ,
3740 audience : None ,
3841 }
3942 }
@@ -48,12 +51,69 @@ impl Annotations {
4851 self
4952 }
5053
54+ /// Sets `lastModified` from a typed timestamp, serialized as RFC 3339.
5155 pub fn with_timestamp ( mut self , timestamp : DateTime < Utc > ) -> Self {
52- self . last_modified = Some ( timestamp) ;
56+ self . last_modified = Some ( timestamp. to_rfc3339 ( ) ) ;
5357 self
5458 }
5559
60+ /// Sets `lastModified` to the current time, serialized as RFC 3339.
5661 pub fn with_timestamp_now ( self ) -> Self {
5762 self . with_timestamp ( Utc :: now ( ) )
5863 }
5964}
65+
66+ #[ cfg( test) ]
67+ mod tests {
68+ use chrono:: TimeZone ;
69+ use serde_json:: json;
70+
71+ use super :: * ;
72+
73+ fn annotations_of ( value : serde_json:: Value ) -> Annotations {
74+ serde_json:: from_value :: < Annotations > ( value) . unwrap ( )
75+ }
76+
77+ #[ test]
78+ fn preserves_date_only_last_modified_verbatim ( ) {
79+ let annotations = annotations_of ( json ! ( { "lastModified" : "2025-01-12" } ) ) ;
80+ assert_eq ! ( annotations. last_modified. as_deref( ) , Some ( "2025-01-12" ) ) ;
81+ }
82+
83+ #[ test]
84+ fn preserves_rfc3339_last_modified_verbatim ( ) {
85+ let value = "2025-01-12T15:00:58Z" ;
86+ let annotations = annotations_of ( json ! ( { "lastModified" : value } ) ) ;
87+ assert_eq ! ( annotations. last_modified. as_deref( ) , Some ( value) ) ;
88+ }
89+
90+ #[ test]
91+ fn missing_last_modified_is_none ( ) {
92+ let annotations = annotations_of ( json ! ( { } ) ) ;
93+ assert_eq ! ( annotations. last_modified, None ) ;
94+ }
95+
96+ #[ test]
97+ fn null_last_modified_is_none ( ) {
98+ let annotations = annotations_of ( json ! ( { "lastModified" : null } ) ) ;
99+ assert_eq ! ( annotations. last_modified, None ) ;
100+ }
101+
102+ #[ test]
103+ fn invalid_last_modified_string_is_preserved ( ) {
104+ let annotations = annotations_of ( json ! ( { "lastModified" : "not-a-date" } ) ) ;
105+ assert_eq ! ( annotations. last_modified. as_deref( ) , Some ( "not-a-date" ) ) ;
106+ }
107+
108+ #[ test]
109+ fn timestamp_round_trips_as_rfc3339_string ( ) {
110+ let timestamp = Utc . with_ymd_and_hms ( 2025 , 1 , 12 , 15 , 0 , 58 ) . unwrap ( ) ;
111+ let annotations = Annotations :: default ( ) . with_timestamp ( timestamp) ;
112+
113+ let value = serde_json:: to_value ( & annotations) . unwrap ( ) ;
114+ assert_eq ! ( value[ "lastModified" ] , json!( timestamp. to_rfc3339( ) ) ) ;
115+
116+ let round_tripped: Annotations = serde_json:: from_value ( value) . unwrap ( ) ;
117+ assert_eq ! ( round_tripped, annotations) ;
118+ }
119+ }
0 commit comments