diff --git a/crates/ruma-client-api/CHANGELOG.md b/crates/ruma-client-api/CHANGELOG.md index 6a758222..25f6647e 100644 --- a/crates/ruma-client-api/CHANGELOG.md +++ b/crates/ruma-client-api/CHANGELOG.md @@ -35,6 +35,7 @@ Improvements: authentication for appservices. - Add support for recursion on the `get_relating_events` endpoints, according to MSC3981 / Matrix 1.10 +- Add server support discovery endpoint, according to MSC1929 / Matrix 1.10 # 0.17.4 diff --git a/crates/ruma-client-api/src/discovery.rs b/crates/ruma-client-api/src/discovery.rs index 15c7b099..f0d59399 100644 --- a/crates/ruma-client-api/src/discovery.rs +++ b/crates/ruma-client-api/src/discovery.rs @@ -1,6 +1,7 @@ //! Server discovery endpoints. pub mod discover_homeserver; +pub mod discover_support; #[cfg(feature = "unstable-msc2965")] pub mod get_authentication_issuer; pub mod get_capabilities; diff --git a/crates/ruma-client-api/src/discovery/discover_support.rs b/crates/ruma-client-api/src/discovery/discover_support.rs new file mode 100644 index 00000000..e38012e8 --- /dev/null +++ b/crates/ruma-client-api/src/discovery/discover_support.rs @@ -0,0 +1,117 @@ +//! `GET /.well-known/matrix/support` ([spec]) +//! +//! [spec]: https://spec.matrix.org/latest/client-server-api/#getwell-knownmatrixsupport +//! +//! Get server admin contact and support page of a homeserver's domain. + +use ruma_common::{ + api::{request, response, Metadata}, + metadata, + serde::StringEnum, + OwnedUserId, +}; +use serde::{Deserialize, Serialize}; + +use crate::PrivOwnedStr; + +const METADATA: Metadata = metadata! { + method: GET, + rate_limited: false, + authentication: None, + history: { + 1.10 => "/.well-known/matrix/support", + } +}; + +/// Request type for the `discover_support` endpoint. +#[request(error = crate::Error)] +#[derive(Default)] +pub struct Request {} + +/// Response type for the `discover_support` endpoint. +#[response(error = crate::Error)] +pub struct Response { + /// Ways to contact the server administrator. + /// + /// At least one of `contacts` or `support_page` is required. If only `contacts` is set, it + /// must contain at least one item. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub contacts: Vec, + + /// The URL of a page to give users help specific to the homeserver, like extra + /// login/registration steps. + /// + /// At least one of `contacts` or `support_page` is required. + #[serde(skip_serializing_if = "Option::is_none")] + pub support_page: Option, +} + +impl Request { + /// Creates an empty `Request`. + pub fn new() -> Self { + Self {} + } +} + +impl Response { + /// Creates a new `Response` with the given contacts. + pub fn with_contacts(contacts: Vec) -> Self { + Self { contacts, support_page: None } + } + + /// Creates a new `Response` with the given support page. + pub fn with_support_page(support_page: String) -> Self { + Self { contacts: Vec::new(), support_page: Some(support_page) } + } +} + +/// A way to contact the server administrator. +#[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] +pub struct Contact { + /// An informal description of what the contact methods are used for. + pub role: ContactRole, + + /// An email address to reach the administrator. + /// + /// At least one of `matrix_id` or `email_address` is required. + #[serde(skip_serializing_if = "Option::is_none")] + pub email_address: Option, + + /// A Matrix User ID representing the administrator. + /// + /// It could be an account registered on a different homeserver so the administrator can be + /// contacted when the homeserver is down. + /// + /// At least one of `matrix_id` or `email_address` is required. + #[serde(skip_serializing_if = "Option::is_none")] + pub matrix_id: Option, +} + +impl Contact { + /// Creates a new `Contact` with the given role and email address. + pub fn with_email_address(role: ContactRole, email_address: String) -> Self { + Self { role, email_address: Some(email_address), matrix_id: None } + } + + /// Creates a new `Contact` with the given role and Matrix User ID. + pub fn with_matrix_id(role: ContactRole, matrix_id: OwnedUserId) -> Self { + Self { role, email_address: None, matrix_id: Some(matrix_id) } + } +} + +/// An informal description of what the contact methods are used for. +#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))] +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, StringEnum)] +#[ruma_enum(rename_all = "m.role.snake_case")] +#[non_exhaustive] +pub enum ContactRole { + /// A catch-all role for any queries. + Admin, + + /// A role intended for sensitive requests. + Security, + + #[doc(hidden)] + _Custom(PrivOwnedStr), +} diff --git a/crates/ruma-macros/src/serde/case.rs b/crates/ruma-macros/src/serde/case.rs index 0e53f4d4..b321b717 100644 --- a/crates/ruma-macros/src/serde/case.rs +++ b/crates/ruma-macros/src/serde/case.rs @@ -42,6 +42,8 @@ pub enum RenameRule { MatrixDottedCase, /// Rename the direct children to "m.rule.snake_case" style. MatrixRuleSnakeCase, + /// Rename the direct children to "m.role.snake_case" style. + MatrixRoleSnakeCase, } impl RenameRule { @@ -71,6 +73,7 @@ impl RenameRule { String::from("m.") + &SnakeCase.apply_to_variant(variant).replace('_', ".") } MatrixRuleSnakeCase => String::from(".m.rule.") + &SnakeCase.apply_to_variant(variant), + MatrixRoleSnakeCase => String::from("m.role.") + &SnakeCase.apply_to_variant(variant), } } @@ -106,6 +109,7 @@ impl RenameRule { MatrixSnakeCase => String::from("m.") + field, MatrixDottedCase => String::from("m.") + &field.replace('_', "."), MatrixRuleSnakeCase => String::from(".m.rule.") + field, + MatrixRoleSnakeCase => String::from("m.role.") + field, } } } @@ -127,6 +131,7 @@ impl FromStr for RenameRule { "m.snake_case" => Ok(MatrixSnakeCase), "m.dotted.case" => Ok(MatrixDottedCase), ".m.rule.snake_case" => Ok(MatrixRuleSnakeCase), + "m.role.snake_case" => Ok(MatrixRoleSnakeCase), _ => Err(()), } } @@ -147,6 +152,7 @@ fn rename_variants() { m_snake, m_dotted, m_rule_snake, + m_role_snake, ) in &[ ( "Outcome", @@ -161,6 +167,7 @@ fn rename_variants() { "m.outcome", "m.outcome", ".m.rule.outcome", + "m.role.outcome", ), ( "VeryTasty", @@ -175,8 +182,9 @@ fn rename_variants() { "m.very_tasty", "m.very.tasty", ".m.rule.very_tasty", + "m.role.very_tasty", ), - ("A", "a", "A", "a", "a", "A", "a", "A", "M_A", "m.a", "m.a", ".m.rule.a"), + ("A", "a", "A", "a", "a", "A", "a", "A", "M_A", "m.a", "m.a", ".m.rule.a", "m.role.a"), ( "Z42", "z42", @@ -190,6 +198,7 @@ fn rename_variants() { "m.z42", "m.z42", ".m.rule.z42", + "m.role.z42", ), ] { assert_eq!(None.apply_to_variant(original), original); @@ -205,6 +214,7 @@ fn rename_variants() { assert_eq!(MatrixSnakeCase.apply_to_variant(original), m_snake); assert_eq!(MatrixDottedCase.apply_to_variant(original), m_dotted); assert_eq!(MatrixRuleSnakeCase.apply_to_variant(original), m_rule_snake); + assert_eq!(MatrixRoleSnakeCase.apply_to_variant(original), m_role_snake); } } @@ -222,6 +232,7 @@ fn rename_fields() { m_snake, m_dotted, m_rule_snake, + m_role_snake, ) in &[ ( "outcome", @@ -235,6 +246,7 @@ fn rename_fields() { "m.outcome", "m.outcome", ".m.rule.outcome", + "m.role.outcome", ), ( "very_tasty", @@ -248,9 +260,23 @@ fn rename_fields() { "m.very_tasty", "m.very.tasty", ".m.rule.very_tasty", + "m.role.very_tasty", + ), + ("a", "A", "A", "a", "A", "a", "A", "M_A", "m.a", "m.a", ".m.rule.a", "m.role.a"), + ( + "z42", + "Z42", + "Z42", + "z42", + "Z42", + "z42", + "Z42", + "M_Z42", + "m.z42", + "m.z42", + ".m.rule.z42", + "m.role.z42", ), - ("a", "A", "A", "a", "A", "a", "A", "M_A", "m.a", "m.a", ".m.rule.a"), - ("z42", "Z42", "Z42", "z42", "Z42", "z42", "Z42", "M_Z42", "m.z42", "m.z42", ".m.rule.z42"), ] { assert_eq!(None.apply_to_field(original), original); assert_eq!(Uppercase.apply_to_field(original), upper); @@ -264,5 +290,6 @@ fn rename_fields() { assert_eq!(MatrixSnakeCase.apply_to_field(original), m_snake); assert_eq!(MatrixDottedCase.apply_to_field(original), m_dotted); assert_eq!(MatrixRuleSnakeCase.apply_to_field(original), m_rule_snake); + assert_eq!(MatrixRoleSnakeCase.apply_to_field(original), m_role_snake); } }