Skip to main content

structfs_core_store/
format.rs

1//! Format hints for wire encoding.
2
3use std::borrow::Cow;
4use std::fmt;
5
6/// A hint about the wire format of raw bytes.
7///
8/// Format is used to guide codecs when parsing `Record::Raw` bytes into
9/// `Value`, or when serializing `Value` back to bytes.
10///
11/// This uses MIME-type-like strings for familiarity, but you can use
12/// any string that your codecs understand.
13#[derive(Clone, Debug, PartialEq, Eq, Hash)]
14pub struct Format(pub Cow<'static, str>);
15
16impl Format {
17    // Common formats as constants for efficiency
18
19    /// JSON format (`application/json`)
20    pub const JSON: Format = Format(Cow::Borrowed("application/json"));
21
22    /// StructFS Value v1 tagged JSON (proposed media identifier).
23    pub const VALUE_JSON: Format = Format(Cow::Borrowed(
24        "application/vnd.structfs.value+json;version=1",
25    ));
26
27    /// Protocol Buffers (`application/protobuf`)
28    pub const PROTOBUF: Format = Format(Cow::Borrowed("application/protobuf"));
29
30    /// MessagePack (`application/msgpack`)
31    pub const MSGPACK: Format = Format(Cow::Borrowed("application/msgpack"));
32
33    /// CBOR (`application/cbor`)
34    pub const CBOR: Format = Format(Cow::Borrowed("application/cbor"));
35
36    /// FlexBuffers (`application/x-flexbuffers`; no registered MIME type)
37    pub const FLEXBUFFERS: Format = Format(Cow::Borrowed("application/x-flexbuffers"));
38
39    /// Opaque binary data (`application/octet-stream`)
40    pub const OCTET_STREAM: Format = Format(Cow::Borrowed("application/octet-stream"));
41
42    /// A parsed Value that was never serialized.
43    ///
44    /// Used when a Record contains a Value that hasn't been through a codec.
45    pub const VALUE: Format = Format(Cow::Borrowed("application/x-structfs-value"));
46
47    /// Create a format from a static string.
48    pub const fn from_static(s: &'static str) -> Self {
49        Format(Cow::Borrowed(s))
50    }
51
52    /// Create a format from an owned string.
53    pub fn new(s: impl Into<String>) -> Self {
54        Format(Cow::Owned(s.into()))
55    }
56
57    /// Get the format string.
58    pub fn as_str(&self) -> &str {
59        &self.0
60    }
61
62    /// Check if this is JSON format.
63    pub fn is_json(&self) -> bool {
64        self == &Self::JSON
65    }
66
67    /// Check if this is protobuf format.
68    pub fn is_protobuf(&self) -> bool {
69        self == &Self::PROTOBUF
70    }
71
72    /// Check if this is the VALUE format (parsed, never serialized).
73    pub fn is_value(&self) -> bool {
74        self == &Self::VALUE
75    }
76}
77
78impl fmt::Display for Format {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        write!(f, "{}", self.0)
81    }
82}
83
84impl From<&'static str> for Format {
85    fn from(s: &'static str) -> Self {
86        Format(Cow::Borrowed(s))
87    }
88}
89
90impl From<String> for Format {
91    fn from(s: String) -> Self {
92        Format(Cow::Owned(s))
93    }
94}
95
96impl AsRef<str> for Format {
97    fn as_ref(&self) -> &str {
98        &self.0
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn constants_work() {
108        assert_eq!(Format::JSON.as_str(), "application/json");
109        assert!(Format::JSON.is_json());
110        assert!(!Format::JSON.is_protobuf());
111    }
112
113    #[test]
114    fn custom_formats() {
115        let f = Format::new("application/x-custom");
116        assert_eq!(f.as_str(), "application/x-custom");
117    }
118
119    #[test]
120    fn equality() {
121        assert_eq!(Format::JSON, Format::from("application/json"));
122        assert_eq!(Format::JSON, Format::new("application/json".to_string()));
123    }
124
125    #[test]
126    fn from_static() {
127        let f = Format::from_static("text/plain");
128        assert_eq!(f.as_str(), "text/plain");
129    }
130
131    #[test]
132    fn is_protobuf() {
133        assert!(Format::PROTOBUF.is_protobuf());
134        assert!(!Format::JSON.is_protobuf());
135    }
136
137    #[test]
138    fn is_value() {
139        assert!(Format::VALUE.is_value());
140        assert!(!Format::JSON.is_value());
141    }
142
143    #[test]
144    fn display_impl() {
145        let f = Format::JSON;
146        assert_eq!(format!("{}", f), "application/json");
147
148        let custom = Format::new("text/html");
149        assert_eq!(format!("{}", custom), "text/html");
150    }
151
152    #[test]
153    fn from_string() {
154        let owned = String::from("application/xml");
155        let f: Format = owned.into();
156        assert_eq!(f.as_str(), "application/xml");
157    }
158
159    #[test]
160    fn as_ref_str() {
161        let f = Format::CBOR;
162        let s: &str = f.as_ref();
163        assert_eq!(s, "application/cbor");
164    }
165
166    #[test]
167    fn hash_works() {
168        use std::collections::HashSet;
169        let mut set = HashSet::new();
170        set.insert(Format::JSON);
171        set.insert(Format::PROTOBUF);
172        set.insert(Format::JSON); // Duplicate
173
174        assert_eq!(set.len(), 2);
175        assert!(set.contains(&Format::JSON));
176        assert!(set.contains(&Format::PROTOBUF));
177    }
178
179    #[test]
180    fn all_constants() {
181        // Cover all constant definitions
182        assert_eq!(Format::MSGPACK.as_str(), "application/msgpack");
183        assert_eq!(Format::CBOR.as_str(), "application/cbor");
184        assert_eq!(Format::FLEXBUFFERS.as_str(), "application/x-flexbuffers");
185        assert_eq!(Format::OCTET_STREAM.as_str(), "application/octet-stream");
186        assert_eq!(Format::VALUE.as_str(), "application/x-structfs-value");
187    }
188
189    #[test]
190    fn clone_works() {
191        let f1 = Format::new("custom/format");
192        let f2 = f1.clone();
193        assert_eq!(f1, f2);
194    }
195
196    #[test]
197    fn debug_impl() {
198        let f = Format::JSON;
199        let debug = format!("{:?}", f);
200        assert!(debug.contains("application/json"));
201    }
202}