Support event ID formats for all room versions.
This commit is contained in:
		
							parent
							
								
									6d9b3c7bf2
								
							
						
					
					
						commit
						fb8039ac6d
					
				
							
								
								
									
										217
									
								
								src/event_id.rs
									
									
									
									
									
								
							
							
						
						
									
										217
									
								
								src/event_id.rs
									
									
									
									
									
								
							| @ -20,24 +20,59 @@ use crate::{display, error::Error, generate_localpart, parse_id}; | ||||
| /// An `EventId` is generated randomly or converted from a string slice, and can be converted back
 | ||||
| /// into a string as needed.
 | ||||
| ///
 | ||||
| /// # Room versions
 | ||||
| ///
 | ||||
| /// Matrix specifies multiple [room versions](https://matrix.org/docs/spec/#room-versions) and the
 | ||||
| /// format of event identifiers differ between them. The original format used by room versions 1
 | ||||
| /// and 2 uses a short pseudorandom "localpart" followed by the hostname and port of the
 | ||||
| /// originating homeserver. Later room versions change event identifiers to be a hash of the event
 | ||||
| /// encoded with Base64. Some of the methods provided by `EventId` are only relevant to the
 | ||||
| /// original event format.
 | ||||
| ///
 | ||||
| /// ```
 | ||||
| /// # use std::convert::TryFrom;
 | ||||
| /// # use ruma_identifiers::EventId;
 | ||||
| /// // Original format
 | ||||
| /// assert_eq!(
 | ||||
| ///     EventId::try_from("$h29iv0s8:example.com").unwrap().to_string(),
 | ||||
| ///     "$h29iv0s8:example.com"
 | ||||
| /// );
 | ||||
| /// // Room version 3 format
 | ||||
| /// assert_eq!(
 | ||||
| ///     EventId::try_from("$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk").unwrap().to_string(),
 | ||||
| ///     "$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk"
 | ||||
| /// );
 | ||||
| /// // Room version 4 format
 | ||||
| /// assert_eq!(
 | ||||
| ///     EventId::try_from("$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg").unwrap().to_string(),
 | ||||
| ///     "$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg"
 | ||||
| /// );
 | ||||
| /// ```
 | ||||
| #[derive(Clone, Debug, Eq, Hash, PartialEq)] | ||||
| #[cfg_attr(feature = "diesel", derive(FromSqlRow, QueryId, AsExpression, SqlType))] | ||||
| #[cfg_attr(feature = "diesel", sql_type = "Text")] | ||||
| pub struct EventId { | ||||
| pub struct EventId(Format); | ||||
| 
 | ||||
| /// Different event ID formats from the different Matrix room versions.
 | ||||
| #[derive(Clone, Debug, Eq, Hash, PartialEq)] | ||||
| enum Format { | ||||
|     /// The original format as used by Matrix room versions 1 and 2.
 | ||||
|     Original(Original), | ||||
|     /// The format used by Matrix room version 3.
 | ||||
|     Base64(String), | ||||
|     /// The format used by Matrix room version 4.
 | ||||
|     UrlSafeBase64(String), | ||||
| } | ||||
| 
 | ||||
| /// An event in the original format as used by Matrix room versions 1 and 2.
 | ||||
| #[derive(Clone, Debug, Eq, Hash, PartialEq)] | ||||
| struct Original { | ||||
|     /// The hostname of the homeserver.
 | ||||
|     hostname: Host, | ||||
|     pub hostname: Host, | ||||
|     /// The event's unique ID.
 | ||||
|     opaque_id: String, | ||||
|     pub localpart: String, | ||||
|     /// The network port of the homeserver.
 | ||||
|     port: u16, | ||||
|     pub port: u16, | ||||
| } | ||||
| 
 | ||||
| /// A serde visitor for `EventId`.
 | ||||
| @ -45,42 +80,67 @@ struct EventIdVisitor; | ||||
| 
 | ||||
| impl EventId { | ||||
|     /// Attempts to generate an `EventId` for the given origin server with a localpart consisting
 | ||||
|     /// of 18 random ASCII characters.
 | ||||
|     /// of 18 random ASCII characters. This should only be used for events in the original format
 | ||||
|     /// as used by Matrix room versions 1 and 2.
 | ||||
|     ///
 | ||||
|     /// Fails if the given origin server name cannot be parsed as a valid host.
 | ||||
|     pub fn new(server_name: &str) -> Result<Self, Error> { | ||||
|         let event_id = format!("${}:{}", generate_localpart(18), server_name); | ||||
|         let (opaque_id, host, port) = parse_id('$', &event_id)?; | ||||
|         let (localpart, host, port) = parse_id('$', &event_id)?; | ||||
| 
 | ||||
|         Ok(Self { | ||||
|         Ok(Self(Format::Original(Original { | ||||
|             hostname: host, | ||||
|             opaque_id: opaque_id.to_string(), | ||||
|             localpart: localpart.to_string(), | ||||
|             port, | ||||
|         }) | ||||
|         }))) | ||||
|     } | ||||
| 
 | ||||
|     /// Returns a `Host` for the event ID, containing the server name (minus the port) of the
 | ||||
|     /// originating homeserver.
 | ||||
|     /// originating homeserver. Only applicable to events in the original format as used by Matrix
 | ||||
|     /// room versions 1 and 2.
 | ||||
|     ///
 | ||||
|     /// The host can be either a domain name, an IPv4 address, or an IPv6 address.
 | ||||
|     pub fn hostname(&self) -> &Host { | ||||
|         &self.hostname | ||||
|     pub fn hostname(&self) -> Option<&Host> { | ||||
|         if let Format::Original(original) = &self.0 { | ||||
|             Some(&original.hostname) | ||||
|         } else { | ||||
|             None | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Returns the event's opaque ID.
 | ||||
|     pub fn opaque_id(&self) -> &str { | ||||
|         &self.opaque_id | ||||
|     /// Returns the event's unique ID. For the original event format as used by Matrix room
 | ||||
|     /// versions 1 and 2, this is the "localpart" that precedes the homeserver. For later formats,
 | ||||
|     /// this is the entire ID without the leading $ sigil.
 | ||||
|     pub fn localpart(&self) -> &str { | ||||
|         match &self.0 { | ||||
|             Format::Original(original) => &original.localpart, | ||||
|             Format::Base64(id) | Format::UrlSafeBase64(id) => id, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Returns the port the originating homeserver can be accessed on.
 | ||||
|     pub fn port(&self) -> u16 { | ||||
|         self.port | ||||
|     /// Returns the port the originating homeserver can be accessed on. Only applicable to events
 | ||||
|     /// in the original format as used by Matrix room versions 1 and 2.
 | ||||
|     pub fn port(&self) -> Option<u16> { | ||||
|         if let Format::Original(original) = &self.0 { | ||||
|             Some(original.port) | ||||
|         } else { | ||||
|             None | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl Display for EventId { | ||||
|     fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { | ||||
|         display(f, '$', &self.opaque_id, &self.hostname, self.port) | ||||
|         match &self.0 { | ||||
|             Format::Original(original) => display( | ||||
|                 f, | ||||
|                 '$', | ||||
|                 &original.localpart, | ||||
|                 &original.hostname, | ||||
|                 original.port, | ||||
|             ), | ||||
|             Format::Base64(id) | Format::UrlSafeBase64(id) => write!(f, "${}", id), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -107,16 +167,25 @@ impl<'a> TryFrom<&'a str> for EventId { | ||||
| 
 | ||||
|     /// Attempts to create a new Matrix event ID from a string representation.
 | ||||
|     ///
 | ||||
|     /// The string must include the leading $ sigil, the opaque ID, a literal colon, and a valid
 | ||||
|     /// server name.
 | ||||
|     /// If using the original event format as used by Matrix room versions 1 and 2, the string must
 | ||||
|     /// include the leading $ sigil, the localpart, a literal colon, and a valid homeserver
 | ||||
|     /// hostname.
 | ||||
|     fn try_from(event_id: &'a str) -> Result<Self, Self::Error> { | ||||
|         let (opaque_id, host, port) = parse_id('$', event_id)?; | ||||
|         if event_id.contains(':') { | ||||
|             let (localpart, host, port) = parse_id('$', event_id)?; | ||||
| 
 | ||||
|         Ok(Self { | ||||
|             hostname: host, | ||||
|             opaque_id: opaque_id.to_owned(), | ||||
|             port, | ||||
|         }) | ||||
|             Ok(Self(Format::Original(Original { | ||||
|                 hostname: host, | ||||
|                 localpart: localpart.to_owned(), | ||||
|                 port, | ||||
|             }))) | ||||
|         } else if !event_id.starts_with('$') { | ||||
|             Err(Error::MissingSigil) | ||||
|         } else if event_id.contains(|chr| chr == '+' || chr == '/') { | ||||
|             Ok(Self(Format::Base64(event_id[1..].to_string()))) | ||||
|         } else { | ||||
|             Ok(Self(Format::UrlSafeBase64(event_id[1..].to_string()))) | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| @ -148,7 +217,7 @@ mod tests { | ||||
|     use crate::error::Error; | ||||
| 
 | ||||
|     #[test] | ||||
|     fn valid_event_id() { | ||||
|     fn valid_original_event_id() { | ||||
|         assert_eq!( | ||||
|             EventId::try_from("$39hvsi03hlne:example.com") | ||||
|                 .expect("Failed to create EventId.") | ||||
| @ -157,6 +226,26 @@ mod tests { | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn valid_base64_event_id() { | ||||
|         assert_eq!( | ||||
|             EventId::try_from("$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk") | ||||
|                 .expect("Failed to create EventId.") | ||||
|                 .to_string(), | ||||
|             "$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk" | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn valid_url_safe_base64_event_id() { | ||||
|         assert_eq!( | ||||
|             EventId::try_from("$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg") | ||||
|                 .expect("Failed to create EventId.") | ||||
|                 .to_string(), | ||||
|             "$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg" | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn generate_random_valid_event_id() { | ||||
|         let event_id = EventId::new("example.com") | ||||
| @ -173,7 +262,7 @@ mod tests { | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn serialize_valid_event_id() { | ||||
|     fn serialize_valid_original_event_id() { | ||||
|         assert_eq!( | ||||
|             to_string( | ||||
|                 &EventId::try_from("$39hvsi03hlne:example.com").expect("Failed to create EventId.") | ||||
| @ -184,7 +273,31 @@ mod tests { | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn deserialize_valid_event_id() { | ||||
|     fn serialize_valid_base64_event_id() { | ||||
|         assert_eq!( | ||||
|             to_string( | ||||
|                 &EventId::try_from("$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk") | ||||
|                     .expect("Failed to create EventId.") | ||||
|             ) | ||||
|             .expect("Failed to convert EventId to JSON."), | ||||
|             r#""$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk""# | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn serialize_valid_url_safe_base64_event_id() { | ||||
|         assert_eq!( | ||||
|             to_string( | ||||
|                 &EventId::try_from("$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg") | ||||
|                     .expect("Failed to create EventId.") | ||||
|             ) | ||||
|             .expect("Failed to convert EventId to JSON."), | ||||
|             r#""$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg""# | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn deserialize_valid_original_event_id() { | ||||
|         assert_eq!( | ||||
|             from_str::<EventId>(r#""$39hvsi03hlne:example.com""#) | ||||
|                 .expect("Failed to convert JSON to EventId"), | ||||
| @ -193,7 +306,27 @@ mod tests { | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn valid_event_id_with_explicit_standard_port() { | ||||
|     fn deserialize_valid_base64_event_id() { | ||||
|         assert_eq!( | ||||
|             from_str::<EventId>(r#""$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk""#) | ||||
|                 .expect("Failed to convert JSON to EventId"), | ||||
|             EventId::try_from("$acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk") | ||||
|                 .expect("Failed to create EventId.") | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn deserialize_valid_url_safe_base64_event_id() { | ||||
|         assert_eq!( | ||||
|             from_str::<EventId>(r#""$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg""#) | ||||
|                 .expect("Failed to convert JSON to EventId"), | ||||
|             EventId::try_from("$Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg") | ||||
|                 .expect("Failed to create EventId.") | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn valid_original_event_id_with_explicit_standard_port() { | ||||
|         assert_eq!( | ||||
|             EventId::try_from("$39hvsi03hlne:example.com:443") | ||||
|                 .expect("Failed to create EventId.") | ||||
| @ -203,7 +336,7 @@ mod tests { | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn valid_event_id_with_non_standard_port() { | ||||
|     fn valid_original_event_id_with_non_standard_port() { | ||||
|         assert_eq!( | ||||
|             EventId::try_from("$39hvsi03hlne:example.com:5000") | ||||
|                 .expect("Failed to create EventId.") | ||||
| @ -213,7 +346,7 @@ mod tests { | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn missing_event_id_sigil() { | ||||
|     fn missing_original_event_id_sigil() { | ||||
|         assert_eq!( | ||||
|             EventId::try_from("39hvsi03hlne:example.com").err().unwrap(), | ||||
|             Error::MissingSigil | ||||
| @ -221,10 +354,22 @@ mod tests { | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn missing_event_id_delimiter() { | ||||
|     fn missing_base64_event_id_sigil() { | ||||
|         assert_eq!( | ||||
|             EventId::try_from("$39hvsi03hlne").err().unwrap(), | ||||
|             Error::MissingDelimiter | ||||
|             EventId::try_from("acR1l0raoZnm60CBwAVgqbZqoO/mYU81xysh1u7XcJk") | ||||
|                 .err() | ||||
|                 .unwrap(), | ||||
|             Error::MissingSigil | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     #[test] | ||||
|     fn missing_url_safe_base64_event_id_sigil() { | ||||
|         assert_eq!( | ||||
|             EventId::try_from("Rqnc-F-dvnEYJTyHq_iKxU2bZ1CI92-kuZq3a5lr5Zg") | ||||
|                 .err() | ||||
|                 .unwrap(), | ||||
|             Error::MissingSigil | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user