puffin/
file_metadata.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::collections::HashMap;
16
17use derive_builder::Builder;
18use serde::{Deserialize, Serialize};
19
20use crate::blob_metadata::BlobMetadata;
21
22/// Metadata of a Puffin file
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Builder)]
24pub struct FileMetadata {
25    /// Metadata for each blob in the file
26    #[builder(default)]
27    pub blobs: Vec<BlobMetadata>,
28
29    /// Storage for arbitrary meta-information, like writer identification/version
30    #[builder(default)]
31    #[serde(default)]
32    #[serde(skip_serializing_if = "HashMap::is_empty")]
33    pub properties: HashMap<String, String>,
34}
35
36impl FileMetadata {
37    /// Calculates the memory usage of the file metadata in bytes.
38    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}