From ed559c63f88221e22a90180608bbeefa9ddf1ccc Mon Sep 17 00:00:00 2001 From: Akshay Date: Wed, 24 Feb 2021 17:02:56 +0530 Subject: [PATCH] appservice-api: Add types for appservice registration YAML --- ruma-appservice-api/Cargo.toml | 4 + ruma-appservice-api/src/lib.rs | 134 ++++++++++++++++++ .../tests/appservice_registration.rs | 65 +++++++++ 3 files changed, 203 insertions(+) create mode 100644 ruma-appservice-api/tests/appservice_registration.rs diff --git a/ruma-appservice-api/Cargo.toml b/ruma-appservice-api/Cargo.toml index 08b730b7..a3141a9d 100644 --- a/ruma-appservice-api/Cargo.toml +++ b/ruma-appservice-api/Cargo.toml @@ -22,3 +22,7 @@ serde_json = "1.0.61" [features] unstable-exhaustive-types = [] + +[dev-dependencies] +matches = "0.1.8" +serde_yaml = "0.8.17" diff --git a/ruma-appservice-api/src/lib.rs b/ruma-appservice-api/src/lib.rs index 475b088d..5aea2dcc 100644 --- a/ruma-appservice-api/src/lib.rs +++ b/ruma-appservice-api/src/lib.rs @@ -4,6 +4,140 @@ #![warn(missing_debug_implementations, missing_docs)] +use serde::{Deserialize, Serialize}; + pub mod event; pub mod query; pub mod thirdparty; + +/// A namespace defined by an application service. +/// +/// Used for [appservice registration](https://matrix.org/docs/spec/application_service/r0.1.2#registration). +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] +pub struct Namespace { + /// Whether this application service has exclusive access to events within this namespace. + pub exclusive: bool, + + /// A regular expression defining which values this namespace includes. + pub regex: String, +} + +impl Namespace { + /// Creates a new `Namespace` with the given exclusivity and regex pattern. + pub fn new(exclusive: bool, regex: String) -> Self { + Namespace { exclusive, regex } + } +} + +/// Namespaces defined by an application service. +/// +/// Used for [appservice registration](https://matrix.org/docs/spec/application_service/r0.1.2#registration). +#[derive(Clone, Debug, Default, Serialize, Deserialize)] +#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] +pub struct Namespaces { + /// Events which are sent from certain users. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub users: Vec, + + /// Events which are sent in rooms with certain room aliases. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub aliases: Vec, + + /// Events which are sent in rooms with certain room IDs. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub rooms: Vec, +} + +impl Namespaces { + /// Creates a new `Namespaces` instance with empty namespaces for `users`, `aliases` and + /// `rooms` (none of them are explicitly required) + pub fn new() -> Self { + Self::default() + } +} + +/// Information required in the registration yaml file that a homeserver needs. +/// +/// To create an instance of this type, first create a `RegistrationInit` and convert it via +/// `Registration::from` / `.into()`. +/// +/// Used for [appservice registration](https://matrix.org/docs/spec/application_service/r0.1.2#registration). +#[derive(Clone, Debug, Serialize, Deserialize)] +#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] +pub struct Registration { + /// A unique, user - defined ID of the application service which will never change. + pub id: String, + + /// The URL for the application service. + pub url: String, + + /// A unique token for application services to use to authenticate requests to Homeservers. + pub as_token: String, + + /// A unique token for Homeservers to use to authenticate requests to application services. + pub hs_token: String, + + /// The localpart of the user associated with the application service. + pub sender_localpart: String, + + /// A list of users, aliases and rooms namespaces that the application service controls. + pub namespaces: Namespaces, + + /// Whether requests from masqueraded users are rate-limited. The sender is excluded. + #[serde(skip_serializing_if = "Option::is_none")] + pub rate_limited: Option, + + /// The external protocols which the application service provides (e.g. IRC). + #[serde(skip_serializing_if = "Option::is_none")] + pub protocols: Option>, +} + +/// Initial set of fields of `Registration`. +/// +/// This struct will not be updated even if additional fields are added to `Registration` in a new +/// (non-breaking) release of the Matrix specification. +/// +/// Used for [appservice registration](https://matrix.org/docs/spec/application_service/r0.1.2#registration). +#[derive(Debug)] +pub struct RegistrationInit { + /// A unique, user - defined ID of the application service which will never change. + pub id: String, + + /// The URL for the application service. + pub url: String, + + /// A unique token for application services to use to authenticate requests to Homeservers. + pub as_token: String, + + /// A unique token for Homeservers to use to authenticate requests to application services. + pub hs_token: String, + + /// The localpart of the user associated with the application service. + pub sender_localpart: String, + + /// A list of users, aliases and rooms namespaces that the application service controls. + pub namespaces: Namespaces, + + /// Whether requests from masqueraded users are rate-limited. The sender is excluded. + pub rate_limited: Option, + + /// The external protocols which the application service provides (e.g. IRC). + pub protocols: Option>, +} + +impl From for Registration { + fn from(init: RegistrationInit) -> Self { + let RegistrationInit { + id, + url, + as_token, + hs_token, + sender_localpart, + namespaces, + rate_limited, + protocols, + } = init; + Self { id, url, as_token, hs_token, sender_localpart, namespaces, rate_limited, protocols } + } +} diff --git a/ruma-appservice-api/tests/appservice_registration.rs b/ruma-appservice-api/tests/appservice_registration.rs new file mode 100644 index 00000000..26ef9800 --- /dev/null +++ b/ruma-appservice-api/tests/appservice_registration.rs @@ -0,0 +1,65 @@ +use matches::assert_matches; +use ruma_appservice_api::{Namespace, Namespaces, Registration}; + +#[test] +fn registration_deserialization() { + let registration_config = r##" + id: "IRC Bridge" + url: "http://127.0.0.1:1234" + as_token: "30c05ae90a248a4188e620216fa72e349803310ec83e2a77b34fe90be6081f46" + hs_token: "312df522183efd404ec1cd22d2ffa4bbc76a8c1ccf541dd692eef281356bb74e" + sender_localpart: "_irc_bot" + namespaces: + users: + - exclusive: true + regex: "@_irc_bridge_.*" + aliases: + - exclusive: false + regex: "#_irc_bridge_.*" + rooms: [] + "##; + let observed = serde_yaml::from_str(®istration_config).unwrap(); + assert_matches!( + observed, + Registration { + id, + url, + as_token, + hs_token, + sender_localpart, + rate_limited, + protocols, + namespaces: Namespaces { users, aliases, rooms, .. }, + .. + } + if id == "IRC Bridge" + && url == "http://127.0.0.1:1234" + && as_token == "30c05ae90a248a4188e620216fa72e349803310ec83e2a77b34fe90be6081f46" + && hs_token == "312df522183efd404ec1cd22d2ffa4bbc76a8c1ccf541dd692eef281356bb74e" + && sender_localpart == "_irc_bot" + && rate_limited == None + && protocols == None + && users[0] == Namespace::new(true, "@_irc_bridge_.*".into()) + && aliases[0] == Namespace::new(false, "#_irc_bridge_.*".into()) + && rooms.is_empty() + ); +} + +#[test] +fn config_with_optional_url() { + let registration_config = r#" + id: "IRC Bridge" + url: null + as_token: "30c05ae90a248a4188e620216fa72e349803310ec83e2a77b34fe90be6081f46" + hs_token: "312df522183efd404ec1cd22d2ffa4bbc76a8c1ccf541dd692eef281356bb74e" + sender_localpart: "_irc_bot" + namespaces: + users: [] + aliases: [] + rooms: [] + "#; + assert_matches!( + serde_yaml::from_str(®istration_config).unwrap(), + Registration { url, .. } if url == "null" + ); +}