moq_json/snapshot/
decoder.rs1use std::marker::PhantomData;
4
5use serde::de::DeserializeOwned;
6use serde_json::Value;
7
8use crate::{Error, Result};
9
10#[derive(Debug, Clone, Default)]
15#[non_exhaustive]
16pub struct ConsumerConfig {
17 pub compression: bool,
20}
21
22impl ConsumerConfig {
23 pub fn with_compression(mut self, compression: bool) -> Self {
25 self.compression = compression;
26 self
27 }
28}
29
30pub struct Decoder<T> {
50 compression: bool,
52
53 flate: Option<moq_flate::Decoder>,
55
56 current: Option<Value>,
58
59 _marker: PhantomData<fn() -> T>,
60}
61
62impl<T> Decoder<T> {
63 pub fn new(config: ConsumerConfig) -> Self {
65 Self {
66 compression: config.compression,
67 flate: None,
68 current: None,
69 _marker: PhantomData,
70 }
71 }
72
73 pub fn snapshot(&mut self, payload: &[u8]) -> Result<()> {
78 self.flate = self.compression.then(moq_flate::Decoder::new);
80 self.current = Some(match self.flate.as_mut() {
81 Some(flate) => serde_json::from_slice(&flate.frame(payload)?)?,
82 None => serde_json::from_slice(payload)?,
83 });
84 Ok(())
85 }
86
87 pub fn delta(&mut self, payload: &[u8]) -> Result<()> {
93 if self.current.is_none() {
94 return Err(Error::MissingSnapshot);
95 }
96
97 let patch: Value = match self.flate.as_mut() {
98 Some(flate) => serde_json::from_slice(&flate.frame(payload)?)?,
99 None => serde_json::from_slice(payload)?,
100 };
101
102 json_patch::merge(self.current.as_mut().expect("a snapshot precedes any delta"), &patch);
103 Ok(())
104 }
105
106 pub fn value(&self) -> Option<&Value> {
108 self.current.as_ref()
109 }
110}
111
112impl<T: DeserializeOwned> Decoder<T> {
113 pub fn decode(&self) -> Result<Option<T>> {
120 let Some(current) = self.current.as_ref() else {
121 return Ok(None);
122 };
123
124 let value = serde_path_to_error::deserialize(current).map_err(|err| {
125 let path = err.path().to_string();
126 match path.as_str() {
127 "." => Error::Json(err.into_inner().to_string()),
129 _ => Error::Json(format!("{}: {}", path, err.into_inner())),
130 }
131 })?;
132
133 Ok(Some(value))
134 }
135}
136
137#[cfg(test)]
138mod test {
139 use super::super::{Encoder, ProducerConfig};
140 use super::*;
141 use serde_json::json;
142
143 fn roundtrip(config: ProducerConfig, values: &[Value]) -> Vec<Value> {
146 let compression = config.compression;
147 let mut encoder = Encoder::<Value>::new(config);
148 let mut decoder = Decoder::<Value>::new(ConsumerConfig::default().with_compression(compression));
149
150 let mut out = Vec::new();
151 for value in values {
152 let Some(frame) = encoder.update(value).unwrap() else {
153 continue;
154 };
155 match frame.keyframe {
156 true => decoder.snapshot(&frame.payload).unwrap(),
157 false => decoder.delta(&frame.payload).unwrap(),
158 }
159 frame.commit();
160 out.push(decoder.decode().unwrap().unwrap());
161 }
162 out
163 }
164
165 #[test]
166 fn plaintext_roundtrip() {
167 let values = vec![
168 json!({ "a": 1, "b": 1 }),
169 json!({ "a": 1, "b": 2 }),
170 json!({ "a": 5, "b": 2 }),
171 ];
172 assert_eq!(roundtrip(ProducerConfig::default(), &values), values);
173 }
174
175 #[test]
176 fn compressed_roundtrip() {
177 let values = vec![
178 json!({ "a": 1, "b": 1 }),
179 json!({ "a": 1, "b": 2 }),
180 json!({ "a": 5, "b": 2 }),
181 ];
182 let config = ProducerConfig::default().with_compression(true);
183 assert_eq!(roundtrip(config, &values), values);
184 }
185
186 #[test]
189 fn compressed_roundtrip_across_a_group_boundary() {
190 let values: Vec<Value> = (0..=40).map(|n| json!({ "n": n })).collect();
192 let config = ProducerConfig::default().with_delta_ratio(2).with_compression(true);
193 assert_eq!(roundtrip(config, &values).last().unwrap(), &json!({ "n": 40 }));
194 }
195
196 #[test]
197 fn no_value_before_the_first_snapshot() {
198 let decoder = Decoder::<Value>::new(ConsumerConfig::default());
199 assert_eq!(decoder.value(), None);
200 assert_eq!(decoder.decode().unwrap(), None);
201 }
202
203 #[test]
204 fn a_delta_before_a_snapshot_is_an_error() {
205 let mut decoder = Decoder::<Value>::new(ConsumerConfig::default());
206 assert!(matches!(decoder.delta(br#"{"a":1}"#), Err(Error::MissingSnapshot)));
207 }
208
209 #[test]
212 fn frames_apply_without_materializing() {
213 let mut encoder = Encoder::<Value>::new(ProducerConfig::default().with_delta_ratio(100));
214 let mut decoder = Decoder::<Value>::new(ConsumerConfig::default());
215
216 for n in 0..=20 {
217 let frame = encoder.update(&json!({ "n": n })).unwrap().unwrap();
218 match frame.keyframe {
219 true => decoder.snapshot(&frame.payload).unwrap(),
220 false => decoder.delta(&frame.payload).unwrap(),
221 }
222 frame.commit();
223 }
224
225 assert_eq!(decoder.decode().unwrap(), Some(json!({ "n": 20 })));
226 }
227
228 #[test]
229 fn a_rejected_field_names_its_path() {
230 #[derive(serde::Deserialize, Debug)]
231 #[allow(dead_code)]
232 struct Inner {
233 count: u8,
234 }
235 #[derive(serde::Deserialize, Debug)]
236 #[allow(dead_code)]
237 struct Outer {
238 inner: Inner,
239 }
240
241 let mut decoder = Decoder::<Outer>::new(ConsumerConfig::default());
242 decoder.snapshot(br#"{"inner":{"count":300}}"#).unwrap();
243
244 let err = decoder.decode().unwrap_err();
245 assert!(err.to_string().starts_with("json: inner.count: "), "{err}");
246 }
247}