1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 use super::Guid; 5 use serde::{Deserialize, Serialize}; 6 use serde_json::{Map, Value as JsonValue}; 7 8 /// Represents the decrypted payload in a Bso. Provides a minimal layer of type 9 /// safety to avoid double-encrypting. 10 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 11 pub struct Payload { 12 pub id: Guid, 13 14 #[serde(default)] 15 #[serde(skip_serializing_if = "crate::skip_if_default")] 16 pub deleted: bool, 17 18 #[serde(flatten)] 19 pub data: Map<String, JsonValue>, 20 } 21 22 impl Payload { new_tombstone(id: impl Into<Guid>) -> Payload23 pub fn new_tombstone(id: impl Into<Guid>) -> Payload { 24 Payload { 25 id: id.into(), 26 deleted: true, 27 data: Map::new(), 28 } 29 } 30 new_tombstone_with_ttl(id: impl Into<Guid>, ttl: u32) -> Payload31 pub fn new_tombstone_with_ttl(id: impl Into<Guid>, ttl: u32) -> Payload { 32 let mut result = Payload::new_tombstone(id); 33 result.data.insert("ttl".into(), ttl.into()); 34 result 35 } 36 37 #[inline] with_sortindex(mut self, index: i32) -> Payload38 pub fn with_sortindex(mut self, index: i32) -> Payload { 39 self.data.insert("sortindex".into(), index.into()); 40 self 41 } 42 43 /// "Auto" fields are fields like 'sortindex' and 'ttl', which are: 44 /// 45 /// - Added to the payload automatically when deserializing if present on 46 /// the incoming BSO or envelope. 47 /// - Removed from the payload automatically and attached to the BSO or 48 /// envelope if present on the outgoing payload. with_auto_field<T: Into<JsonValue>>(mut self, name: &str, v: Option<T>) -> Payload49 pub fn with_auto_field<T: Into<JsonValue>>(mut self, name: &str, v: Option<T>) -> Payload { 50 let old_value: Option<JsonValue> = if let Some(value) = v { 51 self.data.insert(name.into(), value.into()) 52 } else { 53 self.data.remove(name) 54 }; 55 56 // This is a little dubious, but it seems like if we have a e.g. `sortindex` field on the payload 57 // it's going to be a bug if we use it instead of the "real" sort index. 58 if let Some(old_value) = old_value { 59 log::warn!( 60 "Payload for record {} already contains 'automatic' field \"{}\"? \ 61 Overwriting auto value: {} with 'real' value", 62 self.id, 63 name, 64 old_value, 65 ); 66 } 67 self 68 } 69 take_auto_field<V>(&mut self, name: &str) -> Option<V> where for<'a> V: Deserialize<'a>,70 pub fn take_auto_field<V>(&mut self, name: &str) -> Option<V> 71 where 72 for<'a> V: Deserialize<'a>, 73 { 74 let v = self.data.remove(name)?; 75 match serde_json::from_value(v) { 76 Ok(v) => Some(v), 77 Err(e) => { 78 log::error!( 79 "Automatic field {} exists on payload, but cannot be deserialized: {}", 80 name, 81 e 82 ); 83 None 84 } 85 } 86 } 87 88 #[inline] id(&self) -> &str89 pub fn id(&self) -> &str { 90 &self.id[..] 91 } 92 93 #[inline] is_tombstone(&self) -> bool94 pub fn is_tombstone(&self) -> bool { 95 self.deleted 96 } 97 from_json(value: JsonValue) -> Result<Payload, serde_json::Error>98 pub fn from_json(value: JsonValue) -> Result<Payload, serde_json::Error> { 99 serde_json::from_value(value) 100 } 101 102 /// Deserializes the BSO payload into a specific record type `T`. 103 /// 104 /// BSO payloads are unstructured JSON objects, with string keys and 105 /// dynamically-typed values. `into_record` makes it more convenient to 106 /// work with payloads by converting them into data type-specific structs. 107 /// Your record type only needs to derive or implement `serde::Deserialize`; 108 /// Serde will take care of the rest. 109 /// 110 /// # Errors 111 /// 112 /// `into_record` returns errors for type mismatches. As an example, trying 113 /// to deserialize a string value from the payload into an integer field in 114 /// `T` will fail. 115 /// 116 /// If there's a chance that a field contains invalid or mistyped data, 117 /// you'll want to extract it from `payload.data` manually, instead of using 118 /// `into_record`. This has been seen in the wild: for example, `dateAdded` 119 /// for bookmarks can be either an integer or a string. into_record<T>(self) -> Result<T, serde_json::Error> where for<'a> T: Deserialize<'a>,120 pub fn into_record<T>(self) -> Result<T, serde_json::Error> 121 where 122 for<'a> T: Deserialize<'a>, 123 { 124 serde_json::from_value(JsonValue::from(self)) 125 } 126 from_record<T: Serialize>(v: T) -> Result<Payload, serde_json::Error>127 pub fn from_record<T: Serialize>(v: T) -> Result<Payload, serde_json::Error> { 128 // TODO(issue #2588): This is kind of dumb, we do to_value and then 129 // from_value. In general a more strongly typed API would help us avoid 130 // this sort of thing... But also concretely this could probably be 131 // avoided? At least in some cases. 132 Payload::from_json(serde_json::to_value(v)?) 133 } 134 into_json_string(self) -> String135 pub fn into_json_string(self) -> String { 136 serde_json::to_string(&JsonValue::from(self)) 137 .expect("JSON.stringify failed, which shouldn't be possible") 138 } 139 } 140 141 impl From<Payload> for JsonValue { from(cleartext: Payload) -> Self142 fn from(cleartext: Payload) -> Self { 143 let Payload { 144 mut data, 145 id, 146 deleted, 147 } = cleartext; 148 data.insert("id".to_string(), JsonValue::String(id.into_string())); 149 if deleted { 150 data.insert("deleted".to_string(), JsonValue::Bool(true)); 151 } 152 JsonValue::Object(data) 153 } 154 } 155