Skip to main content

lumen/chunks/
json_chunks.rs

1//! The JSON chunks: `META`, `PROF` and `LROV`
2//! ([`spec/05-meta.md`], [`spec/06-prof.md`], [`spec/08-print-control.md`]).
3//!
4//! `META` and `PROF` are objects of their own; an `LROV` payload is one
5//! `(layer, sector)`'s timing delta, an object in the same field namespace META
6//! and a sector entry use.
7
8use crate::check::Check;
9use crate::error::{Error, Result};
10use crate::json::{Meta, Profile, Timing};
11use serde::de::DeserializeOwned;
12use serde::Serialize;
13
14/// Deserialize a JSON chunk payload.
15///
16/// Every JSON chunk reports the same check on a shape failure: the corpus has
17/// no per-chunk JSON variant, and a caller that cares about a specific group
18/// validates the parsed value afterwards.
19fn from_json<T: DeserializeOwned>(payload: &[u8]) -> Result<T> {
20    serde_json::from_slice(payload).map_err(|e| {
21        Error::new(
22            Check::MetaJson,
23            format!("the JSON payload is not a valid object: {e}"),
24        )
25    })
26}
27
28/// Serialize a JSON chunk payload.
29fn to_json<T: Serialize>(value: &T) -> Result<Vec<u8>> {
30    serde_json::to_vec(value).map_err(|e| {
31        Error::new(
32            Check::MetaJson,
33            format!("the JSON payload could not be serialized: {e}"),
34        )
35    })
36}
37
38/// Parse a `META` payload.
39pub fn parse_meta(payload: &[u8]) -> Result<Meta> {
40    from_json(payload)
41}
42
43/// Serialize a `META` payload.
44pub fn meta_to_bytes(meta: &Meta) -> Result<Vec<u8>> {
45    to_json(meta)
46}
47
48/// Parse a `PROF` payload.
49pub fn parse_profile(payload: &[u8]) -> Result<Profile> {
50    from_json(payload)
51}
52
53/// Serialize a `PROF` payload.
54pub fn profile_to_bytes(profile: &Profile) -> Result<Vec<u8>> {
55    to_json(profile)
56}
57
58/// Parse an `LROV` payload: one (layer, sector)'s timing delta.
59///
60/// Its own group reports the shape failure (`lrov.json`), because an `LROV`
61/// payload is not a `META` and a caller needs to know which chunk was wrong.
62pub fn parse_lrov(payload: &[u8]) -> Result<Timing> {
63    serde_json::from_slice(payload).map_err(|e| {
64        Error::new(
65            Check::LrovJson,
66            format!("the LROV payload is not a JSON object: {e}"),
67        )
68    })
69}
70
71/// Serialize an `LROV` payload.
72pub fn lrov_to_bytes(timing: &Timing) -> Result<Vec<u8>> {
73    serde_json::to_vec(timing).map_err(|e| {
74        Error::new(
75            Check::LrovJson,
76            format!("the LROV payload could not be serialized: {e}"),
77        )
78    })
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn meta_round_trips_and_preserves_unknown_keys() {
87        let payload = br#"{
88            "meta_version": 1,
89            "normal_exposure_ms": 2500,
90            "bottom_exposure_ms": 30000,
91            "layer_height_um": 50,
92            "vendor_extension": {"a": 1},
93            "sectors": [{"sector_id": 1, "name": "Support", "normal_exposure_ms": 3000}]
94        }"#;
95        let meta = parse_meta(payload).unwrap();
96        assert_eq!(meta.meta_version, Some(1));
97        assert_eq!(meta.timing.normal_exposure_ms, Some(2500));
98        // An unknown key is preserved rather than rejected or dropped.
99        assert_eq!(
100            meta.timing.extra.get("vendor_extension"),
101            Some(&serde_json::json!({"a": 1}))
102        );
103
104        let sectors = meta.sectors.as_deref().expect("a sectors array");
105        assert_eq!(sectors[0].sector_id, 1);
106        assert_eq!(sectors[0].name.as_deref(), Some("Support"));
107        assert_eq!(sectors[0].timing.normal_exposure_ms, Some(3000));
108
109        let bytes = meta_to_bytes(&meta).unwrap();
110        assert_eq!(parse_meta(&bytes).unwrap(), meta);
111    }
112
113    #[test]
114    fn every_json_chunk_reports_its_group_on_bad_input() {
115        assert_eq!(
116            parse_meta(b"not json").unwrap_err().check(),
117            Check::MetaJson
118        );
119        assert_eq!(parse_profile(b"[").unwrap_err().check(), Check::MetaJson);
120        assert_eq!(parse_lrov(b"{}x").unwrap_err().check(), Check::LrovJson);
121    }
122
123    #[test]
124    fn an_lrov_payload_is_a_flat_timing_object() {
125        let timing = parse_lrov(br#"{"normal_exposure_ms": 2000, "light_pwm": 120}"#).unwrap();
126        assert_eq!(timing.normal_exposure_ms, Some(2000));
127        assert_eq!(timing.light_pwm, Some(120));
128        assert_eq!(timing.bottom_layer_count, None);
129        assert_eq!(
130            parse_lrov(&lrov_to_bytes(&timing).unwrap()).unwrap(),
131            timing
132        );
133    }
134}