use std::collections::HashMap;
use derive_builder::Builder;
use serde::{Deserialize, Serialize};
use crate::blob_metadata::BlobMetadata;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Builder)]
pub struct FileMetadata {
#[builder(default)]
pub blobs: Vec<BlobMetadata>,
#[builder(default)]
#[serde(default)]
#[serde(skip_serializing_if = "HashMap::is_empty")]
pub properties: HashMap<String, String>,
}
impl FileMetadata {
pub fn memory_usage(&self) -> usize {
self.blobs
.iter()
.map(|blob| blob.memory_usage())
.sum::<usize>()
+ self
.properties
.iter()
.map(|(k, v)| k.len() + v.len())
.sum::<usize>()
+ std::mem::size_of::<Self>()
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use super::*;
use crate::blob_metadata::BlobMetadataBuilder;
#[test]
fn test_file_metadata_builder() {
let mut properties = HashMap::new();
properties.insert(String::from("key1"), String::from("value1"));
let blob_metadata = BlobMetadataBuilder::default()
.blob_type("type1".to_string())
.offset(10)
.length(30)
.build()
.unwrap();
let metadata = FileMetadataBuilder::default()
.blobs(vec![blob_metadata.clone()])
.properties(properties.clone())
.build()
.unwrap();
assert_eq!(properties, metadata.properties);
assert_eq!(vec![blob_metadata], metadata.blobs);
}
#[test]
fn test_file_metadata_serialization() {
let mut properties = HashMap::new();
properties.insert(String::from("key1"), String::from("value1"));
let blob_metadata = BlobMetadataBuilder::default()
.blob_type("type1".to_string())
.offset(10)
.length(30)
.build()
.unwrap();
let metadata = FileMetadataBuilder::default()
.blobs(vec![blob_metadata.clone()])
.properties(properties.clone())
.build()
.unwrap();
let serialized = serde_json::to_string(&metadata).unwrap();
assert_eq!(
serialized,
r#"{"blobs":[{"type":"type1","fields":[],"snapshot-id":0,"sequence-number":0,"offset":10,"length":30}],"properties":{"key1":"value1"}}"#
);
}
#[test]
fn test_file_metadata_deserialization() {
let data = r#"{"blobs":[{"type":"type1","fields":[],"snapshot-id":0,"sequence-number":0,"offset":10,"length":30}],"properties":{"key1":"value1"}}"#;
let deserialized: FileMetadata = serde_json::from_str(data).unwrap();
assert_eq!(deserialized.blobs[0].blob_type, "type1");
assert_eq!(deserialized.blobs[0].offset, 10);
assert_eq!(deserialized.blobs[0].length, 30);
assert_eq!(deserialized.properties.get("key1").unwrap(), "value1");
}
#[test]
fn test_empty_properties_not_serialized() {
let blob_metadata = BlobMetadataBuilder::default()
.blob_type("type1".to_string())
.offset(10)
.length(30)
.build()
.unwrap();
let metadata = FileMetadataBuilder::default()
.blobs(vec![blob_metadata.clone()])
.build()
.unwrap();
let serialized = serde_json::to_string(&metadata).unwrap();
assert_eq!(
serialized,
r#"{"blobs":[{"type":"type1","fields":[],"snapshot-id":0,"sequence-number":0,"offset":10,"length":30}]}"#
);
}
#[test]
fn test_empty_blobs_serialization() {
let metadata = FileMetadataBuilder::default()
.blobs(vec![])
.build()
.unwrap();
let serialized = serde_json::to_string(&metadata).unwrap();
assert_eq!(serialized, r#"{"blobs":[]}"#);
}
#[test]
fn test_missing_blobs_deserialization() {
let data = r#"{"properties":{"key1":"value1"}}"#;
let deserialized = serde_json::from_str::<FileMetadata>(data);
assert!(deserialized
.unwrap_err()
.to_string()
.contains("missing field `blobs`"));
}
}