diff --git a/src/r0/sync/sync_events.rs b/src/r0/sync/sync_events.rs index efacb9b2..2055173b 100644 --- a/src/r0/sync/sync_events.rs +++ b/src/r0/sync/sync_events.rs @@ -68,12 +68,45 @@ pub enum SetPresence { #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(untagged)] pub enum Filter { + // The filter definition needs to be (de)serialized twice because it is a URL-encoded JSON + // string. Since #[ruma_api(query)] only does the latter and this is a very uncommon + // setup, we implement it through custom serde logic for this specific enum variant rather than + // adding another ruma_api attribute. + // + // On the deserialization side, because this is an enum with #[serde(untagged)], serde will + // try the variants in order (https://serde.rs/enum-representations.html). That means because + // FilterDefinition is the first variant, JSON decoding is attempted first which is almost + // functionally equivalent to looking at whether the first symbol is a '{' as the spec says. + // (there are probably some corner cases like leading whitespace) + #[serde(with = "filter_def_serde")] /// A complete filter definition serialized to JSON. FilterDefinition(FilterDefinition), /// The ID of a filter saved on the server. FilterId(String), } +mod filter_def_serde { + use serde::{de::Error as _, ser::Error as _, Deserialize, Deserializer, Serializer}; + + use crate::r0::filter::FilterDefinition; + + pub fn serialize(filter_def: &FilterDefinition, serializer: S) -> Result + where + S: Serializer, + { + let string = serde_json::to_string(filter_def).map_err(S::Error::custom)?; + serializer.serialize_str(&string) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let filter_str = <&str>::deserialize(deserializer)?; + serde_json::from_str(filter_str).map_err(D::Error::custom) + } +} + /// Updates to rooms. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct Rooms {