1use crate::{limits::Failure, Limits};
3use bytes::Bytes;
4use structfs_core_store::{Codec, CodecErrorKind, CodecOperation, Error, Format, Value};
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum Profile {
9 ValueJson,
10 Json,
11 Cbor,
12 Flexbuffers,
13}
14impl Profile {
15 pub fn identifier(self) -> &'static str {
16 match self {
17 Self::ValueJson => "structfs-value-json/1",
18 Self::Json => "structfs-json/1",
19 Self::Cbor => "structfs-cbor/1",
20 Self::Flexbuffers => "structfs-flexbuffers/1",
21 }
22 }
23 pub fn format(self) -> Format {
24 match self {
25 Self::ValueJson => Format::VALUE_JSON,
26 Self::Json => Format::JSON,
27 Self::Cbor => Format::CBOR,
28 Self::Flexbuffers => Format::FLEXBUFFERS,
29 }
30 }
31}
32#[derive(Debug, Clone)]
35pub struct ValueCodec {
36 pub profile: Profile,
37 pub limits: Limits,
38 pub require_canonical: bool,
39}
40impl ValueCodec {
41 pub fn new(profile: Profile) -> Self {
42 Self {
43 profile,
44 limits: Limits::default(),
45 require_canonical: false,
46 }
47 }
48 pub fn with_limits(mut self, limits: Limits) -> Self {
49 self.limits = limits;
50 self
51 }
52 pub fn canonical(mut self) -> Self {
53 self.require_canonical = true;
54 self
55 }
56}
57
58pub fn transcode(bytes: &Bytes, source: &ValueCodec, target: &ValueCodec) -> Result<Bytes, Error> {
61 let value = source.decode(bytes, &source.profile.format())?;
62 target.encode(&value, &target.profile.format())
63}
64impl Codec for ValueCodec {
65 fn supports(&self, format: &Format) -> bool {
66 *format == self.profile.format()
67 }
68 fn decode(&self, bytes: &Bytes, format: &Format) -> Result<Value, Error> {
69 if !self.supports(format) {
70 return Err(Error::UnsupportedFormat(format.clone()));
71 }
72 let result = (|| {
73 if self.require_canonical && self.profile != Profile::ValueJson {
74 return Err(Failure(CodecErrorKind::UnsupportedProfile));
75 }
76 let v = match self.profile {
77 Profile::ValueJson => crate::json_profile::decode(bytes, &self.limits, true),
78 Profile::Json => crate::json_profile::decode(bytes, &self.limits, false),
79 Profile::Cbor => crate::cbor_profile::decode(bytes, &self.limits),
80 Profile::Flexbuffers => crate::flex_profile::decode(bytes, &self.limits),
81 }?;
82 if self.require_canonical
83 && crate::json_profile::encode(&v, &self.limits, true)?.as_slice() != bytes.as_ref()
84 {
85 return Err(Failure(CodecErrorKind::Noncanonical));
86 }
87 Ok(v)
88 })();
89 result.map_err(|e: Failure| e.core(format, CodecOperation::Decode, &self.limits))
90 }
91 fn encode(&self, value: &Value, format: &Format) -> Result<Bytes, Error> {
92 if !self.supports(format) {
93 return Err(Error::UnsupportedFormat(format.clone()));
94 }
95 let result = match self.profile {
96 Profile::ValueJson => crate::json_profile::encode(value, &self.limits, true),
97 Profile::Json => crate::json_profile::encode(value, &self.limits, false),
98 Profile::Cbor => crate::cbor_profile::encode(value, &self.limits),
99 Profile::Flexbuffers => crate::flex_profile::encode(value, &self.limits),
100 };
101 result
102 .map(Bytes::from)
103 .map_err(|e| e.core(format, CodecOperation::Encode, &self.limits))
104 }
105}
106macro_rules! default_codec {
107 ($name:ident,$profile:ident,$doc:literal) => {
108 #[doc=$doc]
109 #[derive(Debug, Clone, Copy, Default)]
110 pub struct $name;
111 impl Codec for $name {
112 fn supports(&self, f: &Format) -> bool {
113 ValueCodec::new(Profile::$profile).supports(f)
114 }
115 fn decode(&self, b: &Bytes, f: &Format) -> Result<Value, Error> {
116 ValueCodec::new(Profile::$profile).decode(b, f)
117 }
118 fn encode(&self, v: &Value, f: &Format) -> Result<Bytes, Error> {
119 ValueCodec::new(Profile::$profile).encode(v, f)
120 }
121 }
122 };
123}
124default_codec!(
125 JsonCodec,
126 Json,
127 "Strict plain JSON with default limits; bytes and non-finite floats fail."
128);
129default_codec!(
130 ValueJsonCodec,
131 ValueJson,
132 "Lossless canonical StructFS Value JSON v1 with default limits."
133);
134default_codec!(CborCodec, Cbor, "StructFS CBOR v1 with default limits.");
135default_codec!(
136 FlexbuffersCodec,
137 Flexbuffers,
138 "StructFS FlexBuffers v1 with default limits; NUL map keys fail."
139);
140
141pub struct MultiCodec {
145 codecs: Vec<Box<dyn Codec>>,
146}
147
148impl MultiCodec {
149 pub fn new() -> Self {
151 Self { codecs: Vec::new() }
152 }
153
154 pub fn add(&mut self, codec: impl Codec + 'static) {
156 self.codecs.push(Box::new(codec));
157 }
158
159 pub fn with_json() -> Self {
161 let mut mc = Self::new();
162 mc.add(JsonCodec);
163 mc
164 }
165
166 pub fn standard() -> Self {
168 let mut mc = Self::new();
169 mc.add(JsonCodec);
170 mc.add(CborCodec);
171 mc.add(FlexbuffersCodec);
172 mc.add(ValueJsonCodec);
173 mc
174 }
175}
176
177impl Default for MultiCodec {
178 fn default() -> Self {
179 Self::standard()
180 }
181}
182
183impl Codec for MultiCodec {
184 fn decode(&self, bytes: &Bytes, format: &Format) -> Result<Value, Error> {
185 for codec in &self.codecs {
186 if codec.supports(format) {
187 return codec.decode(bytes, format);
188 }
189 }
190 Err(Error::UnsupportedFormat(format.clone()))
191 }
192
193 fn encode(&self, value: &Value, format: &Format) -> Result<Bytes, Error> {
194 for codec in &self.codecs {
195 if codec.supports(format) {
196 return codec.encode(value, format);
197 }
198 }
199 Err(Error::UnsupportedFormat(format.clone()))
200 }
201
202 fn supports(&self, format: &Format) -> bool {
203 self.codecs.iter().any(|c| c.supports(format))
204 }
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210
211 #[test]
212 fn json_codec_roundtrip() {
213 let codec = JsonCodec;
214
215 let original = Value::Map(
216 [
217 ("name".to_string(), Value::String("Alice".to_string())),
218 ("age".to_string(), Value::Integer(30)),
219 ]
220 .into_iter()
221 .collect(),
222 );
223
224 let bytes = codec.encode(&original, &Format::JSON).unwrap();
225 let decoded = codec.decode(&bytes, &Format::JSON).unwrap();
226
227 assert_eq!(original, decoded);
228 }
229
230 #[test]
231 fn json_codec_rejects_other_formats() {
232 let codec = JsonCodec;
233
234 let bytes = Bytes::from_static(b"hello");
235 let result = codec.decode(&bytes, &Format::PROTOBUF);
236
237 assert!(matches!(result, Err(Error::UnsupportedFormat(_))));
238 }
239
240 #[test]
241 fn multi_codec_routes_correctly() {
242 let codec = MultiCodec::with_json();
243
244 assert!(codec.supports(&Format::JSON));
245 assert!(!codec.supports(&Format::PROTOBUF));
246
247 let value = Value::from("hello");
248 let bytes = codec.encode(&value, &Format::JSON).unwrap();
249 let decoded = codec.decode(&bytes, &Format::JSON).unwrap();
250
251 assert_eq!(value, decoded);
252 }
253
254 #[test]
255 fn json_codec_encode_unsupported_format() {
256 let codec = JsonCodec;
257 let value = Value::from("test");
258 let result = codec.encode(&value, &Format::PROTOBUF);
259 assert!(matches!(result, Err(Error::UnsupportedFormat(_))));
260 }
261
262 #[test]
263 fn json_codec_decode_invalid_json() {
264 let codec = JsonCodec;
265 let bytes = Bytes::from_static(b"not valid json {{{");
266 let result = codec.decode(&bytes, &Format::JSON);
267 assert!(matches!(
268 result,
269 Err(Error::Codec {
270 operation: structfs_core_store::CodecOperation::Decode,
271 ..
272 })
273 ));
274 }
275
276 #[test]
277 fn multi_codec_decode_unsupported() {
278 let codec = MultiCodec::new(); let bytes = Bytes::from_static(b"hello");
280 let result = codec.decode(&bytes, &Format::JSON);
281 assert!(matches!(result, Err(Error::UnsupportedFormat(_))));
282 }
283
284 #[test]
285 fn multi_codec_encode_unsupported() {
286 let codec = MultiCodec::new(); let value = Value::from("test");
288 let result = codec.encode(&value, &Format::JSON);
289 assert!(matches!(result, Err(Error::UnsupportedFormat(_))));
290 }
291
292 #[test]
293 fn multi_codec_default() {
294 let codec = MultiCodec::default();
295 assert!(codec.supports(&Format::JSON));
297 }
298
299 #[test]
300 fn multi_codec_add_custom() {
301 use structfs_core_store::Codec as CoreCodec;
302
303 struct CustomCodec;
304
305 impl CoreCodec for CustomCodec {
306 fn decode(&self, bytes: &Bytes, _format: &Format) -> Result<Value, Error> {
307 Ok(Value::Bytes(bytes.to_vec()))
308 }
309
310 fn encode(&self, _value: &Value, _format: &Format) -> Result<Bytes, Error> {
311 Ok(Bytes::from_static(b"custom"))
312 }
313
314 fn supports(&self, format: &Format) -> bool {
315 format == &Format::OCTET_STREAM
316 }
317 }
318
319 let mut codec = MultiCodec::new();
320 codec.add(CustomCodec);
321
322 assert!(codec.supports(&Format::OCTET_STREAM));
323 assert!(!codec.supports(&Format::JSON));
324
325 let decoded = codec
326 .decode(&Bytes::from_static(b"data"), &Format::OCTET_STREAM)
327 .unwrap();
328 assert_eq!(decoded, Value::Bytes(b"data".to_vec()));
329
330 let encoded = codec.encode(&Value::Null, &Format::OCTET_STREAM).unwrap();
331 assert_eq!(encoded.as_ref(), b"custom");
332 }
333
334 #[test]
335 fn json_codec_supports() {
336 let codec = JsonCodec;
337 assert!(codec.supports(&Format::JSON));
338 assert!(!codec.supports(&Format::PROTOBUF));
339 assert!(!codec.supports(&Format::OCTET_STREAM));
340 }
341
342 #[test]
343 fn json_codec_default() {
344 let codec: JsonCodec = Default::default();
345 assert!(codec.supports(&Format::JSON));
346 }
347
348 #[test]
349 fn json_codec_copy() {
350 let codec1 = JsonCodec;
351 let codec2 = codec1; let bytes = codec1.encode(&Value::from("test"), &Format::JSON).unwrap();
354 let decoded = codec2.decode(&bytes, &Format::JSON).unwrap();
355 assert_eq!(decoded, Value::from("test"));
356 }
357
358 #[test]
359 fn json_codec_debug() {
360 let codec = JsonCodec;
361 let debug = format!("{:?}", codec);
362 assert!(debug.contains("JsonCodec"));
363 }
364
365 fn sample() -> Value {
366 Value::Map(
367 [
368 ("name".to_string(), Value::from("Alice")),
369 ("age".to_string(), Value::Integer(30)),
370 ("score".to_string(), Value::Float(0.5)),
371 ("active".to_string(), Value::Bool(true)),
372 ("note".to_string(), Value::Null),
373 (
374 "tags".to_string(),
375 Value::Array(vec![Value::from("a"), Value::Integer(-7)]),
376 ),
377 ]
378 .into_iter()
379 .collect(),
380 )
381 }
382
383 #[test]
384 fn cbor_codec_roundtrip() {
385 let codec = CborCodec;
386 let bytes = codec.encode(&sample(), &Format::CBOR).unwrap();
387 assert_eq!(codec.decode(&bytes, &Format::CBOR).unwrap(), sample());
388 }
389
390 #[test]
391 fn flexbuffers_codec_roundtrip() {
392 let codec = FlexbuffersCodec;
393 let bytes = codec.encode(&sample(), &Format::FLEXBUFFERS).unwrap();
394 assert_eq!(
395 codec.decode(&bytes, &Format::FLEXBUFFERS).unwrap(),
396 sample()
397 );
398 }
399
400 #[test]
401 fn bytes_survive_the_binary_transports() {
402 let value = Value::Map(
405 [("payload".to_string(), Value::Bytes(vec![0, 159, 146, 150]))]
406 .into_iter()
407 .collect(),
408 );
409 for (codec, format) in [
410 (&CborCodec as &dyn Codec, Format::CBOR),
411 (&FlexbuffersCodec as &dyn Codec, Format::FLEXBUFFERS),
412 ] {
413 let bytes = codec.encode(&value, &format).unwrap();
414 assert_eq!(
415 codec.decode(&bytes, &format).unwrap(),
416 value,
417 "bytes mangled by {format}"
418 );
419 }
420 }
421
422 #[test]
423 fn binary_codecs_reject_other_formats() {
424 assert!(matches!(
425 CborCodec.encode(&Value::Null, &Format::JSON),
426 Err(Error::UnsupportedFormat(_))
427 ));
428 assert!(matches!(
429 FlexbuffersCodec.decode(&Bytes::from_static(b"x"), &Format::CBOR),
430 Err(Error::UnsupportedFormat(_))
431 ));
432 }
433
434 #[test]
435 fn binary_codecs_report_decode_errors() {
436 let garbage = Bytes::from_static(&[0xff, 0xfe, 0xfd]);
437 assert!(matches!(
438 CborCodec.decode(&garbage, &Format::CBOR),
439 Err(Error::Codec { .. })
440 ));
441 assert!(matches!(
442 FlexbuffersCodec.decode(&Bytes::from_static(&[]), &Format::FLEXBUFFERS),
443 Err(Error::Codec { .. })
444 ));
445 }
446
447 #[test]
448 fn standard_multi_codec_routes_every_transport() {
449 let codec = MultiCodec::standard();
450 for format in [Format::JSON, Format::CBOR, Format::FLEXBUFFERS] {
451 assert!(codec.supports(&format), "missing transport: {format}");
452 let bytes = codec.encode(&sample(), &format).unwrap();
453 assert_eq!(codec.decode(&bytes, &format).unwrap(), sample());
454 }
455 assert!(!codec.supports(&Format::PROTOBUF));
456 }
457
458 #[test]
459 fn multi_codec_supports_empty() {
460 let codec = MultiCodec::new();
461 assert!(!codec.supports(&Format::JSON));
462 assert!(!codec.supports(&Format::PROTOBUF));
463 }
464}