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 if let Ok(value) = T::deserialize(current) {
123 return Ok(Some(value));
124 }
125
126 let value = serde_path_to_error::deserialize(current).map_err(|err| {
127 let path = err.path().to_string();
128 match path.as_str() {
129 "." => Error::Json(err.into_inner().to_string()),
131 _ => Error::Json(format!("{}: {}", path, err.into_inner())),
132 }
133 })?;
134
135 Ok(Some(value))
136 }
137}
138
139#[cfg(test)]
140mod test {
141 use super::super::consumer::Config as ConsumerConfig;
142 use super::super::{Config, Encoder};
143 use super::*;
144 use crate::Compression;
145 use serde_json::{Value, json};
146
147 fn consume(compression: Compression) -> ConsumerConfig {
148 ConsumerConfig { compression }
149 }
150
151 fn deflate() -> Config {
152 Config {
153 compression: Compression::Deflate,
154 ..Default::default()
155 }
156 }
157
158 fn roundtrip(config: Config, values: &[Value]) -> Vec<Value> {
161 let compression = config.compression;
162 let mut encoder = Encoder::<Value>::new(config);
163 let mut decoder = Decoder::<Value>::new(consume(compression));
164
165 let mut out = Vec::new();
166 for value in values {
167 let Some(frame) = encoder.update(value).unwrap() else {
168 continue;
169 };
170 match frame.keyframe {
171 true => decoder.snapshot(&frame.payload).unwrap(),
172 false => decoder.delta(&frame.payload).unwrap(),
173 }
174 frame.commit();
175 out.push(decoder.decode().unwrap().unwrap());
176 }
177 out
178 }
179
180 #[test]
181 fn plaintext_roundtrip() {
182 let values = vec![
183 json!({ "a": 1, "b": 1 }),
184 json!({ "a": 1, "b": 2 }),
185 json!({ "a": 5, "b": 2 }),
186 ];
187 assert_eq!(roundtrip(Config::default(), &values), values);
188 }
189
190 #[test]
191 fn compressed_roundtrip() {
192 let values = vec![
193 json!({ "a": 1, "b": 1 }),
194 json!({ "a": 1, "b": 2 }),
195 json!({ "a": 5, "b": 2 }),
196 ];
197 assert_eq!(roundtrip(deflate(), &values), values);
198 }
199
200 #[test]
203 fn compressed_roundtrip_across_a_group_boundary() {
204 let values: Vec<Value> = (0..=40).map(|n| json!({ "n": n })).collect();
206 let config = deflate().with_delta_ratio(2);
207 assert_eq!(roundtrip(config, &values).last().unwrap(), &json!({ "n": 40 }));
208 }
209
210 #[test]
211 fn no_value_before_the_first_snapshot() {
212 let decoder = Decoder::<Value>::new(ConsumerConfig::default());
213 assert_eq!(decoder.value(), None);
214 assert_eq!(decoder.decode().unwrap(), None);
215 }
216
217 #[test]
218 fn a_delta_before_a_snapshot_is_an_error() {
219 let mut decoder = Decoder::<Value>::new(ConsumerConfig::default());
220 assert!(matches!(decoder.delta(br#"{"a":1}"#), Err(Error::MissingSnapshot)));
221 }
222
223 #[test]
226 fn frames_apply_without_materializing() {
227 let mut encoder = Encoder::<Value>::new(Config::default().with_delta_ratio(100));
228 let mut decoder = Decoder::<Value>::new(ConsumerConfig::default());
229
230 for n in 0..=20 {
231 let frame = encoder.update(&json!({ "n": n })).unwrap().unwrap();
232 match frame.keyframe {
233 true => decoder.snapshot(&frame.payload).unwrap(),
234 false => decoder.delta(&frame.payload).unwrap(),
235 }
236 frame.commit();
237 }
238
239 assert_eq!(decoder.decode().unwrap(), Some(json!({ "n": 20 })));
240 }
241
242 #[test]
243 fn a_rejected_field_names_its_path() {
244 #[derive(serde::Deserialize, Debug)]
245 #[allow(dead_code)]
246 struct Inner {
247 count: u8,
248 }
249 #[derive(serde::Deserialize, Debug)]
250 #[allow(dead_code)]
251 struct Outer {
252 inner: Inner,
253 }
254
255 let mut decoder = Decoder::<Outer>::new(ConsumerConfig::default());
256 decoder.snapshot(br#"{"inner":{"count":300}}"#).unwrap();
257
258 let err = decoder.decode().unwrap_err();
259 assert!(err.to_string().starts_with("json: inner.count: "), "{err}");
260 }
261}