Skip to main content

datacard_rs/
metadata.rs

1//! Card metadata implementation
2
3use crate::error::Result;
4use serde::{Deserialize, Serialize};
5
6/// Card metadata (stored as JSON)
7#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
8pub struct CardMetadata {
9    /// Document identifier (e.g., "std::vec::Vec")
10    pub id: String,
11
12    /// Compressed payload size in bytes
13    pub compressed_size: u64,
14
15    /// Original CML size before compression (optional)
16    #[serde(skip_serializing_if = "Option::is_none")]
17    pub original_size: Option<u64>,
18
19    /// CML profile (e.g., "code:api", "legal:constitution")
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub profile: Option<String>,
22
23    /// Creation timestamp (Unix milliseconds)
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub created: Option<u64>,
26
27    /// BytePunch dictionary version used
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub dict_version: Option<String>,
30}
31
32impl CardMetadata {
33    /// Create minimal metadata with just ID and compressed size
34    pub fn new(id: impl Into<String>, compressed_size: u64) -> Self {
35        Self {
36            id: id.into(),
37            compressed_size,
38            original_size: None,
39            profile: None,
40            created: None,
41            dict_version: None,
42        }
43    }
44
45    /// Builder: set original size
46    pub fn with_original_size(mut self, size: u64) -> Self {
47        self.original_size = Some(size);
48        self
49    }
50
51    /// Builder: set CML profile
52    pub fn with_profile(mut self, profile: impl Into<String>) -> Self {
53        self.profile = Some(profile.into());
54        self
55    }
56
57    /// Builder: set creation timestamp
58    pub fn with_timestamp(mut self, timestamp: u64) -> Self {
59        self.created = Some(timestamp);
60        self
61    }
62
63    /// Builder: set dictionary version
64    pub fn with_dict_version(mut self, version: impl Into<String>) -> Self {
65        self.dict_version = Some(version.into());
66        self
67    }
68
69    /// Serialize to JSON bytes
70    pub fn to_json(&self) -> Result<Vec<u8>> {
71        Ok(serde_json::to_vec(self)?)
72    }
73
74    /// Deserialize from JSON bytes
75    pub fn from_json(bytes: &[u8]) -> Result<Self> {
76        Ok(serde_json::from_slice(bytes)?)
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn test_metadata_new() {
86        let metadata = CardMetadata::new("test::doc", 1234);
87        assert_eq!(metadata.id, "test::doc");
88        assert_eq!(metadata.compressed_size, 1234);
89        assert_eq!(metadata.original_size, None);
90    }
91
92    #[test]
93    fn test_metadata_builder() {
94        let metadata = CardMetadata::new("test::doc", 1234)
95            .with_original_size(5678)
96            .with_profile("code:api")
97            .with_timestamp(1703001234567)
98            .with_dict_version("cml-core-v1");
99
100        assert_eq!(metadata.original_size, Some(5678));
101        assert_eq!(metadata.profile.as_deref(), Some("code:api"));
102        assert_eq!(metadata.created, Some(1703001234567));
103        assert_eq!(metadata.dict_version.as_deref(), Some("cml-core-v1"));
104    }
105
106    #[test]
107    fn test_metadata_json_roundtrip() {
108        let metadata = CardMetadata::new("test::doc", 1234)
109            .with_original_size(5678)
110            .with_profile("code:api")
111            .with_timestamp(1703001234567);
112
113        let json = metadata.to_json().unwrap();
114        let loaded = CardMetadata::from_json(&json).unwrap();
115
116        assert_eq!(loaded, metadata);
117    }
118
119    #[test]
120    fn test_metadata_optional_fields() {
121        let metadata = CardMetadata::new("minimal", 100);
122        let json = metadata.to_json().unwrap();
123        let json_str = String::from_utf8(json).unwrap();
124
125        // Optional fields should be omitted
126        assert!(!json_str.contains("original_size"));
127        assert!(!json_str.contains("profile"));
128        assert!(!json_str.contains("created"));
129        assert!(!json_str.contains("dict_version"));
130    }
131}