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 limit: usize,
37
38 size: usize,
44
45 flate: Option<moq_flate::Decoder>,
47
48 plain: Vec<u8>,
50
51 check: RefCell<crate::merge::CheckScratch>,
53
54 current: Option<Value>,
56
57 _marker: PhantomData<fn() -> T>,
58}
59
60impl<T> Decoder<T> {
61 pub fn new(config: Config) -> Self {
63 Self {
64 compression: config.compression.is_deflate(),
65 limit: config.max_size.unwrap_or(moq_flate::DEFAULT_MAX_FRAME_SIZE as usize),
68 size: 0,
69 flate: None,
70 plain: Vec::new(),
71 check: RefCell::new(crate::merge::CheckScratch::default()),
72 current: None,
73 _marker: PhantomData,
74 }
75 }
76
77 pub fn snapshot(&mut self, payload: &[u8]) -> Result<()> {
82 self.current = None;
83 self.flate = self.compression.then(|| {
85 moq_flate::Decoder::with_max_frame_size((self.limit as u64).min(moq_flate::DEFAULT_MAX_FRAME_SIZE))
86 });
87 let inflated = self.flate.as_mut().map(|flate| flate.frame(payload)).transpose()?;
88 let plain = inflated.as_deref().unwrap_or(payload);
89 if plain.len() > self.limit {
90 return Err(Error::TooLarge(self.limit));
91 }
92 self.current = Some(serde_json::from_slice(plain)?);
93 self.size = plain.len();
94 Ok(())
95 }
96
97 pub fn delta(&mut self, payload: &[u8]) -> Result<()> {
100 if self.current.is_none() {
101 return Err(Error::MissingSnapshot);
102 }
103 let result = (|| {
104 let plain = match self.flate.as_mut() {
105 Some(flate) => {
106 flate.frame_into(payload, &mut self.plain)?;
107 self.plain.as_slice()
108 }
109 None => payload,
110 };
111 if plain.len() > self.limit {
112 return Err(Error::TooLarge(self.limit));
113 }
114 let current = self.current.as_mut().expect("a snapshot precedes any delta");
115 let growth = crate::merge::apply_bytes(current, plain, &self.check)?;
116 self.size = (self.size as i64).saturating_add(growth).max(0) as usize;
117 if self.size > self.limit {
119 return Err(Error::TooLarge(self.limit));
120 }
121 Ok(())
122 })();
123 if result.is_err() {
124 self.current = None;
125 }
126 result
127 }
128
129 pub fn value(&self) -> Option<&Value> {
131 self.current.as_ref()
132 }
133}
134
135impl<T: DeserializeOwned> Decoder<T> {
136 pub fn decode(&self) -> Result<Option<T>> {
143 let Some(current) = self.current.as_ref() else {
144 return Ok(None);
145 };
146
147 if let Ok(value) = T::deserialize(current) {
150 return Ok(Some(value));
151 }
152
153 let value = serde_path_to_error::deserialize(current).map_err(|err| {
154 let path = err.path().to_string();
155 match path.as_str() {
156 "." => Error::Json(err.into_inner().to_string()),
158 _ => Error::Json(format!("{}: {}", path, err.into_inner())),
159 }
160 })?;
161
162 Ok(Some(value))
163 }
164}
165
166#[cfg(test)]
167mod test {
168 use super::super::consumer::Config as ConsumerConfig;
169 use super::super::{Config, Encoder};
170 use super::*;
171 use crate::Compression;
172 use serde_json::{Value, json};
173
174 fn consume(compression: Compression) -> ConsumerConfig {
175 ConsumerConfig {
176 compression,
177 ..Default::default()
178 }
179 }
180
181 fn deflate() -> Config {
182 Config {
183 compression: Compression::Deflate,
184 ..Default::default()
185 }
186 }
187
188 fn roundtrip(config: Config, values: &[Value]) -> Vec<Value> {
191 let compression = config.compression;
192 let mut encoder = Encoder::<Value>::new(config);
193 let mut decoder = Decoder::<Value>::new(consume(compression));
194
195 let mut out = Vec::new();
196 for value in values {
197 let Some(frame) = encoder.update(value).unwrap() else {
198 continue;
199 };
200 match frame.keyframe {
201 true => decoder.snapshot(&frame.payload).unwrap(),
202 false => decoder.delta(&frame.payload).unwrap(),
203 }
204 frame.commit();
205 out.push(decoder.decode().unwrap().unwrap());
206 }
207 out
208 }
209
210 #[test]
211 fn inflated_snapshots_and_patches_obey_the_budget() {
212 for snapshot in [true, false] {
213 let mut config = consume(Compression::Deflate);
214 config.max_size = Some(1024);
215 let mut decoder = Decoder::<Value>::new(config);
216 let mut encoder = moq_flate::Encoder::new();
217 if !snapshot {
218 decoder.snapshot(&encoder.frame(b"{}")).unwrap();
219 }
220 let payload = encoder.frame(
221 serde_json::to_string(&json!({"large": "x".repeat(4096)}))
222 .unwrap()
223 .as_bytes(),
224 );
225 assert!(payload.len() < 1024);
226 let result = if snapshot {
227 decoder.snapshot(&payload)
228 } else {
229 decoder.delta(&payload)
230 };
231 assert!(matches!(result, Err(Error::Flate(moq_flate::Error::TooLarge(1024)))));
232 assert!(decoder.value().is_none());
233 }
234 }
235
236 #[test]
237 fn patches_cannot_accumulate_past_the_budget() {
238 for compression in [Compression::None, Compression::Deflate] {
239 let mut config = consume(compression);
240 config.max_size = Some(13);
241 let mut decoder = Decoder::<Value>::new(config);
242 let mut encoder = moq_flate::Encoder::new();
243 let mut payload = |bytes: &[u8]| {
244 if compression == Compression::Deflate {
245 encoder.frame(bytes).to_vec()
246 } else {
247 bytes.to_vec()
248 }
249 };
250 decoder.snapshot(&payload(br#"{"a":1}"#)).unwrap();
251 decoder.delta(&payload(br#"{"b":2}"#)).unwrap(); assert_eq!(decoder.value(), Some(&json!({"a":1,"b":2})));
253 decoder.delta(&payload(br#"{"a":null}"#)).unwrap(); decoder.delta(&payload(br#"{"c":3}"#)).unwrap();
255 assert!(matches!(
256 decoder.delta(&payload(br#"{"d":4}"#)),
257 Err(Error::TooLarge(13))
258 ));
259 assert!(decoder.value().is_none());
260 assert!(matches!(decoder.delta(b"{}"), Err(Error::MissingSnapshot)));
261 let bytes = if compression == Compression::Deflate {
263 moq_flate::Encoder::new().frame(b"{}").to_vec()
264 } else {
265 b"{}".to_vec()
266 };
267 decoder.snapshot(&bytes).unwrap();
268 }
269 }
270
271 #[test]
272 fn unset_budget_still_bounds_compressed_state() {
273 let half = moq_flate::DEFAULT_MAX_FRAME_SIZE as usize / 2;
276 let mut decoder = Decoder::<Value>::new(consume(Compression::Deflate));
277 let mut encoder = moq_flate::Encoder::new();
278 let mut frame = |key: &str| encoder.frame(format!(r#"{{"{key}":"{}"}}"#, "x".repeat(half)).as_bytes());
279 decoder.snapshot(&frame("a")).unwrap();
280 assert!(matches!(
281 decoder.delta(&frame("b")),
282 Err(Error::TooLarge(limit)) if limit as u64 == moq_flate::DEFAULT_MAX_FRAME_SIZE
283 ));
284 assert!(decoder.value().is_none());
285 }
286
287 #[test]
288 fn plain_frames_obey_the_budget_before_parsing() {
289 let config = ConsumerConfig {
290 max_size: Some(2),
291 ..Default::default()
292 };
293 let mut decoder = Decoder::<Value>::new(config);
294 assert!(matches!(decoder.snapshot(b"not json"), Err(Error::TooLarge(2))));
295 decoder.snapshot(b"{}").unwrap();
296 assert!(matches!(decoder.delta(b"not json"), Err(Error::TooLarge(2))));
297 assert!(decoder.value().is_none());
298 }
299
300 #[test]
301 fn plaintext_roundtrip() {
302 let values = vec![
303 json!({ "a": 1, "b": 1 }),
304 json!({ "a": 1, "b": 2 }),
305 json!({ "a": 5, "b": 2 }),
306 ];
307 assert_eq!(roundtrip(Config::default(), &values), values);
308 }
309
310 #[test]
311 fn compressed_roundtrip() {
312 let values = vec![
313 json!({ "a": 1, "b": 1 }),
314 json!({ "a": 1, "b": 2 }),
315 json!({ "a": 5, "b": 2 }),
316 ];
317 assert_eq!(roundtrip(deflate(), &values), values);
318 }
319
320 #[test]
323 fn compressed_roundtrip_across_a_group_boundary() {
324 let values: Vec<Value> = (0..=40).map(|n| json!({ "n": n })).collect();
326 let config = deflate().with_delta_ratio(2);
327 assert_eq!(roundtrip(config, &values).last().unwrap(), &json!({ "n": 40 }));
328 }
329
330 #[test]
331 fn no_value_before_the_first_snapshot() {
332 let decoder = Decoder::<Value>::new(ConsumerConfig::default());
333 assert_eq!(decoder.value(), None);
334 assert_eq!(decoder.decode().unwrap(), None);
335 }
336
337 #[test]
338 fn a_delta_before_a_snapshot_is_an_error() {
339 let mut decoder = Decoder::<Value>::new(ConsumerConfig::default());
340 assert!(matches!(decoder.delta(br#"{"a":1}"#), Err(Error::MissingSnapshot)));
341 }
342
343 #[test]
346 fn frames_apply_without_materializing() {
347 let mut encoder = Encoder::<Value>::new(Config::default().with_delta_ratio(100));
348 let mut decoder = Decoder::<Value>::new(ConsumerConfig::default());
349
350 for n in 0..=20 {
351 let frame = encoder.update(&json!({ "n": n })).unwrap().unwrap();
352 match frame.keyframe {
353 true => decoder.snapshot(&frame.payload).unwrap(),
354 false => decoder.delta(&frame.payload).unwrap(),
355 }
356 frame.commit();
357 }
358
359 assert_eq!(decoder.decode().unwrap(), Some(json!({ "n": 20 })));
360 }
361
362 #[test]
363 fn a_rejected_field_names_its_path() {
364 #[derive(serde::Deserialize, Debug)]
365 #[allow(dead_code)]
366 struct Inner {
367 count: u8,
368 }
369 #[derive(serde::Deserialize, Debug)]
370 #[allow(dead_code)]
371 struct Outer {
372 inner: Inner,
373 }
374
375 let mut decoder = Decoder::<Outer>::new(ConsumerConfig::default());
376 decoder.snapshot(br#"{"inner":{"count":300}}"#).unwrap();
377
378 let err = decoder.decode().unwrap_err();
379 assert!(err.to_string().starts_with("json: inner.count: "), "{err}");
380 }
381}