1use std::collections::HashMap;
16
17use derive_builder::Builder;
18use serde::{Deserialize, Serialize};
19
20use crate::blob_metadata::BlobMetadata;
21
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Builder)]
24pub struct FileMetadata {
25 #[builder(default)]
27 pub blobs: Vec<BlobMetadata>,
28
29 #[builder(default)]
31 #[serde(default)]
32 #[serde(skip_serializing_if = "HashMap::is_empty")]
33 pub properties: HashMap<String, String>,
34}
35
36impl FileMetadata {
37 pub fn memory_usage(&self) -> usize {
39 self.blobs
40 .iter()
41 .map(|blob| blob.memory_usage())
42 .sum::<usize>()
43 + self
44 .properties
45 .iter()
46 .map(|(k, v)| k.len() + v.len())
47 .sum::<usize>()
48 + std::mem::size_of::<Self>()
49 }
50}
51
52#[cfg(test)]
53mod tests {
54 use std::collections::HashMap;
55
56 use super::*;
57 use crate::blob_metadata::BlobMetadataBuilder;
58
59 #[test]
60 fn test_file_metadata_builder() {
61 let mut properties = HashMap::new();
62 properties.insert(String::from("key1"), String::from("value1"));
63
64 let blob_metadata = BlobMetadataBuilder::default()
65 .blob_type("type1".to_string())
66 .offset(10)
67 .length(30)
68 .build()
69 .unwrap();
70
71 let metadata = FileMetadataBuilder::default()
72 .blobs(vec![blob_metadata.clone()])
73 .properties(properties.clone())
74 .build()
75 .unwrap();
76
77 assert_eq!(properties, metadata.properties);
78 assert_eq!(vec![blob_metadata], metadata.blobs);
79 }
80
81 #[test]
82 fn test_file_metadata_serialization() {
83 let mut properties = HashMap::new();
84 properties.insert(String::from("key1"), String::from("value1"));
85
86 let blob_metadata = BlobMetadataBuilder::default()
87 .blob_type("type1".to_string())
88 .offset(10)
89 .length(30)
90 .build()
91 .unwrap();
92
93 let metadata = FileMetadataBuilder::default()
94 .blobs(vec![blob_metadata.clone()])
95 .properties(properties.clone())
96 .build()
97 .unwrap();
98
99 let serialized = serde_json::to_string(&metadata).unwrap();
100 assert_eq!(
101 serialized,
102 r#"{"blobs":[{"type":"type1","fields":[],"snapshot-id":0,"sequence-number":0,"offset":10,"length":30}],"properties":{"key1":"value1"}}"#
103 );
104 }
105
106 #[test]
107 fn test_file_metadata_deserialization() {
108 let data = r#"{"blobs":[{"type":"type1","fields":[],"snapshot-id":0,"sequence-number":0,"offset":10,"length":30}],"properties":{"key1":"value1"}}"#;
109 let deserialized: FileMetadata = serde_json::from_str(data).unwrap();
110
111 assert_eq!(deserialized.blobs[0].blob_type, "type1");
112 assert_eq!(deserialized.blobs[0].offset, 10);
113 assert_eq!(deserialized.blobs[0].length, 30);
114 assert_eq!(deserialized.properties.get("key1").unwrap(), "value1");
115 }
116
117 #[test]
118 fn test_empty_properties_not_serialized() {
119 let blob_metadata = BlobMetadataBuilder::default()
120 .blob_type("type1".to_string())
121 .offset(10)
122 .length(30)
123 .build()
124 .unwrap();
125
126 let metadata = FileMetadataBuilder::default()
127 .blobs(vec![blob_metadata.clone()])
128 .build()
129 .unwrap();
130
131 let serialized = serde_json::to_string(&metadata).unwrap();
132 assert_eq!(
133 serialized,
134 r#"{"blobs":[{"type":"type1","fields":[],"snapshot-id":0,"sequence-number":0,"offset":10,"length":30}]}"#
135 );
136 }
137
138 #[test]
139 fn test_empty_blobs_serialization() {
140 let metadata = FileMetadataBuilder::default()
141 .blobs(vec![])
142 .build()
143 .unwrap();
144
145 let serialized = serde_json::to_string(&metadata).unwrap();
146 assert_eq!(serialized, r#"{"blobs":[]}"#);
147 }
148
149 #[test]
150 fn test_missing_blobs_deserialization() {
151 let data = r#"{"properties":{"key1":"value1"}}"#;
152 let deserialized = serde_json::from_str::<FileMetadata>(data);
153
154 assert!(deserialized
155 .unwrap_err()
156 .to_string()
157 .contains("missing field `blobs`"));
158 }
159}