1use super::{IncomingBso, IncomingContent, IncomingKind, OutgoingBso, OutgoingEnvelope};
13use crate::Guid;
14use crate::error::{trace, warn};
15use error_support::report_error;
16use serde::Serialize;
17use serde::ser::Error as _;
18
19type Result<T> = std::result::Result<T, serde_json::Error>;
21
22impl<T> IncomingContent<T> {
23 pub fn content(self) -> Option<T> {
25 match self.kind {
26 IncomingKind::Content(t) => Some(t),
27 _ => None,
28 }
29 }
30}
31
32impl<T: std::fmt::Debug> std::fmt::Debug for IncomingKind<T> {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 match self {
36 IncomingKind::Content(r) => {
37 write!(f, "IncomingKind::Content<{:?}>", r)
38 }
39 IncomingKind::Tombstone => write!(f, "IncomingKind::Tombstone"),
40 IncomingKind::Malformed => write!(f, "IncomingKind::Malformed"),
41 }
42 }
43}
44
45impl IncomingBso {
46 pub fn into_content<T: for<'de> serde::Deserialize<'de>>(self) -> IncomingContent<T> {
48 self.into_content_with_fixup(|_| {})
49 }
50
51 pub fn into_content_with_fixup<T: for<'de> serde::Deserialize<'de>>(
54 self,
55 fixup: impl FnOnce(&mut serde_json::Value),
56 ) -> IncomingContent<T> {
57 match serde_json::from_str(&self.payload) {
58 Ok(mut json) => {
59 fixup(&mut json);
61 let kind = json_to_kind(json, &self.envelope.id);
63 IncomingContent {
64 envelope: self.envelope,
65 kind,
66 }
67 }
68 Err(e) => {
69 warn!("Invalid incoming cleartext {}: {}", self.envelope.id, e);
71 IncomingContent {
72 envelope: self.envelope,
73 kind: IncomingKind::Malformed,
74 }
75 }
76 }
77 }
78}
79
80impl OutgoingBso {
81 pub fn new_tombstone(envelope: OutgoingEnvelope) -> Self {
84 Self {
85 envelope,
86 payload: serde_json::json!({"deleted": true}).to_string(),
87 }
88 }
89
90 pub fn from_content_with_id<T>(record: T) -> Result<Self>
95 where
96 T: Serialize,
97 {
98 let (json, id) = content_with_id_to_json(record)?;
99 Ok(Self {
100 envelope: id.into(),
101 payload: serde_json::to_string(&json)?,
102 })
103 }
104
105 pub fn from_content<T>(envelope: OutgoingEnvelope, record: T) -> Result<Self>
108 where
109 T: Serialize,
110 {
111 let json = content_to_json(record, &envelope.id)?;
112 Ok(Self {
113 envelope,
114 payload: serde_json::to_string(&json)?,
115 })
116 }
117}
118
119fn json_to_kind<T>(mut json: serde_json::Value, id: &Guid) -> IncomingKind<T>
132where
133 T: for<'de> serde::Deserialize<'de>,
134{
135 if let serde_json::Value::Object(ref mut map) = json {
138 if map.contains_key("deleted") {
139 return IncomingKind::Tombstone;
140 }
141 match map.get("id") {
142 Some(serde_json::Value::String(content_id)) => {
143 if content_id != id {
145 trace!(
146 "malformed incoming record: envelope id: {} payload id: {}",
147 content_id, 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 if !id.is_valid_for_sync_server() {
213 return Err(serde_json::Error::custom("record's ID is invalid"));
218 }
219 id
220 }
221 None => panic!("record does not have an ID in the payload"),
223 }
224 }
225 None => panic!("record is not a json object"),
226 };
227 Ok((json, id))
228}
229
230fn content_to_json<T>(record: T, id: &Guid) -> Result<serde_json::Value>
235where
236 T: Serialize,
237{
238 let mut payload = serde_json::to_value(record)?;
239 if let Some(ref mut map) = payload.as_object_mut() {
240 if let Some(content_id) = map.get("id").as_ref().and_then(|v| v.as_str()) {
241 assert_eq!(content_id, id);
242 if !id.is_valid_for_sync_server() {
243 return Err(serde_json::Error::custom("record's ID is invalid"));
245 }
246 } else {
247 map.insert("id".to_string(), serde_json::Value::String(id.to_string()));
248 }
249 };
250 Ok(payload)
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256 use crate::bso::IncomingBso;
257 use serde::{Deserialize, Serialize};
258 use serde_json::json;
259
260 #[derive(Default, Debug, PartialEq, Serialize, Deserialize)]
261 struct TestStruct {
262 id: Guid,
263 data: u32,
264 }
265 #[test]
266 fn test_content_deser() {
267 error_support::init_for_tests();
268 let json = json!({
269 "id": "test",
270 "payload": json!({"data": 1}).to_string(),
271 });
272 let incoming: IncomingBso = serde_json::from_value(json).unwrap();
273 assert_eq!(incoming.envelope.id, "test");
274 let record = incoming.into_content::<TestStruct>().content().unwrap();
275 let expected = TestStruct {
276 id: Guid::new("test"),
277 data: 1,
278 };
279 assert_eq!(record, expected);
280 }
281
282 #[test]
283 fn test_content_deser_empty_id() {
284 error_support::init_for_tests();
285 let json = json!({
286 "id": "",
287 "payload": json!({"data": 1}).to_string(),
288 });
289 let incoming: IncomingBso = serde_json::from_value(json).unwrap();
290 assert_eq!(incoming.envelope.id, "");
293 let content = incoming.into_content::<TestStruct>();
294 assert!(matches!(content.kind, IncomingKind::Malformed));
295 }
296
297 #[test]
298 fn test_content_deser_invalid() {
299 error_support::init_for_tests();
300 let json = json!({
302 "id": "X".repeat(65),
303 "payload": json!({"data": 1}).to_string(),
304 });
305 let incoming: IncomingBso = serde_json::from_value(json).unwrap();
306 let content = incoming.into_content::<TestStruct>();
307 assert!(matches!(content.kind, IncomingKind::Malformed));
308 }
309
310 #[test]
311 fn test_content_deser_not_string() {
312 error_support::init_for_tests();
313 let json = json!({
315 "id": "0",
316 "payload": json!({"id": 0, "data": 1}).to_string(),
317 });
318 let incoming: IncomingBso = serde_json::from_value(json).unwrap();
319 let content = incoming.into_content::<serde_json::Value>();
320 assert!(matches!(content.kind, IncomingKind::Malformed));
321 }
322
323 #[test]
324 fn test_content_ser_with_id() {
325 error_support::init_for_tests();
326 let val = TestStruct {
330 id: Guid::new("test"),
331 data: 1,
332 };
333 let outgoing = OutgoingBso::from_content_with_id(val).unwrap();
334
335 assert_eq!(outgoing.envelope.id, Guid::new("test"));
337
338 let ct_value = serde_json::from_str::<serde_json::Value>(&outgoing.payload).unwrap();
340 assert_eq!(ct_value, json!({"data": 1, "id": "test"}));
341 }
342
343 #[test]
344 fn test_content_ser_with_envelope() {
345 error_support::init_for_tests();
346 let val = TestStruct {
349 id: Guid::new("test"),
350 data: 1,
351 };
352 let envelope: OutgoingEnvelope = Guid::new("test").into();
353 let outgoing = OutgoingBso::from_content(envelope, val).unwrap();
354
355 assert_eq!(outgoing.envelope.id, Guid::new("test"));
357
358 let ct_value = serde_json::from_str::<serde_json::Value>(&outgoing.payload).unwrap();
360 assert_eq!(ct_value, json!({"data": 1, "id": "test"}));
361 }
362
363 #[test]
364 #[should_panic]
365 fn test_content_ser_no_ids() {
366 error_support::init_for_tests();
367 #[derive(Serialize)]
368 struct StructWithNoId {
369 data: u32,
370 }
371 let val = StructWithNoId { data: 1 };
372 let _ = OutgoingBso::from_content_with_id(val);
373 }
374
375 #[test]
376 #[should_panic]
377 fn test_content_ser_not_object() {
378 error_support::init_for_tests();
379 let _ = OutgoingBso::from_content_with_id(json!("string"));
380 }
381
382 #[test]
383 #[should_panic]
384 fn test_content_ser_mismatched_ids() {
385 error_support::init_for_tests();
386 let val = TestStruct {
387 id: Guid::new("test"),
388 data: 1,
389 };
390 let envelope: OutgoingEnvelope = Guid::new("different").into();
391 let _ = OutgoingBso::from_content(envelope, val);
392 }
393
394 #[test]
395 fn test_content_empty_id() {
396 error_support::init_for_tests();
397 let val = TestStruct {
398 id: Guid::new(""),
399 data: 1,
400 };
401 assert!(OutgoingBso::from_content_with_id(val).is_err());
403 }
404
405 #[test]
406 fn test_content_invalid_id() {
407 error_support::init_for_tests();
408 let val = TestStruct {
409 id: Guid::new(&"X".repeat(65)),
410 data: 1,
411 };
412 assert!(OutgoingBso::from_content_with_id(val).is_err());
413 }
414}