summaryrefslogtreecommitdiffstats
path: root/third_party/rust/sync15-traits/src/payload.rs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--third_party/rust/sync15-traits/src/payload.rs154
1 files changed, 154 insertions, 0 deletions
diff --git a/third_party/rust/sync15-traits/src/payload.rs b/third_party/rust/sync15-traits/src/payload.rs
new file mode 100644
index 0000000000..2e241774fe
--- /dev/null
+++ b/third_party/rust/sync15-traits/src/payload.rs
@@ -0,0 +1,154 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+use super::Guid;
+use serde::{Deserialize, Serialize};
+use serde_json::{Map, Value as JsonValue};
+
+/// Represents the decrypted payload in a Bso. Provides a minimal layer of type
+/// safety to avoid double-encrypting.
+#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
+pub struct Payload {
+ pub id: Guid,
+
+ #[serde(default)]
+ #[serde(skip_serializing_if = "crate::skip_if_default")]
+ pub deleted: bool,
+
+ #[serde(flatten)]
+ pub data: Map<String, JsonValue>,
+}
+
+impl Payload {
+ pub fn new_tombstone(id: impl Into<Guid>) -> Payload {
+ Payload {
+ id: id.into(),
+ deleted: true,
+ data: Map::new(),
+ }
+ }
+
+ pub fn new_tombstone_with_ttl(id: impl Into<Guid>, ttl: u32) -> Payload {
+ let mut result = Payload::new_tombstone(id);
+ result.data.insert("ttl".into(), ttl.into());
+ result
+ }
+
+ #[inline]
+ pub fn with_sortindex(mut self, index: i32) -> Payload {
+ self.data.insert("sortindex".into(), index.into());
+ self
+ }
+
+ /// "Auto" fields are fields like 'sortindex' and 'ttl', which are:
+ ///
+ /// - Added to the payload automatically when deserializing if present on
+ /// the incoming BSO or envelope.
+ /// - Removed from the payload automatically and attached to the BSO or
+ /// envelope if present on the outgoing payload.
+ pub fn with_auto_field<T: Into<JsonValue>>(mut self, name: &str, v: Option<T>) -> Payload {
+ let old_value: Option<JsonValue> = if let Some(value) = v {
+ self.data.insert(name.into(), value.into())
+ } else {
+ self.data.remove(name)
+ };
+
+ // This is a little dubious, but it seems like if we have a e.g. `sortindex` field on the payload
+ // it's going to be a bug if we use it instead of the "real" sort index.
+ if let Some(old_value) = old_value {
+ log::warn!(
+ "Payload for record {} already contains 'automatic' field \"{}\"? \
+ Overwriting auto value: {} with 'real' value",
+ self.id,
+ name,
+ old_value,
+ );
+ }
+ self
+ }
+
+ pub fn take_auto_field<V>(&mut self, name: &str) -> Option<V>
+ where
+ for<'a> V: Deserialize<'a>,
+ {
+ let v = self.data.remove(name)?;
+ match serde_json::from_value(v) {
+ Ok(v) => Some(v),
+ Err(e) => {
+ log::error!(
+ "Automatic field {} exists on payload, but cannot be deserialized: {}",
+ name,
+ e
+ );
+ None
+ }
+ }
+ }
+
+ #[inline]
+ pub fn id(&self) -> &str {
+ &self.id[..]
+ }
+
+ #[inline]
+ pub fn is_tombstone(&self) -> bool {
+ self.deleted
+ }
+
+ pub fn from_json(value: JsonValue) -> Result<Payload, serde_json::Error> {
+ serde_json::from_value(value)
+ }
+
+ /// Deserializes the BSO payload into a specific record type `T`.
+ ///
+ /// BSO payloads are unstructured JSON objects, with string keys and
+ /// dynamically-typed values. `into_record` makes it more convenient to
+ /// work with payloads by converting them into data type-specific structs.
+ /// Your record type only needs to derive or implement `serde::Deserialize`;
+ /// Serde will take care of the rest.
+ ///
+ /// # Errors
+ ///
+ /// `into_record` returns errors for type mismatches. As an example, trying
+ /// to deserialize a string value from the payload into an integer field in
+ /// `T` will fail.
+ ///
+ /// If there's a chance that a field contains invalid or mistyped data,
+ /// you'll want to extract it from `payload.data` manually, instead of using
+ /// `into_record`. This has been seen in the wild: for example, `dateAdded`
+ /// for bookmarks can be either an integer or a string.
+ pub fn into_record<T>(self) -> Result<T, serde_json::Error>
+ where
+ for<'a> T: Deserialize<'a>,
+ {
+ serde_json::from_value(JsonValue::from(self))
+ }
+
+ pub fn from_record<T: Serialize>(v: T) -> Result<Payload, serde_json::Error> {
+ // TODO(issue #2588): This is kind of dumb, we do to_value and then
+ // from_value. In general a more strongly typed API would help us avoid
+ // this sort of thing... But also concretely this could probably be
+ // avoided? At least in some cases.
+ Ok(Payload::from_json(serde_json::to_value(v)?)?)
+ }
+
+ pub fn into_json_string(self) -> String {
+ serde_json::to_string(&JsonValue::from(self))
+ .expect("JSON.stringify failed, which shouldn't be possible")
+ }
+}
+
+impl From<Payload> for JsonValue {
+ fn from(cleartext: Payload) -> Self {
+ let Payload {
+ mut data,
+ id,
+ deleted,
+ } = cleartext;
+ data.insert("id".to_string(), JsonValue::String(id.into_string()));
+ if deleted {
+ data.insert("deleted".to_string(), JsonValue::Bool(true));
+ }
+ JsonValue::Object(data)
+ }
+}