1use super::{IncomingBso, IncomingContent, IncomingKind, OutgoingBso, OutgoingEnvelope};
13use crate::Guid;
14use crate::error::{trace, warn};
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, id
147 );
148 report_error!(
149 "incoming-invalid-mismatched-ids",
150 "Envelope and payload don't agree on the ID"
151 );
152 return IncomingKind::Malformed;
153 }
154 if !id.is_valid_for_sync_server() {
155 trace!("malformed incoming record: id is not valid: {}", id);
156 report_error!(
157 "incoming-invalid-bad-payload-id",
158 "ID in the payload is invalid"
159 );
160 return IncomingKind::Malformed;
161 }
162 }
163 Some(v) => {
164 trace!("malformed incoming record: id is not a string: {}", v);
167 report_error!("incoming-invalid-wrong_type", "ID is not a string");
168 return IncomingKind::Malformed;
169 }
170 None => {
171 if !id.is_valid_for_sync_server() {
173 trace!("malformed incoming record: id is not valid: {}", id);
174 report_error!(
175 "incoming-invalid-bad-envelope-id",
176 "ID in envelope is not valid"
177 );
178 return IncomingKind::Malformed;
179 }
180 map.insert("id".to_string(), id.to_string().into());
181 }
182 }
183 };
184 match serde_path_to_error::deserialize(json) {
185 Ok(v) => IncomingKind::Content(v),
186 Err(e) => {
187 report_error!(
188 "invalid-incoming-content",
189 "{}.{}: {}",
190 std::any::type_name::<T>(),
191 e.path(),
192 e.inner()
193 );
194 IncomingKind::Malformed
195 }
196 }
197}
198
199fn content_with_id_to_json<T>(record: T) -> Result<(serde_json::Value, Guid)>
202where
203 T: Serialize,
204{
205 let mut json = serde_json::to_value(record)?;
206 let id = match json.as_object_mut() {
207 Some(ref mut map) => {
208 match map.get("id").as_ref().and_then(|v| v.as_str()) {
209 Some(id) => {
210 let id: Guid = id.into();
211 assert!(id.is_valid_for_sync_server(), "record's ID is invalid");
212 id
213 }
214 None => panic!("record does not have an ID in the payload"),
216 }
217 }
218 None => panic!("record is not a json object"),
219 };
220 Ok((json, id))
221}
222
223fn content_to_json<T>(record: T, id: &Guid) -> Result<serde_json::Value>
228where
229 T: Serialize,
230{
231 let mut payload = serde_json::to_value(record)?;
232 if let Some(ref mut map) = payload.as_object_mut() {
233 if let Some(content_id) = map.get("id").as_ref().and_then(|v| v.as_str()) {
234 assert_eq!(content_id, id);
235 assert!(id.is_valid_for_sync_server(), "record's ID is invalid");
236 } else {
237 map.insert("id".to_string(), serde_json::Value::String(id.to_string()));
238 }
239 };
240 Ok(payload)
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246 use crate::bso::IncomingBso;
247 use serde::{Deserialize, Serialize};
248 use serde_json::json;
249
250 #[derive(Default, Debug, PartialEq, Serialize, Deserialize)]
251 struct TestStruct {
252 id: Guid,
253 data: u32,
254 }
255 #[test]
256 fn test_content_deser() {
257 error_support::init_for_tests();
258 let json = json!({
259 "id": "test",
260 "payload": json!({"data": 1}).to_string(),
261 });
262 let incoming: IncomingBso = serde_json::from_value(json).unwrap();
263 assert_eq!(incoming.envelope.id, "test");
264 let record = incoming.into_content::<TestStruct>().content().unwrap();
265 let expected = TestStruct {
266 id: Guid::new("test"),
267 data: 1,
268 };
269 assert_eq!(record, expected);
270 }
271
272 #[test]
273 fn test_content_deser_empty_id() {
274 error_support::init_for_tests();
275 let json = json!({
276 "id": "",
277 "payload": json!({"data": 1}).to_string(),
278 });
279 let incoming: IncomingBso = serde_json::from_value(json).unwrap();
280 assert_eq!(incoming.envelope.id, "");
283 let content = incoming.into_content::<TestStruct>();
284 assert!(matches!(content.kind, IncomingKind::Malformed));
285 }
286
287 #[test]
288 fn test_content_deser_invalid() {
289 error_support::init_for_tests();
290 let json = json!({
292 "id": "X".repeat(65),
293 "payload": json!({"data": 1}).to_string(),
294 });
295 let incoming: IncomingBso = serde_json::from_value(json).unwrap();
296 let content = incoming.into_content::<TestStruct>();
297 assert!(matches!(content.kind, IncomingKind::Malformed));
298 }
299
300 #[test]
301 fn test_content_deser_not_string() {
302 error_support::init_for_tests();
303 let json = json!({
305 "id": "0",
306 "payload": json!({"id": 0, "data": 1}).to_string(),
307 });
308 let incoming: IncomingBso = serde_json::from_value(json).unwrap();
309 let content = incoming.into_content::<serde_json::Value>();
310 assert!(matches!(content.kind, IncomingKind::Malformed));
311 }
312
313 #[test]
314 fn test_content_ser_with_id() {
315 error_support::init_for_tests();
316 let val = TestStruct {
320 id: Guid::new("test"),
321 data: 1,
322 };
323 let outgoing = OutgoingBso::from_content_with_id(val).unwrap();
324
325 assert_eq!(outgoing.envelope.id, Guid::new("test"));
327
328 let ct_value = serde_json::from_str::<serde_json::Value>(&outgoing.payload).unwrap();
330 assert_eq!(ct_value, json!({"data": 1, "id": "test"}));
331 }
332
333 #[test]
334 fn test_content_ser_with_envelope() {
335 error_support::init_for_tests();
336 let val = TestStruct {
339 id: Guid::new("test"),
340 data: 1,
341 };
342 let envelope: OutgoingEnvelope = Guid::new("test").into();
343 let outgoing = OutgoingBso::from_content(envelope, val).unwrap();
344
345 assert_eq!(outgoing.envelope.id, Guid::new("test"));
347
348 let ct_value = serde_json::from_str::<serde_json::Value>(&outgoing.payload).unwrap();
350 assert_eq!(ct_value, json!({"data": 1, "id": "test"}));
351 }
352
353 #[test]
354 #[should_panic]
355 fn test_content_ser_no_ids() {
356 error_support::init_for_tests();
357 #[derive(Serialize)]
358 struct StructWithNoId {
359 data: u32,
360 }
361 let val = StructWithNoId { data: 1 };
362 let _ = OutgoingBso::from_content_with_id(val);
363 }
364
365 #[test]
366 #[should_panic]
367 fn test_content_ser_not_object() {
368 error_support::init_for_tests();
369 let _ = OutgoingBso::from_content_with_id(json!("string"));
370 }
371
372 #[test]
373 #[should_panic]
374 fn test_content_ser_mismatched_ids() {
375 error_support::init_for_tests();
376 let val = TestStruct {
377 id: Guid::new("test"),
378 data: 1,
379 };
380 let envelope: OutgoingEnvelope = Guid::new("different").into();
381 let _ = OutgoingBso::from_content(envelope, val);
382 }
383
384 #[test]
385 #[should_panic]
386 fn test_content_empty_id() {
387 error_support::init_for_tests();
388 let val = TestStruct {
389 id: Guid::new(""),
390 data: 1,
391 };
392 let _ = OutgoingBso::from_content_with_id(val);
393 }
394
395 #[test]
396 #[should_panic]
397 fn test_content_invalid_id() {
398 error_support::init_for_tests();
399 let val = TestStruct {
400 id: Guid::new(&"X".repeat(65)),
401 data: 1,
402 };
403 let _ = OutgoingBso::from_content_with_id(val);
404 }
405}