moq_json/snapshot/
decoder.rs1use std::cell::RefCell;
4use std::marker::PhantomData;
5
6use serde::de::DeserializeOwned;
7use serde_json::Value;
8
9use super::consumer::Config;
10use crate::{Error, Result};
11
12pub struct Decoder<T> {
32 compression: bool,
34
35 flate: Option<moq_flate::Decoder>,
37
38 plain: Vec<u8>,
40
41 check: RefCell<crate::merge::CheckScratch>,
43
44 current: Option<Value>,
46
47 _marker: PhantomData<fn() -> T>,
48}
49
50impl<T> Decoder<T> {
51 pub fn new(config: Config) -> Self {
53 Self {
54 compression: config.compression.is_deflate(),
55 flate: None,
56 plain: Vec::new(),
57 check: RefCell::new(crate::merge::CheckScratch::default()),
58 current: None,
59 _marker: PhantomData,
60 }
61 }
62
63 pub fn snapshot(&mut self, payload: &[u8]) -> Result<()> {
68 self.flate = self.compression.then(moq_flate::Decoder::new);
70 self.current = Some(match self.flate.as_mut() {
71 Some(flate) => serde_json::from_slice(&flate.frame(payload)?)?,
72 None => serde_json::from_slice(payload)?,
73 });
74 Ok(())
75 }
76
77 pub fn delta(&mut self, payload: &[u8]) -> Result<()> {
83 if self.current.is_none() {
84 return Err(Error::MissingSnapshot);
85 }
86
87 let plain = match self.flate.as_mut() {
88 Some(flate) => {
89 flate.frame_into(payload, &mut self.plain)?;
90 self.plain.as_slice()
91 }
92 None => payload,
93 };
94 crate::merge::apply_bytes(
95 self.current.as_mut().expect("a snapshot precedes any delta"),
96 plain,
97 &self.check,
98 )?;
99 Ok(())
100 }
101
102 pub fn value(&self) -> Option<&Value> {
104 self.current.as_ref()
105 }
106}
107
108impl<T: DeserializeOwned> Decoder<T> {
109 pub fn decode(&self) -> Result<Option<T>> {
116 let Some(current) = self.current.as_ref() else {
117 return Ok(None);
118 };
119
120 let value = serde_path_to_error::deserialize(current).map_err(|err| {
121 let path = err.path().to_string();
122 match path.as_str() {
123 "." => Error::Json(err.into_inner().to_string()),
125 _ => Error::Json(format!("{}: {}", path, err.into_inner())),
126 }
127 })?;
128
129 Ok(Some(value))
130 }
131}
132
133#[cfg(test)]
134mod test {
135 use super::super::consumer::Config as ConsumerConfig;
136 use super::super::{Config, Encoder};
137 use super::*;
138 use crate::Compression;
139 use serde_json::{Value, json};
140
141 fn consume(compression: Compression) -> ConsumerConfig {
142 ConsumerConfig { compression }
143 }
144
145 fn deflate() -> Config {
146 Config {
147 compression: Compression::Deflate,
148 ..Default::default()
149 }
150 }
151
152 fn roundtrip(config: Config, values: &[Value]) -> Vec<Value> {
155 let compression = config.compression;
156 let mut encoder = Encoder::<Value>::new(config);
157 let mut decoder = Decoder::<Value>::new(consume(compression));
158
159 let mut out = Vec::new();
160 for value in values {
161 let Some(frame) = encoder.update(value).unwrap() else {
162 continue;
163 };
164 match frame.keyframe {
165 true => decoder.snapshot(&frame.payload).unwrap(),
166 false => decoder.delta(&frame.payload).unwrap(),
167 }
168 frame.commit();
169 out.push(decoder.decode().unwrap().unwrap());
170 }
171 out
172 }
173
174 #[test]
175 fn plaintext_roundtrip() {
176 let values = vec![
177 json!({ "a": 1, "b": 1 }),
178 json!({ "a": 1, "b": 2 }),
179 json!({ "a": 5, "b": 2 }),
180 ];
181 assert_eq!(roundtrip(Config::default(), &values), values);
182 }
183
184 #[test]
185 fn compressed_roundtrip() {
186 let values = vec![
187 json!({ "a": 1, "b": 1 }),
188 json!({ "a": 1, "b": 2 }),
189 json!({ "a": 5, "b": 2 }),
190 ];
191 assert_eq!(roundtrip(deflate(), &values), values);
192 }
193
194 #[test]
197 fn compressed_roundtrip_across_a_group_boundary() {
198 let values: Vec<Value> = (0..=40).map(|n| json!({ "n": n })).collect();
200 let config = deflate().with_delta_ratio(2);
201 assert_eq!(roundtrip(config, &values).last().unwrap(), &json!({ "n": 40 }));
202 }
203
204 #[test]
205 fn no_value_before_the_first_snapshot() {
206 let decoder = Decoder::<Value>::new(ConsumerConfig::default());
207 assert_eq!(decoder.value(), None);
208 assert_eq!(decoder.decode().unwrap(), None);
209 }
210
211 #[test]
212 fn a_delta_before_a_snapshot_is_an_error() {
213 let mut decoder = Decoder::<Value>::new(ConsumerConfig::default());
214 assert!(matches!(decoder.delta(br#"{"a":1}"#), Err(Error::MissingSnapshot)));
215 }
216
217 #[test]
220 fn frames_apply_without_materializing() {
221 let mut encoder = Encoder::<Value>::new(Config::default().with_delta_ratio(100));
222 let mut decoder = Decoder::<Value>::new(ConsumerConfig::default());
223
224 for n in 0..=20 {
225 let frame = encoder.update(&json!({ "n": n })).unwrap().unwrap();
226 match frame.keyframe {
227 true => decoder.snapshot(&frame.payload).unwrap(),
228 false => decoder.delta(&frame.payload).unwrap(),
229 }
230 frame.commit();
231 }
232
233 assert_eq!(decoder.decode().unwrap(), Some(json!({ "n": 20 })));
234 }
235
236 #[test]
237 fn a_rejected_field_names_its_path() {
238 #[derive(serde::Deserialize, Debug)]
239 #[allow(dead_code)]
240 struct Inner {
241 count: u8,
242 }
243 #[derive(serde::Deserialize, Debug)]
244 #[allow(dead_code)]
245 struct Outer {
246 inner: Inner,
247 }
248
249 let mut decoder = Decoder::<Outer>::new(ConsumerConfig::default());
250 decoder.snapshot(br#"{"inner":{"count":300}}"#).unwrap();
251
252 let err = decoder.decode().unwrap_err();
253 assert!(err.to_string().starts_with("json: inner.count: "), "{err}");
254 }
255}