1use serde::{de::DeserializeOwned, Deserialize, Serialize};
8
9use super::encoding::Encoding;
10use super::error::Error;
11
12const VERSIONED_VALUE_MAGIC: &[u8; 4] = b"PLVV";
13const VERSIONED_VALUE_WIRE_VERSION: u8 = 1;
14const ENCODING_RAW: u8 = 0;
15const ENCODING_CBOR: u8 = 1;
16const ENCODING_JSON: u8 = 2;
17const ENCODING_CUSTOM: u8 = 3;
18const HEADER_LEN: usize = 4 + 1 + 1 + 8 + 4 + 4 + 8;
19
20pub trait ValueCodec {
26 fn encode<T: Serialize>(&self, value: &T) -> Result<Vec<u8>, Error>;
28
29 fn decode<T: DeserializeOwned>(&self, bytes: &[u8]) -> Result<T, Error>;
31}
32
33#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
35pub struct JsonCodec;
36
37#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
39pub struct CborCodec;
40
41#[derive(Clone, Debug, PartialEq, Eq)]
43pub struct VersionedJsonCodec {
44 schema: String,
45 version: u64,
46}
47
48#[derive(Clone, Debug, PartialEq, Eq)]
50pub struct VersionedCborCodec {
51 schema: String,
52 version: u64,
53}
54
55pub fn encode_json<T: Serialize>(value: &T) -> Result<Vec<u8>, Error> {
57 serde_json::to_vec(value).map_err(|err| Error::Serialize(err.to_string()))
58}
59
60pub fn decode_json<T: DeserializeOwned>(bytes: &[u8]) -> Result<T, Error> {
62 serde_json::from_slice(bytes).map_err(|err| Error::Deserialize(err.to_string()))
63}
64
65pub fn encode_cbor<T: Serialize>(value: &T) -> Result<Vec<u8>, Error> {
67 serde_cbor::ser::to_vec_packed(value).map_err(|err| Error::Serialize(err.to_string()))
68}
69
70pub fn decode_cbor<T: DeserializeOwned>(bytes: &[u8]) -> Result<T, Error> {
72 serde_cbor::from_slice(bytes).map_err(|err| Error::Deserialize(err.to_string()))
73}
74
75impl ValueCodec for JsonCodec {
76 fn encode<T: Serialize>(&self, value: &T) -> Result<Vec<u8>, Error> {
77 encode_json(value)
78 }
79
80 fn decode<T: DeserializeOwned>(&self, bytes: &[u8]) -> Result<T, Error> {
81 decode_json(bytes)
82 }
83}
84
85impl ValueCodec for CborCodec {
86 fn encode<T: Serialize>(&self, value: &T) -> Result<Vec<u8>, Error> {
87 encode_cbor(value)
88 }
89
90 fn decode<T: DeserializeOwned>(&self, bytes: &[u8]) -> Result<T, Error> {
91 decode_cbor(bytes)
92 }
93}
94
95impl VersionedJsonCodec {
96 pub fn new(schema: impl Into<String>, version: u64) -> Self {
98 Self {
99 schema: schema.into(),
100 version,
101 }
102 }
103
104 pub fn schema(&self) -> &str {
106 &self.schema
107 }
108
109 pub fn version(&self) -> u64 {
111 self.version
112 }
113}
114
115impl ValueCodec for VersionedJsonCodec {
116 fn encode<T: Serialize>(&self, value: &T) -> Result<Vec<u8>, Error> {
117 VersionedValue::json(&self.schema, self.version, value)?.to_bytes()
118 }
119
120 fn decode<T: DeserializeOwned>(&self, bytes: &[u8]) -> Result<T, Error> {
121 let envelope = VersionedValue::from_bytes(bytes)?;
122 envelope.require_schema(&self.schema, self.version)?;
123 envelope.decode_json()
124 }
125}
126
127impl VersionedCborCodec {
128 pub fn new(schema: impl Into<String>, version: u64) -> Self {
130 Self {
131 schema: schema.into(),
132 version,
133 }
134 }
135
136 pub fn schema(&self) -> &str {
138 &self.schema
139 }
140
141 pub fn version(&self) -> u64 {
143 self.version
144 }
145}
146
147impl ValueCodec for VersionedCborCodec {
148 fn encode<T: Serialize>(&self, value: &T) -> Result<Vec<u8>, Error> {
149 VersionedValue::cbor(&self.schema, self.version, value)?.to_bytes()
150 }
151
152 fn decode<T: DeserializeOwned>(&self, bytes: &[u8]) -> Result<T, Error> {
153 let envelope = VersionedValue::from_bytes(bytes)?;
154 envelope.require_schema(&self.schema, self.version)?;
155 envelope.decode_cbor()
156 }
157}
158
159#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
166pub struct VersionedValue {
167 pub schema: String,
169 pub version: u64,
171 pub encoding: Encoding,
173 pub payload: Vec<u8>,
175}
176
177impl VersionedValue {
178 pub fn raw(schema: impl Into<String>, version: u64, payload: impl Into<Vec<u8>>) -> Self {
180 Self {
181 schema: schema.into(),
182 version,
183 encoding: Encoding::Raw,
184 payload: payload.into(),
185 }
186 }
187
188 pub fn json<T: Serialize>(
190 schema: impl Into<String>,
191 version: u64,
192 value: &T,
193 ) -> Result<Self, Error> {
194 Ok(Self {
195 schema: schema.into(),
196 version,
197 encoding: Encoding::Json,
198 payload: encode_json(value)?,
199 })
200 }
201
202 pub fn cbor<T: Serialize>(
204 schema: impl Into<String>,
205 version: u64,
206 value: &T,
207 ) -> Result<Self, Error> {
208 Ok(Self {
209 schema: schema.into(),
210 version,
211 encoding: Encoding::Cbor,
212 payload: encode_cbor(value)?,
213 })
214 }
215
216 pub fn with_encoding(
218 schema: impl Into<String>,
219 version: u64,
220 encoding: Encoding,
221 payload: impl Into<Vec<u8>>,
222 ) -> Self {
223 Self {
224 schema: schema.into(),
225 version,
226 encoding,
227 payload: payload.into(),
228 }
229 }
230
231 pub fn to_bytes(&self) -> Result<Vec<u8>, Error> {
233 let schema = self.schema.as_bytes();
234 let custom = custom_encoding_name(&self.encoding);
235 let schema_len = checked_u32_len(schema.len(), "schema")?;
236 let custom_len = checked_u32_len(custom.len(), "custom encoding")?;
237
238 let mut out =
239 Vec::with_capacity(HEADER_LEN + schema.len() + custom.len() + self.payload.len());
240 out.extend_from_slice(VERSIONED_VALUE_MAGIC);
241 out.push(VERSIONED_VALUE_WIRE_VERSION);
242 out.push(encoding_tag(&self.encoding));
243 out.extend_from_slice(&self.version.to_be_bytes());
244 out.extend_from_slice(&schema_len.to_be_bytes());
245 out.extend_from_slice(&custom_len.to_be_bytes());
246 out.extend_from_slice(&(self.payload.len() as u64).to_be_bytes());
247 out.extend_from_slice(schema);
248 out.extend_from_slice(custom);
249 out.extend_from_slice(&self.payload);
250 Ok(out)
251 }
252
253 pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
255 decode_versioned_value(bytes)
256 }
257
258 pub fn decode_json<T: DeserializeOwned>(&self) -> Result<T, Error> {
260 if self.encoding != Encoding::Json {
261 return Err(invalid_value(format!(
262 "expected JSON payload, got {:?}",
263 self.encoding
264 )));
265 }
266 decode_json(&self.payload)
267 }
268
269 pub fn decode_cbor<T: DeserializeOwned>(&self) -> Result<T, Error> {
271 if self.encoding != Encoding::Cbor {
272 return Err(invalid_value(format!(
273 "expected CBOR payload, got {:?}",
274 self.encoding
275 )));
276 }
277 decode_cbor(&self.payload)
278 }
279
280 pub fn require_schema(&self, schema: &str, version: u64) -> Result<(), Error> {
282 if self.schema == schema && self.version == version {
283 return Ok(());
284 }
285
286 Err(invalid_value(format!(
287 "schema/version mismatch: expected {schema}@{version}, got {}@{}",
288 self.schema, self.version
289 )))
290 }
291
292 pub fn matches_schema(&self, schema: &str, version: u64) -> bool {
294 self.schema == schema && self.version == version
295 }
296}
297
298fn decode_versioned_value(bytes: &[u8]) -> Result<VersionedValue, Error> {
299 if bytes.len() < HEADER_LEN {
300 return Err(invalid_value("versioned value envelope is too short"));
301 }
302 if !bytes.starts_with(VERSIONED_VALUE_MAGIC) {
303 return Err(invalid_value("versioned value missing PLVV magic"));
304 }
305
306 let wire_version = bytes[4];
307 if wire_version != VERSIONED_VALUE_WIRE_VERSION {
308 return Err(invalid_value(format!(
309 "unsupported versioned value wire version {wire_version}"
310 )));
311 }
312
313 let encoding = bytes[5];
314 let version = read_u64(bytes, 6)?;
315 let schema_len = read_u32(bytes, 14)? as usize;
316 let custom_len = read_u32(bytes, 18)? as usize;
317 let payload_len = usize::try_from(read_u64(bytes, 22)?)
318 .map_err(|_| invalid_value("payload length does not fit in usize"))?;
319
320 let schema_start = HEADER_LEN;
321 let custom_start = checked_add(schema_start, schema_len, "schema")?;
322 let payload_start = checked_add(custom_start, custom_len, "custom encoding")?;
323 let expected_len = checked_add(payload_start, payload_len, "payload")?;
324 if expected_len != bytes.len() {
325 return Err(invalid_value(format!(
326 "versioned value length mismatch: header expects {expected_len} bytes, got {}",
327 bytes.len()
328 )));
329 }
330
331 let schema = decode_utf8(&bytes[schema_start..custom_start], "schema")?;
332 let custom = decode_utf8(&bytes[custom_start..payload_start], "custom encoding")?;
333 let encoding = decode_encoding(encoding, custom)?;
334 let payload = bytes[payload_start..expected_len].to_vec();
335
336 Ok(VersionedValue {
337 schema,
338 version,
339 encoding,
340 payload,
341 })
342}
343
344fn checked_u32_len(len: usize, field: &str) -> Result<u32, Error> {
345 u32::try_from(len).map_err(|_| invalid_value(format!("{field} is too large")))
346}
347
348fn checked_add(base: usize, len: usize, field: &str) -> Result<usize, Error> {
349 base.checked_add(len)
350 .ok_or_else(|| invalid_value(format!("{field} length overflows usize")))
351}
352
353fn read_u32(bytes: &[u8], offset: usize) -> Result<u32, Error> {
354 let value = bytes
355 .get(offset..offset + 4)
356 .ok_or_else(|| invalid_value("versioned value header is truncated"))?;
357 Ok(u32::from_be_bytes(
358 value.try_into().expect("fixed slice length"),
359 ))
360}
361
362fn read_u64(bytes: &[u8], offset: usize) -> Result<u64, Error> {
363 let value = bytes
364 .get(offset..offset + 8)
365 .ok_or_else(|| invalid_value("versioned value header is truncated"))?;
366 Ok(u64::from_be_bytes(
367 value.try_into().expect("fixed slice length"),
368 ))
369}
370
371fn decode_utf8(bytes: &[u8], field: &str) -> Result<String, Error> {
372 std::str::from_utf8(bytes)
373 .map(str::to_owned)
374 .map_err(|err| invalid_value(format!("{field} is not valid UTF-8: {err}")))
375}
376
377fn custom_encoding_name(encoding: &Encoding) -> &[u8] {
378 match encoding {
379 Encoding::Custom(name) => name.as_bytes(),
380 _ => &[],
381 }
382}
383
384fn encoding_tag(encoding: &Encoding) -> u8 {
385 match encoding {
386 Encoding::Raw => ENCODING_RAW,
387 Encoding::Cbor => ENCODING_CBOR,
388 Encoding::Json => ENCODING_JSON,
389 Encoding::Custom(_) => ENCODING_CUSTOM,
390 }
391}
392
393fn decode_encoding(tag: u8, custom: String) -> Result<Encoding, Error> {
394 match tag {
395 ENCODING_RAW => require_no_custom(custom, Encoding::Raw),
396 ENCODING_CBOR => require_no_custom(custom, Encoding::Cbor),
397 ENCODING_JSON => require_no_custom(custom, Encoding::Json),
398 ENCODING_CUSTOM => Ok(Encoding::Custom(custom)),
399 tag => Err(invalid_value(format!("unknown value encoding tag {tag}"))),
400 }
401}
402
403fn require_no_custom(custom: String, encoding: Encoding) -> Result<Encoding, Error> {
404 if custom.is_empty() {
405 Ok(encoding)
406 } else {
407 Err(invalid_value(format!(
408 "non-custom encoding {:?} included custom encoding name",
409 encoding
410 )))
411 }
412}
413
414fn invalid_value(message: impl Into<String>) -> Error {
415 Error::Deserialize(format!("invalid versioned value: {}", message.into()))
416}
417
418#[cfg(test)]
419mod tests {
420 use super::*;
421
422 #[derive(Debug, PartialEq, Serialize, Deserialize)]
423 struct Example {
424 name: String,
425 score: u64,
426 }
427
428 #[test]
429 fn json_and_cbor_helpers_round_trip_typed_values() {
430 let value = Example {
431 name: "memory".to_string(),
432 score: 42,
433 };
434
435 let json = encode_json(&value).unwrap();
436 assert_eq!(decode_json::<Example>(&json).unwrap(), value);
437
438 let cbor = encode_cbor(&value).unwrap();
439 assert_eq!(decode_cbor::<Example>(&cbor).unwrap(), value);
440 }
441
442 #[test]
443 fn versioned_value_round_trips_schema_encoding_and_payload() {
444 let value = Example {
445 name: "chunk".to_string(),
446 score: 9,
447 };
448 let envelope = VersionedValue::json("memory.chunk", 2, &value).unwrap();
449
450 let bytes = envelope.to_bytes().unwrap();
451 let decoded = VersionedValue::from_bytes(&bytes).unwrap();
452
453 assert_eq!(decoded.schema, "memory.chunk");
454 assert_eq!(decoded.version, 2);
455 assert_eq!(decoded.encoding, Encoding::Json);
456 decoded.require_schema("memory.chunk", 2).unwrap();
457 assert_eq!(decoded.decode_json::<Example>().unwrap(), value);
458 }
459
460 #[test]
461 fn versioned_value_rejects_bad_magic_and_schema_mismatch() {
462 assert!(matches!(
463 VersionedValue::from_bytes(b"raw"),
464 Err(Error::Deserialize(_))
465 ));
466
467 let value = VersionedValue::raw("memory", 1, b"bytes");
468 let err = value.require_schema("memory", 2).unwrap_err();
469 assert!(matches!(err, Error::Deserialize(_)));
470 }
471}