1use super::{IncomingBso, IncomingContent, IncomingKind, OutgoingBso, OutgoingEnvelope};
13use crate::error::{trace, warn};
14use crate::Guid;
15use error_support::report_error;
16use serde::Serialize;
17
18type Result<T> = std::result::Result<T, serde_json::Error>;
20
21impl<T> IncomingContent<T> {
22 pub fn content(self) -> Option<T> {
24 match self.kind {
25 IncomingKind::Content(t) => Some(t),
26 _ => None,
27 }
28 }
29}
30
31impl<T: std::fmt::Debug> std::fmt::Debug for IncomingKind<T> {
33 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34 match self {
35 IncomingKind::Content(r) => {
36 write!(f, "IncomingKind::Content<{:?}>", r)
37 }
38 IncomingKind::Tombstone => write!(f, "IncomingKind::Tombstone"),
39 IncomingKind::Malformed => write!(f, "IncomingKind::Malformed"),
40 }
41 }
42}
43
44impl IncomingBso {
45 pub fn into_content<T: for<'de> serde::Deserialize<'de>>(self) -> IncomingContent<T> {
47 self.into_content_with_fixup(|_| {})
48 }
49
50 pub fn into_content_with_fixup<T: for<'de> serde::Deserialize<'de>>(
53 self,
54 fixup: impl FnOnce(&mut serde_json::Value),
55 ) -> IncomingContent<T> {
56 match serde_json::from_str(&self.payload) {
57 Ok(mut json) => {
58 fixup(&mut json);
60 let kind = json_to_kind(json, &self.envelope.id);
62 IncomingContent {
63 envelope: self.envelope,
64 kind,
65 }
66 }
67 Err(e) => {
68 warn!("Invalid incoming cleartext {}: {}", self.envelope.id, e);
70 IncomingContent {
71 envelope: self.envelope,
72 kind: IncomingKind::Malformed,
73 }
74 }
75 }
76 }
77}
78
79impl OutgoingBso {
80 pub fn new_tombstone(envelope: OutgoingEnvelope) -> Self {
83 Self {
84 envelope,
85 payload: serde_json::json!({"deleted": true}).to_string(),
86 }
87 }
88
89 pub fn from_content_with_id<T>(record: T) -> Result<Self>
94 where
95 T: Serialize,
96 {
97 let (json, id) = content_with_id_to_json(record)?;
98 Ok(Self {
99 envelope: id.into(),
100 payload: serde_json::to_string(&json)?,
101 })
102 }
103
104 pub fn from_content<T>(envelope: OutgoingEnvelope, record: T) -> Result<Self>
107 where
108 T: Serialize,
109 {
110 let json = content_to_json(record, &envelope.id)?;
111 Ok(Self {
112 envelope,
113 payload: serde_json::to_string(&json)?,
114 })
115 }
116}
117
118fn json_to_kind<T>(mut json: serde_json::Value, id: &Guid) -> IncomingKind<T>
131where
132 T: for<'de> serde::Deserialize<'de>,
133{
134 if let serde_json::Value::Object(ref mut map) = json {
137 if map.contains_key("deleted") {
138 return IncomingKind::Tombstone;
139 }
140 match map.get("id") {
141 Some(serde_json::Value::String(content_id)) => {
142 if content_id != id {
144 trace!(
145 "malformed incoming record: envelope id: {} payload id: {}",
146 content_id,
147 id
148 );
149 report_error!(
150 "incoming-invalid-mismatched-ids",
151 "Envelope and payload don't agree on the ID"
152 );
153 return IncomingKind::Malformed;
154 }
155 if !id.is_valid_for_sync_server() {
156 trace!("malformed incoming record: id is not valid: {}", id);
157 report_error!(
158 "incoming-invalid-bad-payload-id",
159 "ID in the payload is invalid"
160 );
161 return IncomingKind::Malformed;
162 }
163 }
164 Some(v) => {
165 trace!("malformed incoming record: id is not a string: {}", v);
168 report_error!("incoming-invalid-wrong_type", "ID is not a string");
169 return IncomingKind::Malformed;
170 }
171 None => {
172 if !id.is_valid_for_sync_server() {
174 trace!("malformed incoming record: id is not valid: {}", id);
175 report_error!(
176 "incoming-invalid-bad-envelope-id",
177 "ID in envelope is not valid"
178 );
179 return IncomingKind::Malformed;
180 }
181 map.insert("id".to_string(), id.to_string().into());
182 }
183 }
184 };
185 match serde_path_to_error::deserialize(json) {
186 Ok(v) => IncomingKind::Content(v),
187 Err(e) => {
188 report_error!(
189 "invalid-incoming-content",
190 "{}.{}: {}",
191 std::any::type_name::<T>(),
192 e.path(),
193 e.inner()
194 );
195 IncomingKind::Malformed
196 }
197 }
198}
199
200fn content_with_id_to_json<T>(record: T) -> Result<(serde_json::Value, Guid)>
203where
204 T: Serialize,
205{
206 let mut json = serde_json::to_value(record)?;
207 let id = match json.as_object_mut() {
208 Some(ref mut map) => {
209 match map.get("id").as_ref().and_then(|v| v.as_str()) {
210 Some(id) => {
211 let id: Guid = id.into();
212 assert!(id.is_valid_for_sync_server(), "record's ID is invalid");
213 id
214 }
215 None => panic!("record does not have an ID in the payload"),
217 }
218 }
219 None => panic!("record is not a json object"),
220 };
221 Ok((json, id))
222}
223
224fn content_to_json<T>(record: T, id: &Guid) -> Result<serde_json::Value>
229where
230 T: Serialize,
231{
232 let mut payload = serde_json::to_value(record)?;
233 if let Some(ref mut map) = payload.as_object_mut() {
234 if let Some(content_id) = map.get("id").as_ref().and_then(|v| v.as_str()) {
235 assert_eq!(content_id, id);
236 assert!(id.is_valid_for_sync_server(), "record's ID is invalid");
237 } else {
238 map.insert("id".to_string(), serde_json::Value::String(id.to_string()));
239 }
240 };
241 Ok(payload)
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247 use crate::bso::IncomingBso;
248 use serde::{Deserialize, Serialize};
249 use serde_json::json;
250
251 #[derive(Default, Debug, PartialEq, Serialize, Deserialize)]
252 struct TestStruct {
253 id: Guid,
254 data: u32,
255 }
256 #[test]
257 fn test_content_deser() {
258 error_support::init_for_tests();
259 let json = json!({
260 "id": "test",
261 "payload": json!({"data": 1}).to_string(),
262 });
263 let incoming: IncomingBso = serde_json::from_value(json).unwrap();
264 assert_eq!(incoming.envelope.id, "test");
265 let record = incoming.into_content::<TestStruct>().content().unwrap();
266 let expected = TestStruct {
267 id: Guid::new("test"),
268 data: 1,
269 };
270 assert_eq!(record, expected);
271 }
272
273 #[test]
274 fn test_content_deser_empty_id() {
275 error_support::init_for_tests();
276 let json = json!({
277 "id": "",
278 "payload": json!({"data": 1}).to_string(),
279 });
280 let incoming: IncomingBso = serde_json::from_value(json).unwrap();
281 assert_eq!(incoming.envelope.id, "");
284 let content = incoming.into_content::<TestStruct>();
285 assert!(matches!(content.kind, IncomingKind::Malformed));
286 }
287
288 #[test]
289 fn test_content_deser_invalid() {
290 error_support::init_for_tests();
291 let json = json!({
293 "id": "X".repeat(65),
294 "payload": json!({"data": 1}).to_string(),
295 });
296 let incoming: IncomingBso = serde_json::from_value(json).unwrap();
297 let content = incoming.into_content::<TestStruct>();
298 assert!(matches!(content.kind, IncomingKind::Malformed));
299 }
300
301 #[test]
302 fn test_content_deser_not_string() {
303 error_support::init_for_tests();
304 let json = json!({
306 "id": "0",
307 "payload": json!({"id": 0, "data": 1}).to_string(),
308 });
309 let incoming: IncomingBso = serde_json::from_value(json).unwrap();
310 let content = incoming.into_content::<serde_json::Value>();
311 assert!(matches!(content.kind, IncomingKind::Malformed));
312 }
313
314 #[test]
315 fn test_content_ser_with_id() {
316 error_support::init_for_tests();
317 let val = TestStruct {
321 id: Guid::new("test"),
322 data: 1,
323 };
324 let outgoing = OutgoingBso::from_content_with_id(val).unwrap();
325
326 assert_eq!(outgoing.envelope.id, Guid::new("test"));
328
329 let ct_value = serde_json::from_str::<serde_json::Value>(&outgoing.payload).unwrap();
331 assert_eq!(ct_value, json!({"data": 1, "id": "test"}));
332 }
333
334 #[test]
335 fn test_content_ser_with_envelope() {
336 error_support::init_for_tests();
337 let val = TestStruct {
340 id: Guid::new("test"),
341 data: 1,
342 };
343 let envelope: OutgoingEnvelope = Guid::new("test").into();
344 let outgoing = OutgoingBso::from_content(envelope, val).unwrap();
345
346 assert_eq!(outgoing.envelope.id, Guid::new("test"));
348
349 let ct_value = serde_json::from_str::<serde_json::Value>(&outgoing.payload).unwrap();
351 assert_eq!(ct_value, json!({"data": 1, "id": "test"}));
352 }
353
354 #[test]
355 #[should_panic]
356 fn test_content_ser_no_ids() {
357 error_support::init_for_tests();
358 #[derive(Serialize)]
359 struct StructWithNoId {
360 data: u32,
361 }
362 let val = StructWithNoId { data: 1 };
363 let _ = OutgoingBso::from_content_with_id(val);
364 }
365
366 #[test]
367 #[should_panic]
368 fn test_content_ser_not_object() {
369 error_support::init_for_tests();
370 let _ = OutgoingBso::from_content_with_id(json!("string"));
371 }
372
373 #[test]
374 #[should_panic]
375 fn test_content_ser_mismatched_ids() {
376 error_support::init_for_tests();
377 let val = TestStruct {
378 id: Guid::new("test"),
379 data: 1,
380 };
381 let envelope: OutgoingEnvelope = Guid::new("different").into();
382 let _ = OutgoingBso::from_content(envelope, val);
383 }
384
385 #[test]
386 #[should_panic]
387 fn test_content_empty_id() {
388 error_support::init_for_tests();
389 let val = TestStruct {
390 id: Guid::new(""),
391 data: 1,
392 };
393 let _ = OutgoingBso::from_content_with_id(val);
394 }
395
396 #[test]
397 #[should_panic]
398 fn test_content_invalid_id() {
399 error_support::init_for_tests();
400 let val = TestStruct {
401 id: Guid::new(&"X".repeat(65)),
402 data: 1,
403 };
404 let _ = OutgoingBso::from_content_with_id(val);
405 }
406}