Skip to main content

msr/
codec.rs

1//! Encoding and decoding of MSR records.
2//!
3//! Two equivalent forms carry the same JSON:
4//! - **Compact** (`MS1:` + URL-safe Base64 of DEFLATE-compressed JSON) — short,
5//!   for storage and transport.
6//! - **JSON** — the human-readable, diff-friendly interchange form.
7//!
8//! [`decode`] reads either, so a tool need not know which it was given.
9
10use crate::model::Record;
11use crate::Error;
12use base64::Engine as _;
13
14/// Tag identifying the compact MSR form (version 1 of the envelope).
15pub const PREFIX: &str = "MS1:";
16
17/// Encode a record to the compact `MS1:` form.
18pub fn encode(record: &Record) -> Result<String, Error> {
19    let json = serde_json::to_vec(record)?;
20    let compressed = miniz_oxide::deflate::compress_to_vec(&json, 9);
21    let b64 = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(compressed);
22    Ok(format!("{PREFIX}{b64}"))
23}
24
25/// Encode a record to pretty-printed JSON (the readable interchange form).
26pub fn encode_json(record: &Record) -> Result<String, Error> {
27    Ok(serde_json::to_string_pretty(record)?)
28}
29
30/// Decode a record from either form: the `MS1:` compact envelope, or raw JSON
31/// (pretty or compact). Surrounding whitespace is ignored.
32pub fn decode(text: &str) -> Result<Record, Error> {
33    let trimmed = text.trim();
34    if let Some(b64) = trimmed.strip_prefix(PREFIX) {
35        let compressed = base64::engine::general_purpose::URL_SAFE_NO_PAD
36            .decode(b64.trim())
37            .map_err(|e| Error::Base64(e.to_string()))?;
38        let json = miniz_oxide::inflate::decompress_to_vec(&compressed)
39            .map_err(|e| Error::Inflate(format!("{e:?}")))?;
40        return Ok(serde_json::from_slice(&json)?);
41    }
42    Ok(serde_json::from_str(trimmed)?)
43}