use std::collections::BTreeMap;
use std::fmt;
use serde::de::DeserializeOwned;
use serde::ser::SerializeMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use crate::id::{Id, State, UTCDate};
const STATE_CHANGE_TYPE: &str = "StateChange";
const PUSH_VERIFICATION_TYPE: &str = "PushVerification";
const REDACTED: &str = "[REDACTED]";
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct StateChange {
pub changed: BTreeMap<Id, BTreeMap<String, State>>,
pub extra: serde_json::Map<String, serde_json::Value>,
}
impl StateChange {
pub fn new(changed: BTreeMap<Id, BTreeMap<String, State>>) -> Self {
Self {
changed,
extra: serde_json::Map::new(),
}
}
}
impl Serialize for StateChange {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
let mut map = s.serialize_map(None)?;
map.serialize_entry("@type", STATE_CHANGE_TYPE)?;
map.serialize_entry("changed", &self.changed)?;
for (k, v) in &self.extra {
map.serialize_entry(k, v)?;
}
map.end()
}
}
impl<'de> Deserialize<'de> for StateChange {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let mut extra = serde_json::Map::deserialize(d)?;
take_type(&mut extra, STATE_CHANGE_TYPE)?;
let changed = take_field(&mut extra, "changed")?;
Ok(StateChange { changed, extra })
}
}
#[non_exhaustive]
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PushSubscription {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<Id>,
pub device_client_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub keys: Option<PushKeys>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub verification_code: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires: Option<UTCDate>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub types: Option<Vec<String>>,
#[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
pub extra: serde_json::Map<String, serde_json::Value>,
}
impl fmt::Debug for PushSubscription {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PushSubscription")
.field("id", &self.id)
.field("device_client_id", &self.device_client_id)
.field("url", &self.url.as_ref().map(|_| REDACTED))
.field("keys", &self.keys)
.field(
"verification_code",
&self.verification_code.as_ref().map(|_| REDACTED),
)
.field("expires", &self.expires)
.field("types", &self.types)
.field("extra", &self.extra)
.finish()
}
}
#[non_exhaustive]
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PushKeys {
pub p256dh: String,
pub auth: String,
#[serde(flatten, default, skip_serializing_if = "serde_json::Map::is_empty")]
pub extra: serde_json::Map<String, serde_json::Value>,
}
impl fmt::Debug for PushKeys {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PushKeys")
.field("p256dh", &self.p256dh)
.field("auth", &REDACTED)
.field("extra", &self.extra)
.finish()
}
}
#[non_exhaustive]
#[derive(Clone, PartialEq, Eq)]
pub struct PushVerification {
pub push_subscription_id: Id,
pub verification_code: String,
pub extra: serde_json::Map<String, serde_json::Value>,
}
impl fmt::Debug for PushVerification {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("PushVerification")
.field("push_subscription_id", &self.push_subscription_id)
.field("verification_code", &REDACTED)
.field("extra", &self.extra)
.finish()
}
}
impl Serialize for PushVerification {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
let mut map = s.serialize_map(None)?;
map.serialize_entry("@type", PUSH_VERIFICATION_TYPE)?;
map.serialize_entry("pushSubscriptionId", &self.push_subscription_id)?;
map.serialize_entry("verificationCode", &self.verification_code)?;
for (k, v) in &self.extra {
map.serialize_entry(k, v)?;
}
map.end()
}
}
impl<'de> Deserialize<'de> for PushVerification {
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let mut extra = serde_json::Map::deserialize(d)?;
take_type(&mut extra, PUSH_VERIFICATION_TYPE)?;
let push_subscription_id = take_field(&mut extra, "pushSubscriptionId")?;
let verification_code = take_field(&mut extra, "verificationCode")?;
Ok(PushVerification {
push_subscription_id,
verification_code,
extra,
})
}
}
fn take_type<E: serde::de::Error>(
map: &mut serde_json::Map<String, serde_json::Value>,
expected: &str,
) -> Result<(), E> {
match map.remove("@type") {
None => Ok(()),
Some(serde_json::Value::String(s)) if s == expected => Ok(()),
Some(other) => Err(E::custom(format!(
"@type must be \"{expected}\", found {other}"
))),
}
}
fn take_field<T: DeserializeOwned, E: serde::de::Error>(
map: &mut serde_json::Map<String, serde_json::Value>,
key: &'static str,
) -> Result<T, E> {
let value = map.remove(key).ok_or_else(|| E::missing_field(key))?;
serde_json::from_value(value).map_err(E::custom)
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn state_change_preserves_vendor_extras() {
let raw = json!({
"changed": {
"acc1": { "Email": "s42" }
},
"acmeCorpSequence": 17
});
let obj: StateChange = serde_json::from_value(raw).expect("StateChange must deserialize");
assert_eq!(
obj.extra.get("acmeCorpSequence").and_then(|v| v.as_u64()),
Some(17)
);
let v = serde_json::to_value(&obj).expect("StateChange must serialize");
assert_eq!(v["acmeCorpSequence"], json!(17));
}
#[test]
fn extra_equality_is_order_insensitive_under_workspace_flags() {
let raw_a = json!({
"changed": {"acc1": {"Email": "s1"}},
"vendorA": 1,
"vendorB": 2
});
let raw_b = json!({
"changed": {"acc1": {"Email": "s1"}},
"vendorB": 2,
"vendorA": 1
});
let a: StateChange = serde_json::from_value(raw_a).expect("a must deserialize");
let b: StateChange = serde_json::from_value(raw_b).expect("b must deserialize");
assert_eq!(
a, b,
"extra-map equality is order-insensitive under the workspace's \
default serde_json::Map (BTreeMap-backed); if this fails, \
check whether preserve_order has been enabled in the dep graph"
);
}
}