use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
#[serde(transparent)]
#[non_exhaustive]
pub struct PatchObject(Map<String, Value>);
impl PatchObject {
pub fn new() -> Self {
Self(Map::new())
}
pub fn from_map(map: Map<String, Value>) -> Self {
Self(map)
}
pub fn as_map(&self) -> &Map<String, Value> {
&self.0
}
pub fn as_map_mut(&mut self) -> &mut Map<String, Value> {
&mut self.0
}
pub fn into_inner(self) -> Map<String, Value> {
self.0
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
}
impl From<Map<String, Value>> for PatchObject {
fn from(map: Map<String, Value>) -> Self {
Self(map)
}
}
impl From<PatchObject> for Map<String, Value> {
fn from(patch: PatchObject) -> Self {
patch.0
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn empty_patch_serializes_as_empty_object() {
let p = PatchObject::new();
let wire = serde_json::to_string(&p).expect("serialize empty patch");
assert_eq!(wire, "{}");
}
#[test]
fn empty_object_deserializes_as_empty_patch() {
let p: PatchObject = serde_json::from_str("{}").expect("deserialize empty patch");
assert!(p.is_empty());
assert_eq!(p.len(), 0);
}
#[test]
fn whole_object_form_round_trips() {
let wire = r#"{"name":"Inbox","sortOrder":0,"isSubscribed":true}"#;
let p: PatchObject = serde_json::from_str(wire).expect("deserialize whole-object patch");
assert_eq!(p.len(), 3);
assert_eq!(p.as_map().get("name"), Some(&json!("Inbox")));
assert_eq!(p.as_map().get("sortOrder"), Some(&json!(0)));
assert_eq!(p.as_map().get("isSubscribed"), Some(&json!(true)));
let reserialized = serde_json::to_string(&p).expect("reserialize");
let reparsed: serde_json::Value = serde_json::from_str(&reserialized).unwrap();
let original_parsed: serde_json::Value = serde_json::from_str(wire).unwrap();
assert_eq!(reparsed, original_parsed);
}
#[test]
fn null_leaf_round_trips() {
let wire = r#"{"keywords/$flagged":null}"#;
let p: PatchObject = serde_json::from_str(wire).expect("deserialize null-leaf patch");
assert_eq!(p.as_map().get("keywords/$flagged"), Some(&Value::Null));
let reserialized = serde_json::to_string(&p).expect("reserialize");
assert_eq!(reserialized, wire);
}
#[test]
fn pointer_path_key_round_trips() {
let wire = r#"{"alerts/abc/offset":"PT5M"}"#;
let p: PatchObject = serde_json::from_str(wire).expect("deserialize pointer-path patch");
assert_eq!(p.as_map().get("alerts/abc/offset"), Some(&json!("PT5M")));
let reserialized = serde_json::to_string(&p).expect("reserialize");
assert_eq!(reserialized, wire);
}
#[test]
fn nested_object_value_round_trips() {
let inner = json!({"offset": "PT5M", "type": "display"});
let mut m = Map::new();
m.insert("alerts/abc".to_owned(), inner.clone());
let p = PatchObject::from_map(m);
let wire = serde_json::to_string(&p).expect("serialize nested object");
let parsed: PatchObject = serde_json::from_str(&wire).expect("reparse");
assert_eq!(parsed.as_map().get("alerts/abc"), Some(&inner));
}
#[test]
fn wire_format_is_transparent_json_object() {
let mut m = Map::new();
m.insert("name".to_owned(), json!("Test"));
let p = PatchObject::from_map(m.clone());
let from_patch = serde_json::to_string(&p).unwrap();
let from_map = serde_json::to_string(&m).unwrap();
assert_eq!(from_patch, from_map);
}
#[test]
fn from_into_map_round_trip() {
let mut m = Map::new();
m.insert("k".to_owned(), json!(42));
let p: PatchObject = m.clone().into();
let back: Map<String, Value> = p.into();
assert_eq!(back, m);
}
#[test]
fn default_is_empty() {
let p: PatchObject = Default::default();
assert!(p.is_empty());
assert_eq!(p, PatchObject::new());
}
#[test]
fn as_map_mut_allows_in_place_edits() {
let mut p = PatchObject::new();
p.as_map_mut().insert("name".to_owned(), json!("Edited"));
let wire = serde_json::to_string(&p).unwrap();
assert_eq!(wire, r#"{"name":"Edited"}"#);
}
#[test]
fn non_object_json_fails_to_deserialize() {
assert!(serde_json::from_str::<PatchObject>("[]").is_err());
assert!(serde_json::from_str::<PatchObject>("42").is_err());
assert!(serde_json::from_str::<PatchObject>("\"string\"").is_err());
assert!(serde_json::from_str::<PatchObject>("true").is_err());
assert!(serde_json::from_str::<PatchObject>("null").is_err());
}
}