diff --git a/ruma-client-api/CHANGELOG.md b/ruma-client-api/CHANGELOG.md index 2350d7ed..1339021e 100644 --- a/ruma-client-api/CHANGELOG.md +++ b/ruma-client-api/CHANGELOG.md @@ -5,6 +5,7 @@ Bug fixes: * Fix deserialization of `r0::room::get_room_event::Response` * More missing fields in `r0::sync::sync_events::Response` can be deserialized * Fix `get_tags::Response` serialization +* Fix unsetting avatar URL when `compat` feature is enabled Breaking changes: diff --git a/ruma-client-api/src/r0/profile/set_avatar_url.rs b/ruma-client-api/src/r0/profile/set_avatar_url.rs index e877bead..026f9021 100644 --- a/ruma-client-api/src/r0/profile/set_avatar_url.rs +++ b/ruma-client-api/src/r0/profile/set_avatar_url.rs @@ -24,10 +24,17 @@ ruma_api! { /// /// If you activate the `compat` feature, this field being an empty string in JSON will give /// you `None` here. - #[serde(skip_serializing_if = "Option::is_none")] #[cfg_attr( feature = "compat", - serde(default, deserialize_with = "ruma_serde::empty_string_as_none") + serde( + default, + deserialize_with = "ruma_serde::empty_string_as_none", + serialize_with = "ruma_serde::none_as_empty_string" + ) + )] + #[cfg_attr( + not(feature = "compat"), + serde(skip_serializing_if = "Option::is_none") )] pub avatar_url: Option<&'a MxcUri>, } diff --git a/ruma-serde/CHANGELOG.md b/ruma-serde/CHANGELOG.md index 42b3b9cb..c2bcc700 100644 --- a/ruma-serde/CHANGELOG.md +++ b/ruma-serde/CHANGELOG.md @@ -4,6 +4,10 @@ Breaking changes: * Remove the `empty` module from the public API +Improvements: + +* Add serialization decorator `none_as_empty_string` to serialize `None`s as empty strings + # 0.3.1 Bug fixes: diff --git a/ruma-serde/src/lib.rs b/ruma-serde/src/lib.rs index 6d137a57..3f572086 100644 --- a/ruma-serde/src/lib.rs +++ b/ruma-serde/src/lib.rs @@ -28,6 +28,7 @@ pub use empty::vec_as_map_of_empty; pub use raw::Raw; pub use strings::{ btreemap_int_or_string_to_int_values, empty_string_as_none, int_or_string_to_int, + none_as_empty_string, }; /// Check whether a value is equal to its default value. diff --git a/ruma-serde/src/strings.rs b/ruma-serde/src/strings.rs index 68ccb814..b1cef542 100644 --- a/ruma-serde/src/strings.rs +++ b/ruma-serde/src/strings.rs @@ -3,7 +3,8 @@ use std::{collections::BTreeMap, convert::TryInto, fmt, marker::PhantomData}; use js_int::Int; use serde::{ de::{self, Deserializer, IntoDeserializer as _, MapAccess, Visitor}, - Deserialize, + ser::Serializer, + Deserialize, Serialize, }; /// Serde deserialization decorator to map empty Strings to None, @@ -30,6 +31,24 @@ where } } +/// Serde serializiation decorator to map None to an empty String, +/// and forward Somes to the Serialize implemention for T. +/// +/// To be used like this: +/// `#[serde(serialize_with = "empty_string_as_none")]` +pub fn none_as_empty_string( + value: &Option, + serializer: S, +) -> Result +where + S: Serializer, +{ + match value { + Some(x) => x.serialize(serializer), + None => serializer.serialize_str(""), + } +} + /// Take either an integer number or a string and deserialize to an integer number. /// /// To be used like this: diff --git a/ruma-serde/tests/empty_strings.rs b/ruma-serde/tests/empty_strings.rs new file mode 100644 index 00000000..b93665d9 --- /dev/null +++ b/ruma-serde/tests/empty_strings.rs @@ -0,0 +1,33 @@ +use serde::{Deserialize, Serialize}; +use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +struct StringStruct { + #[serde( + deserialize_with = "ruma_serde::empty_string_as_none", + serialize_with = "ruma_serde::none_as_empty_string" + )] + x: Option, +} + +#[derive(Serialize, Deserialize, PartialEq, Debug)] +struct NoneStruct { + #[serde(skip_serializing_if = "Option::is_none")] + x: Option, +} + +#[test] +fn empty_se() { + let string = StringStruct { x: None }; + let none = NoneStruct { x: None }; + assert_eq!(to_json_value(string).unwrap(), json!({"x": ""})); + assert_eq!(to_json_value(none).unwrap(), json!({})); +} + +#[test] +fn empty_de() { + let string = StringStruct { x: None }; + let none = NoneStruct { x: None }; + assert_eq!(from_json_value::(json!({"x": ""})).unwrap(), string); + assert_eq!(from_json_value::(json!({})).unwrap(), none); +}