diff --git a/src/core/matrix/mod.rs b/src/core/matrix/mod.rs index b38d4c9a..08a88971 100644 --- a/src/core/matrix/mod.rs +++ b/src/core/matrix/mod.rs @@ -2,6 +2,7 @@ pub mod event; pub mod pdu; +pub mod space_roles; pub mod state_key; pub mod state_res; diff --git a/src/core/matrix/space_roles.rs b/src/core/matrix/space_roles.rs new file mode 100644 index 00000000..560e3f00 --- /dev/null +++ b/src/core/matrix/space_roles.rs @@ -0,0 +1,100 @@ +//! Custom state event content types for space permission cascading. +//! +//! These events live in Space rooms and define roles, user-role assignments, +//! and room-role requirements. + +use std::collections::BTreeMap; + +use serde::{Deserialize, Serialize}; + +/// Content for `m.space.roles` (state key: "") +/// +/// Defines available roles for a Space. +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct SpaceRolesEventContent { + pub roles: BTreeMap, +} + +/// A single role definition within a Space. +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct RoleDefinition { + pub description: String, + + /// If present, users with this role receive this power level in child + /// rooms. + #[serde(skip_serializing_if = "Option::is_none")] + pub power_level: Option, +} + +/// Content for `m.space.role.member` (state key: user ID) +/// +/// Assigns roles to a user within a Space. +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct SpaceRoleMemberEventContent { + pub roles: Vec, +} + +/// Content for `m.space.role.room` (state key: room ID) +/// +/// Declares which roles a child room requires for access. +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +pub struct SpaceRoleRoomEventContent { + pub required_roles: Vec, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serialize_space_roles() { + let mut roles = BTreeMap::new(); + roles.insert( + "admin".to_owned(), + RoleDefinition { + description: "Space administrator".to_owned(), + power_level: Some(100), + }, + ); + roles.insert( + "nsfw".to_owned(), + RoleDefinition { + description: "NSFW access".to_owned(), + power_level: None, + }, + ); + let content = SpaceRolesEventContent { roles }; + let json = serde_json::to_string(&content).unwrap(); + let deserialized: SpaceRolesEventContent = serde_json::from_str(&json).unwrap(); + assert_eq!(deserialized.roles.len(), 2); + assert_eq!(deserialized.roles["admin"].power_level, Some(100)); + assert!(deserialized.roles["nsfw"].power_level.is_none()); + } + + #[test] + fn serialize_role_member() { + let content = SpaceRoleMemberEventContent { + roles: vec!["nsfw".to_owned(), "vip".to_owned()], + }; + let json = serde_json::to_string(&content).unwrap(); + let deserialized: SpaceRoleMemberEventContent = serde_json::from_str(&json).unwrap(); + assert_eq!(deserialized.roles, vec!["nsfw", "vip"]); + } + + #[test] + fn serialize_role_room() { + let content = SpaceRoleRoomEventContent { + required_roles: vec!["nsfw".to_owned()], + }; + let json = serde_json::to_string(&content).unwrap(); + let deserialized: SpaceRoleRoomEventContent = serde_json::from_str(&json).unwrap(); + assert_eq!(deserialized.required_roles, vec!["nsfw"]); + } + + #[test] + fn empty_roles_deserialize() { + let json = r#"{"roles":{}}"#; + let content: SpaceRolesEventContent = serde_json::from_str(json).unwrap(); + assert!(content.roles.is_empty()); + } +}