22
33use std:: collections:: HashMap ;
44
5- use serde:: ser:: { SerializeMap as _, Serializer } ;
65use serde:: { Deserialize , Serialize } ;
6+ use serde_json:: Value ;
7+
8+ const IMAGE_FILE_NAME_FIELD : & str = "image_file_name" ;
9+ const WIDTH_FIELD : & str = "width" ;
10+ const HEIGHT_FIELD : & str = "height" ;
711
812/// Response from the create snapshot endpoint.
913#[ derive( Debug , Deserialize ) ]
@@ -24,40 +28,60 @@ pub struct SnapshotsManifest {
2428// Keep in sync with https://github.com/getsentry/sentry/blob/master/src/sentry/preprod/snapshots/manifest.py
2529/// Metadata for a single image in a snapshot manifest.
2630///
27- /// Serializes as a flat JSON object. User-provided sidecar fields are included
28- /// first, then CLI-managed fields (`image_file_name`, `width`, `height`) are
29- /// written last so they always take precedence.
30- #[ derive( Debug ) ]
31+ /// Serializes as a flat JSON object.
32+ ///
33+ /// CLI-managed fields (`image_file_name`, `width`, `height`) override any
34+ /// identically named fields provided by user sidecar metadata.
35+ #[ derive( Debug , Serialize ) ]
3136pub struct ImageMetadata {
32- pub image_file_name : String ,
33- pub width : u32 ,
34- pub height : u32 ,
35- pub extra : HashMap < String , serde_json:: Value > ,
37+ #[ serde( flatten) ]
38+ data : HashMap < String , Value > ,
39+ }
40+
41+ impl ImageMetadata {
42+ pub fn new (
43+ image_file_name : String ,
44+ width : u32 ,
45+ height : u32 ,
46+ mut extra : HashMap < String , Value > ,
47+ ) -> Self {
48+ extra. insert (
49+ IMAGE_FILE_NAME_FIELD . to_owned ( ) ,
50+ Value :: String ( image_file_name) ,
51+ ) ;
52+ extra. insert ( WIDTH_FIELD . to_owned ( ) , Value :: from ( width) ) ;
53+ extra. insert ( HEIGHT_FIELD . to_owned ( ) , Value :: from ( height) ) ;
54+
55+ Self { data : extra }
56+ }
3657}
3758
38- const RESERVED_KEYS : & [ & str ] = & [ "image_file_name" , "width" , "height" ] ;
39-
40- impl Serialize for ImageMetadata {
41- fn serialize < S : Serializer > ( & self , serializer : S ) -> Result < S :: Ok , S :: Error > {
42- let extra_count = self
43- . extra
44- . keys ( )
45- . filter ( |k| !RESERVED_KEYS . contains ( & k. as_str ( ) ) )
46- . count ( ) ;
47- let mut map = serializer. serialize_map ( Some ( extra_count + 3 ) ) ?;
48-
49- // CLI-managed fields first
50- map. serialize_entry ( "image_file_name" , & self . image_file_name ) ?;
51- map. serialize_entry ( "width" , & self . width ) ?;
52- map. serialize_entry ( "height" , & self . height ) ?;
53-
54- // User-provided sidecar fields, skipping any that conflict with CLI fields
55- for ( key, value) in & self . extra {
56- if !RESERVED_KEYS . contains ( & key. as_str ( ) ) {
57- map. serialize_entry ( key, value) ?;
58- }
59- }
60-
61- map. end ( )
59+ #[ cfg( test) ]
60+ mod tests {
61+ use super :: * ;
62+
63+ use serde_json:: json;
64+
65+ #[ test]
66+ fn cli_managed_fields_override_sidecar_fields ( ) {
67+ let extra = serde_json:: from_value ( json ! ( {
68+ ( IMAGE_FILE_NAME_FIELD ) : "from-sidecar.png" ,
69+ ( WIDTH_FIELD ) : 1 ,
70+ ( HEIGHT_FIELD ) : 2 ,
71+ "custom" : "keep-me"
72+ } ) )
73+ . unwrap ( ) ;
74+
75+ let metadata = ImageMetadata :: new ( "from-cli.png" . to_owned ( ) , 100 , 200 , extra) ;
76+ let serialized = serde_json:: to_value ( metadata) . unwrap ( ) ;
77+
78+ let expected = json ! ( {
79+ ( IMAGE_FILE_NAME_FIELD ) : "from-cli.png" ,
80+ ( WIDTH_FIELD ) : 100 ,
81+ ( HEIGHT_FIELD ) : 200 ,
82+ "custom" : "keep-me"
83+ } ) ;
84+
85+ assert_eq ! ( serialized, expected) ;
6286 }
6387}
0 commit comments