Properly handle CanonicalAliasEvent and NameEvent content being absent, null or empty,
which is allowed by the spec to show those events were deleted: https://matrix.org/docs/spec/client_server/r0.4.0.html#m-room-canonical-alias https://matrix.org/docs/spec/client_server/r0.4.0.html#m-room-canonical-alias
This commit is contained in:
		
							parent
							
								
									f7aa8edd5f
								
							
						
					
					
						commit
						01156ad661
					
				
							
								
								
									
										27
									
								
								src/lib.rs
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								src/lib.rs
									
									
									
									
									
								
							| @ -104,7 +104,7 @@ use std::fmt::{Debug, Display, Error as FmtError, Formatter, Result as FmtResult | ||||
| 
 | ||||
| use ruma_identifiers::{EventId, RoomId, UserId}; | ||||
| use serde::{ | ||||
|     de::{Error as SerdeError, Visitor}, | ||||
|     de::{Error as SerdeError, IntoDeserializer, Visitor}, | ||||
|     Deserialize, Deserializer, Serialize, Serializer, | ||||
| }; | ||||
| use serde_json::Value; | ||||
| @ -346,6 +346,31 @@ impl<'de> Deserialize<'de> for EventType { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Serde deserialization decorator to map empty Strings to None,
 | ||||
| /// and forward non-empty Strings to the Deserialize implementation for T.
 | ||||
| /// Useful for the typical
 | ||||
| /// "A room with an X event with an absent, null, or empty Y field
 | ||||
| /// should be treated the same as a room with no such event."
 | ||||
| /// formulation in the spec.
 | ||||
| ///
 | ||||
| /// To be used like this:
 | ||||
| /// `#[serde(deserialize_with = "empty_string_as_none"]`
 | ||||
| /// Relevant serde issue: https://github.com/serde-rs/serde/issues/1425
 | ||||
| fn empty_string_as_none<'de, D, T>(de: D) -> Result<Option<T>, D::Error> | ||||
| where | ||||
|     D: serde::Deserializer<'de>, | ||||
|     T: serde::Deserialize<'de>, | ||||
| { | ||||
|     let opt = Option::<String>::deserialize(de)?; | ||||
|     let opt = opt.as_ref().map(String::as_str); | ||||
|     match opt { | ||||
|         None | Some("") => Ok(None), | ||||
|         // If T = String, like in m.room.name, the second deserialize is actually superfluous.
 | ||||
|         // TODO: optimize that somehow?
 | ||||
|         Some(s) => T::deserialize(s.into_deserializer()).map(Some), | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use serde_json::{from_str, to_string}; | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| //! Types for the *m.room.canonical_alias* event.
 | ||||
| 
 | ||||
| use crate::empty_string_as_none; | ||||
| use ruma_identifiers::RoomAliasId; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| 
 | ||||
| @ -12,5 +13,63 @@ state_event! { | ||||
| #[derive(Clone, Debug, Deserialize, Serialize)] | ||||
| pub struct CanonicalAliasEventContent { | ||||
|     /// The canonical alias.
 | ||||
|     pub alias: RoomAliasId, | ||||
|     /// Rooms with `alias: None` should be treated the same as a room with no canonical alias.
 | ||||
|     // The spec says “A room with an m.room.canonical_alias event with an absent, null, or empty alias field
 | ||||
|     // should be treated the same as a room with no m.room.canonical_alias event.”.
 | ||||
|     // Serde maps null fields to None by default, serde(default) maps an absent field to None,
 | ||||
|     // and empty_string_as_none does exactly that, preventing empty strings getting parsed as RoomAliasId.
 | ||||
|     #[serde(default)] | ||||
|     #[serde(deserialize_with = "empty_string_as_none")] | ||||
|     pub alias: Option<RoomAliasId>, | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use serde_json::from_str; | ||||
| 
 | ||||
|     use super::CanonicalAliasEventContent; | ||||
|     use ruma_identifiers::RoomAliasId; | ||||
|     use std::convert::TryFrom; | ||||
| 
 | ||||
|     #[test] | ||||
|     fn absent_field_as_none() { | ||||
|         assert_eq!( | ||||
|             from_str::<CanonicalAliasEventContent>(r#"{}"#) | ||||
|                 .unwrap() | ||||
|                 .alias, | ||||
|             None | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn null_field_as_none() { | ||||
|         assert_eq!( | ||||
|             from_str::<CanonicalAliasEventContent>(r#"{"alias":null}"#) | ||||
|                 .unwrap() | ||||
|                 .alias, | ||||
|             None | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn empty_field_as_none() { | ||||
|         assert_eq!( | ||||
|             from_str::<CanonicalAliasEventContent>(r#"{"alias":""}"#) | ||||
|                 .unwrap() | ||||
|                 .alias, | ||||
|             None | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn nonempty_field_as_some() { | ||||
|         let alias = Some(RoomAliasId::try_from("#somewhere:localhost").unwrap()); | ||||
| 
 | ||||
|         assert_eq!( | ||||
|             from_str::<CanonicalAliasEventContent>(r##"{"alias":"#somewhere:localhost"}"##) | ||||
|                 .unwrap() | ||||
|                 .alias, | ||||
|             alias | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,5 +1,6 @@ | ||||
| //! Types for the *m.room.name* event.
 | ||||
| 
 | ||||
| use crate::empty_string_as_none; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| 
 | ||||
| state_event! { | ||||
| @ -11,5 +12,53 @@ state_event! { | ||||
| #[derive(Clone, Debug, Deserialize, Serialize)] | ||||
| pub struct NameEventContent { | ||||
|     /// The name of the room. This MUST NOT exceed 255 bytes.
 | ||||
|     pub name: String, | ||||
|     /// Rooms with `name: None` should be treated the same as a room with no name.
 | ||||
|     // The spec says “A room with an m.room.name event with an absent, null, or empty name field
 | ||||
|     // should be treated the same as a room with no m.room.name event.”.
 | ||||
|     // Serde maps null fields to None by default, serde(default) maps an absent field to None,
 | ||||
|     // and empty_string_as_none completes the handling.
 | ||||
|     #[serde(default)] | ||||
|     #[serde(deserialize_with = "empty_string_as_none")] | ||||
|     pub name: Option<String>, | ||||
| } | ||||
| 
 | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::NameEventContent; | ||||
|     use serde_json::from_str; | ||||
| 
 | ||||
|     #[test] | ||||
|     fn absent_field_as_none() { | ||||
|         assert_eq!(from_str::<NameEventContent>(r#"{}"#).unwrap().name, None); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn null_field_as_none() { | ||||
|         assert_eq!( | ||||
|             from_str::<NameEventContent>(r#"{"name":null}"#) | ||||
|                 .unwrap() | ||||
|                 .name, | ||||
|             None | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn empty_field_as_none() { | ||||
|         assert_eq!( | ||||
|             from_str::<NameEventContent>(r#"{"name":""}"#).unwrap().name, | ||||
|             None | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn nonempty_field_as_some() { | ||||
|         let name = Some("The room name".to_string()); | ||||
| 
 | ||||
|         assert_eq!( | ||||
|             from_str::<NameEventContent>(r##"{"name":"The room name"}"##) | ||||
|                 .unwrap() | ||||
|                 .name, | ||||
|             name | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -333,7 +333,7 @@ mod tests { | ||||
| 
 | ||||
|         match from_str::<StrippedState>(name_event).unwrap() { | ||||
|             StrippedState::RoomName(event) => { | ||||
|                 assert_eq!(event.content.name, "Ruma"); | ||||
|                 assert_eq!(event.content.name, Some("Ruma".to_string())); | ||||
|                 assert_eq!(event.event_type, EventType::RoomName); | ||||
|                 assert_eq!(event.state_key, ""); | ||||
|                 assert_eq!(event.sender.to_string(), "@example:localhost"); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user