1use std::borrow::Cow;
4
5use as_variant::as_variant;
6use js_int::UInt;
7use serde::{Deserialize, Serialize, de};
8use serde_json::{Value as JsonValue, value::RawValue as RawJsonValue};
9
10use crate::{
11 EventEncryptionAlgorithm, OwnedMxcUri, OwnedRoomAliasId, OwnedRoomId, PrivOwnedStr,
12 RoomVersionId,
13 serde::{JsonObject, StringEnum, from_raw_json_value},
14};
15
16#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
18#[derive(Clone, StringEnum)]
19#[non_exhaustive]
20pub enum RoomType {
21 #[ruma_enum(rename = "m.space")]
23 Space,
24
25 #[cfg(feature = "unstable-msc3417")]
30 #[ruma_enum(rename = "org.matrix.msc3417.call")]
31 Call,
32
33 #[doc(hidden)]
35 _Custom(PrivOwnedStr),
36}
37
38#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
44#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
45#[serde(tag = "join_rule", rename_all = "snake_case")]
46pub enum JoinRule {
47 Invite,
50
51 Knock,
55
56 Private,
58
59 Restricted(Restricted),
62
63 KnockRestricted(Restricted),
66
67 Public,
69
70 #[doc(hidden)]
71 #[serde(untagged)]
72 _Custom(CustomJoinRule),
73}
74
75impl JoinRule {
76 pub fn kind(&self) -> JoinRuleKind {
78 match self {
79 Self::Invite => JoinRuleKind::Invite,
80 Self::Knock => JoinRuleKind::Knock,
81 Self::Private => JoinRuleKind::Private,
82 Self::Restricted(_) => JoinRuleKind::Restricted,
83 Self::KnockRestricted(_) => JoinRuleKind::KnockRestricted,
84 Self::Public => JoinRuleKind::Public,
85 Self::_Custom(CustomJoinRule { join_rule, .. }) => {
86 JoinRuleKind::_Custom(PrivOwnedStr(join_rule.as_str().into()))
87 }
88 }
89 }
90
91 pub fn as_str(&self) -> &str {
93 match self {
94 JoinRule::Invite => "invite",
95 JoinRule::Knock => "knock",
96 JoinRule::Private => "private",
97 JoinRule::Restricted(_) => "restricted",
98 JoinRule::KnockRestricted(_) => "knock_restricted",
99 JoinRule::Public => "public",
100 JoinRule::_Custom(CustomJoinRule { join_rule, .. }) => join_rule,
101 }
102 }
103
104 pub fn data(&self) -> Cow<'_, JsonObject> {
112 fn serialize<T: Serialize>(obj: &T) -> JsonObject {
113 match serde_json::to_value(obj).expect("join rule serialization should succeed") {
114 JsonValue::Object(mut obj) => {
115 obj.remove("body");
116 obj
117 }
118 _ => panic!("all message types should serialize to objects"),
119 }
120 }
121
122 match self {
123 JoinRule::Invite | JoinRule::Knock | JoinRule::Private | JoinRule::Public => {
124 Cow::Owned(JsonObject::new())
125 }
126 JoinRule::Restricted(restricted) | JoinRule::KnockRestricted(restricted) => {
127 Cow::Owned(serialize(restricted))
128 }
129 Self::_Custom(c) => Cow::Borrowed(&c.data),
130 }
131 }
132}
133
134impl<'de> Deserialize<'de> for JoinRule {
135 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
136 where
137 D: de::Deserializer<'de>,
138 {
139 let json: Box<RawJsonValue> = Box::deserialize(deserializer)?;
140
141 #[derive(Deserialize)]
142 struct ExtractType<'a> {
143 #[serde(borrow)]
144 join_rule: Cow<'a, str>,
145 }
146
147 let ExtractType { join_rule } = from_raw_json_value(&json)?;
148
149 match join_rule.as_ref() {
150 "invite" => Ok(Self::Invite),
151 "knock" => Ok(Self::Knock),
152 "private" => Ok(Self::Private),
153 "restricted" => from_raw_json_value(&json).map(Self::Restricted),
154 "knock_restricted" => from_raw_json_value(&json).map(Self::KnockRestricted),
155 "public" => Ok(Self::Public),
156 _ => {
157 let mut data = from_raw_json_value::<JsonObject, _>(&json)?;
158 let join_rule = as_variant!(
159 data.remove("join_rule")
160 .expect("we already checked that the join_rule field is present"),
161 JsonValue::String
162 )
163 .expect("we already checked that the join rule is a string");
164
165 Ok(Self::_Custom(CustomJoinRule { join_rule, data }))
166 }
167 }
168 }
169}
170
171#[doc(hidden)]
173#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
174pub struct CustomJoinRule {
175 join_rule: String,
177
178 #[serde(flatten)]
180 data: JsonObject,
181}
182
183#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
185#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
186pub struct Restricted {
187 #[serde(default, deserialize_with = "crate::serde::ignore_invalid_vec_items")]
189 pub allow: Vec<AllowRule>,
190}
191
192impl Restricted {
193 pub fn new(allow: Vec<AllowRule>) -> Self {
195 Self { allow }
196 }
197}
198
199#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
201#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
202#[serde(untagged)]
203pub enum AllowRule {
204 RoomMembership(RoomMembership),
206
207 #[doc(hidden)]
208 _Custom(CustomAllowRule),
209}
210
211impl AllowRule {
212 pub fn room_membership(room_id: OwnedRoomId) -> Self {
214 Self::RoomMembership(RoomMembership::new(room_id))
215 }
216
217 pub fn rule_type(&self) -> &str {
219 match self {
220 AllowRule::RoomMembership(_) => "m.room_membership",
221 AllowRule::_Custom(CustomAllowRule { rule_type, .. }) => rule_type,
222 }
223 }
224
225 pub fn data(&self) -> Cow<'_, JsonObject> {
233 fn serialize<T: Serialize>(obj: &T) -> JsonObject {
234 match serde_json::to_value(obj).expect("join rule serialization should succeed") {
235 JsonValue::Object(obj) => obj,
236 _ => panic!("all message types should serialize to objects"),
237 }
238 }
239
240 match self {
241 AllowRule::RoomMembership(membership) => Cow::Owned(serialize(membership)),
242 Self::_Custom(custom) => Cow::Borrowed(&custom.data),
243 }
244 }
245}
246
247#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
249#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
250#[serde(tag = "type", rename = "m.room_membership")]
251pub struct RoomMembership {
252 pub room_id: OwnedRoomId,
254}
255
256impl RoomMembership {
257 pub fn new(room_id: OwnedRoomId) -> Self {
259 Self { room_id }
260 }
261}
262
263#[doc(hidden)]
264#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
265#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
266pub struct CustomAllowRule {
267 #[serde(rename = "type")]
269 rule_type: String,
270
271 #[serde(flatten)]
273 data: JsonObject,
274}
275
276impl<'de> Deserialize<'de> for AllowRule {
277 fn deserialize<D>(deserializer: D) -> Result<AllowRule, D::Error>
278 where
279 D: de::Deserializer<'de>,
280 {
281 let json: Box<RawJsonValue> = Box::deserialize(deserializer)?;
282
283 #[derive(Deserialize)]
285 struct ExtractType<'a> {
286 #[serde(borrow, rename = "type")]
287 rule_type: Cow<'a, str>,
288 }
289
290 let ExtractType { rule_type } = from_raw_json_value(&json)?;
292
293 match rule_type.as_ref() {
294 "m.room_membership" => from_raw_json_value(&json).map(Self::RoomMembership),
295 _ => {
296 let mut data = from_raw_json_value::<JsonObject, _>(&json)?;
297 let rule_type = as_variant!(
298 data.remove("type").expect("we already checked that the type field is present"),
299 JsonValue::String
300 )
301 .expect("we already checked that the type is a string");
302
303 Ok(Self::_Custom(CustomAllowRule { rule_type, data }))
304 }
305 }
306 }
307}
308
309#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
311#[derive(Clone, Default, StringEnum)]
312#[ruma_enum(rename_all = "snake_case")]
313#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
314pub enum JoinRuleKind {
315 Invite,
318
319 Knock,
323
324 Private,
326
327 Restricted,
330
331 KnockRestricted,
334
335 #[default]
337 Public,
338
339 #[doc(hidden)]
340 _Custom(PrivOwnedStr),
341}
342
343impl From<JoinRuleKind> for JoinRuleSummary {
344 fn from(value: JoinRuleKind) -> Self {
345 match value {
346 JoinRuleKind::Invite => Self::Invite,
347 JoinRuleKind::Knock => Self::Knock,
348 JoinRuleKind::Private => Self::Private,
349 JoinRuleKind::Restricted => Self::Restricted(Default::default()),
350 JoinRuleKind::KnockRestricted => Self::KnockRestricted(Default::default()),
351 JoinRuleKind::Public => Self::Public,
352 JoinRuleKind::_Custom(s) => Self::_Custom(s),
353 }
354 }
355}
356
357#[derive(Debug, Clone, Serialize)]
359#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
360pub struct RoomSummary {
361 pub room_id: OwnedRoomId,
363
364 #[serde(skip_serializing_if = "Option::is_none")]
369 pub canonical_alias: Option<OwnedRoomAliasId>,
370
371 #[serde(skip_serializing_if = "Option::is_none")]
373 pub name: Option<String>,
374
375 #[serde(skip_serializing_if = "Option::is_none")]
377 pub topic: Option<String>,
378
379 #[serde(skip_serializing_if = "Option::is_none")]
384 pub avatar_url: Option<OwnedMxcUri>,
385
386 #[serde(skip_serializing_if = "Option::is_none")]
388 pub room_type: Option<RoomType>,
389
390 pub num_joined_members: UInt,
392
393 #[serde(flatten, skip_serializing_if = "ruma_common::serde::is_default")]
395 pub join_rule: JoinRuleSummary,
396
397 pub world_readable: bool,
399
400 pub guest_can_join: bool,
404
405 #[serde(skip_serializing_if = "Option::is_none")]
407 pub encryption: Option<EventEncryptionAlgorithm>,
408
409 #[serde(skip_serializing_if = "Option::is_none")]
411 pub room_version: Option<RoomVersionId>,
412}
413
414impl RoomSummary {
415 pub fn new(
417 room_id: OwnedRoomId,
418 join_rule: JoinRuleSummary,
419 guest_can_join: bool,
420 num_joined_members: UInt,
421 world_readable: bool,
422 ) -> Self {
423 Self {
424 room_id,
425 canonical_alias: None,
426 name: None,
427 topic: None,
428 avatar_url: None,
429 room_type: None,
430 num_joined_members,
431 join_rule,
432 world_readable,
433 guest_can_join,
434 encryption: None,
435 room_version: None,
436 }
437 }
438}
439
440impl<'de> Deserialize<'de> for RoomSummary {
441 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
442 where
443 D: de::Deserializer<'de>,
444 {
445 #[derive(Deserialize)]
448 struct RoomSummaryDeHelper {
449 room_id: OwnedRoomId,
450 #[cfg_attr(
451 feature = "compat-empty-string-null",
452 serde(default, deserialize_with = "ruma_common::serde::empty_string_as_none")
453 )]
454 canonical_alias: Option<OwnedRoomAliasId>,
455 name: Option<String>,
456 topic: Option<String>,
457 #[cfg_attr(
458 feature = "compat-empty-string-null",
459 serde(default, deserialize_with = "ruma_common::serde::empty_string_as_none")
460 )]
461 avatar_url: Option<OwnedMxcUri>,
462 room_type: Option<RoomType>,
463 num_joined_members: UInt,
464 world_readable: bool,
465 guest_can_join: bool,
466 encryption: Option<EventEncryptionAlgorithm>,
467 room_version: Option<RoomVersionId>,
468 }
469
470 let json = Box::<RawJsonValue>::deserialize(deserializer)?;
471 let RoomSummaryDeHelper {
472 room_id,
473 canonical_alias,
474 name,
475 topic,
476 avatar_url,
477 room_type,
478 num_joined_members,
479 world_readable,
480 guest_can_join,
481 encryption,
482 room_version,
483 } = from_raw_json_value(&json)?;
484 let join_rule: JoinRuleSummary = from_raw_json_value(&json)?;
485
486 Ok(Self {
487 room_id,
488 canonical_alias,
489 name,
490 topic,
491 avatar_url,
492 room_type,
493 num_joined_members,
494 join_rule,
495 world_readable,
496 guest_can_join,
497 encryption,
498 room_version,
499 })
500 }
501}
502
503#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
517#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
518#[serde(tag = "join_rule", rename_all = "snake_case")]
519pub enum JoinRuleSummary {
520 Invite,
523
524 Knock,
528
529 Private,
531
532 Restricted(RestrictedSummary),
535
536 KnockRestricted(RestrictedSummary),
539
540 #[default]
542 Public,
543
544 #[doc(hidden)]
545 #[serde(skip_serializing)]
546 _Custom(PrivOwnedStr),
547}
548
549impl JoinRuleSummary {
550 pub fn kind(&self) -> JoinRuleKind {
552 match self {
553 Self::Invite => JoinRuleKind::Invite,
554 Self::Knock => JoinRuleKind::Knock,
555 Self::Private => JoinRuleKind::Private,
556 Self::Restricted(_) => JoinRuleKind::Restricted,
557 Self::KnockRestricted(_) => JoinRuleKind::KnockRestricted,
558 Self::Public => JoinRuleKind::Public,
559 Self::_Custom(rule) => JoinRuleKind::_Custom(rule.clone()),
560 }
561 }
562
563 pub fn as_str(&self) -> &str {
565 match self {
566 Self::Invite => "invite",
567 Self::Knock => "knock",
568 Self::Private => "private",
569 Self::Restricted(_) => "restricted",
570 Self::KnockRestricted(_) => "knock_restricted",
571 Self::Public => "public",
572 Self::_Custom(rule) => &rule.0,
573 }
574 }
575}
576
577impl From<JoinRule> for JoinRuleSummary {
578 fn from(value: JoinRule) -> Self {
579 match value {
580 JoinRule::Invite => Self::Invite,
581 JoinRule::Knock => Self::Knock,
582 JoinRule::Private => Self::Private,
583 JoinRule::Restricted(restricted) => Self::Restricted(restricted.into()),
584 JoinRule::KnockRestricted(restricted) => Self::KnockRestricted(restricted.into()),
585 JoinRule::Public => Self::Public,
586 JoinRule::_Custom(CustomJoinRule { join_rule, .. }) => {
587 Self::_Custom(PrivOwnedStr(join_rule.into()))
588 }
589 }
590 }
591}
592
593impl<'de> Deserialize<'de> for JoinRuleSummary {
594 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
595 where
596 D: de::Deserializer<'de>,
597 {
598 let json: Box<RawJsonValue> = Box::deserialize(deserializer)?;
599
600 #[derive(Deserialize)]
601 struct ExtractType<'a> {
602 #[serde(borrow)]
603 join_rule: Option<Cow<'a, str>>,
604 }
605
606 let Some(join_rule) = serde_json::from_str::<ExtractType<'_>>(json.get())
607 .map_err(de::Error::custom)?
608 .join_rule
609 else {
610 return Ok(Self::default());
611 };
612
613 match join_rule.as_ref() {
614 "invite" => Ok(Self::Invite),
615 "knock" => Ok(Self::Knock),
616 "private" => Ok(Self::Private),
617 "restricted" => from_raw_json_value(&json).map(Self::Restricted),
618 "knock_restricted" => from_raw_json_value(&json).map(Self::KnockRestricted),
619 "public" => Ok(Self::Public),
620 _ => Ok(Self::_Custom(PrivOwnedStr(join_rule.into()))),
621 }
622 }
623}
624
625#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
627#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
628pub struct RestrictedSummary {
629 #[serde(default)]
631 pub allowed_room_ids: Vec<OwnedRoomId>,
632}
633
634impl RestrictedSummary {
635 pub fn new(allowed_room_ids: Vec<OwnedRoomId>) -> Self {
637 Self { allowed_room_ids }
638 }
639}
640
641impl From<Restricted> for RestrictedSummary {
642 fn from(value: Restricted) -> Self {
643 let allowed_room_ids = value
644 .allow
645 .into_iter()
646 .filter_map(|allow_rule| {
647 let membership = as_variant!(allow_rule, AllowRule::RoomMembership)?;
648 Some(membership.room_id)
649 })
650 .collect();
651
652 Self::new(allowed_room_ids)
653 }
654}
655
656#[cfg(test)]
657mod tests {
658 use assert_matches::assert_matches;
659 use js_int::uint;
660 use ruma_common::{OwnedRoomId, owned_room_id};
661 use serde_json::{from_value as from_json_value, json};
662 use strass::assert_let;
663
664 use super::{
665 AllowRule, CustomAllowRule, JoinRule, JoinRuleSummary, Restricted, RestrictedSummary,
666 RoomMembership, RoomSummary,
667 };
668 use crate::{assert_to_canonical_json_eq, serde::JsonObject};
669
670 #[test]
671 fn deserialize_summary_no_join_rule() {
672 let json = json!({
673 "room_id": "!room:localhost",
674 "num_joined_members": 5,
675 "world_readable": false,
676 "guest_can_join": false,
677 });
678
679 let summary: RoomSummary = from_json_value(json).unwrap();
680 assert_eq!(summary.room_id, "!room:localhost");
681 assert_eq!(summary.num_joined_members, uint!(5));
682 assert!(!summary.world_readable);
683 assert!(!summary.guest_can_join);
684 assert_matches!(summary.join_rule, JoinRuleSummary::Public);
685 }
686
687 #[test]
688 fn deserialize_summary_private_join_rule() {
689 let json = json!({
690 "room_id": "!room:localhost",
691 "num_joined_members": 5,
692 "world_readable": false,
693 "guest_can_join": false,
694 "join_rule": "private",
695 });
696
697 let summary: RoomSummary = from_json_value(json).unwrap();
698 assert_eq!(summary.room_id, "!room:localhost");
699 assert_eq!(summary.num_joined_members, uint!(5));
700 assert!(!summary.world_readable);
701 assert!(!summary.guest_can_join);
702 assert_matches!(summary.join_rule, JoinRuleSummary::Private);
703 }
704
705 #[test]
706 fn deserialize_summary_restricted_join_rule() {
707 let json = json!({
708 "room_id": "!room:localhost",
709 "num_joined_members": 5,
710 "world_readable": false,
711 "guest_can_join": false,
712 "join_rule": "restricted",
713 "allowed_room_ids": ["!otherroom:localhost"],
714 });
715
716 let summary: RoomSummary = from_json_value(json).unwrap();
717 assert_eq!(summary.room_id, "!room:localhost");
718 assert_eq!(summary.num_joined_members, uint!(5));
719 assert!(!summary.world_readable);
720 assert!(!summary.guest_can_join);
721 assert_let!(JoinRuleSummary::Restricted(restricted) = summary.join_rule);
722 assert_eq!(restricted.allowed_room_ids.len(), 1);
723 }
724
725 #[test]
726 fn deserialize_summary_restricted_join_rule_no_allowed_room_ids() {
727 let json = json!({
728 "room_id": "!room:localhost",
729 "num_joined_members": 5,
730 "world_readable": false,
731 "guest_can_join": false,
732 "join_rule": "restricted",
733 });
734
735 let summary: RoomSummary = from_json_value(json).unwrap();
736 assert_eq!(summary.room_id, "!room:localhost");
737 assert_eq!(summary.num_joined_members, uint!(5));
738 assert!(!summary.world_readable);
739 assert!(!summary.guest_can_join);
740 assert_let!(JoinRuleSummary::Restricted(restricted) = summary.join_rule);
741 assert_eq!(restricted.allowed_room_ids.len(), 0);
742 }
743
744 #[test]
745 fn serialize_summary_knock_join_rule() {
746 let summary = RoomSummary::new(
747 owned_room_id!("!room:localhost"),
748 JoinRuleSummary::Knock,
749 false,
750 uint!(5),
751 false,
752 );
753
754 assert_to_canonical_json_eq!(
755 summary,
756 json!({
757 "room_id": "!room:localhost",
758 "num_joined_members": 5,
759 "world_readable": false,
760 "guest_can_join": false,
761 "join_rule": "knock",
762 })
763 );
764 }
765
766 #[test]
767 fn serialize_summary_restricted_join_rule() {
768 let summary = RoomSummary::new(
769 owned_room_id!("!room:localhost"),
770 JoinRuleSummary::Restricted(RestrictedSummary::new(vec![owned_room_id!(
771 "!otherroom:localhost"
772 )])),
773 false,
774 uint!(5),
775 false,
776 );
777
778 assert_to_canonical_json_eq!(
779 summary,
780 json!({
781 "room_id": "!room:localhost",
782 "num_joined_members": 5,
783 "world_readable": false,
784 "guest_can_join": false,
785 "join_rule": "restricted",
786 "allowed_room_ids": ["!otherroom:localhost"],
787 })
788 );
789 }
790
791 #[test]
792 fn custom_join_rule_serialize_roundtrip() {
793 let json = json!({
794 "join_rule": "local.dev.unicorns",
795 "rainbows": true,
796 });
797
798 let join_rule = from_json_value::<JoinRule>(json.clone()).unwrap();
799 assert_eq!(join_rule.kind().as_str(), "local.dev.unicorns");
800 let data = &*join_rule.data();
801 assert_eq!(data.len(), 1);
802 assert_eq!(data["rainbows"], true);
803
804 assert_to_canonical_json_eq!(join_rule, json);
805 }
806
807 #[test]
808 fn join_rule_to_join_rule_summary() {
809 assert_eq!(JoinRuleSummary::Invite, JoinRule::Invite.into());
810 assert_eq!(JoinRuleSummary::Knock, JoinRule::Knock.into());
811 assert_eq!(JoinRuleSummary::Public, JoinRule::Public.into());
812 assert_eq!(JoinRuleSummary::Private, JoinRule::Private.into());
813
814 assert_let!(
815 JoinRuleSummary::KnockRestricted(restricted) =
816 JoinRule::KnockRestricted(Restricted::default()).into()
817 );
818 assert_eq!(restricted.allowed_room_ids, &[] as &[OwnedRoomId]);
819
820 let room_id = owned_room_id!("!room:localhost");
821 assert_let!(
822 JoinRuleSummary::Restricted(restricted) =
823 JoinRule::Restricted(Restricted::new(vec![AllowRule::RoomMembership(
824 RoomMembership::new(room_id.clone())
825 )]))
826 .into()
827 );
828 assert_eq!(restricted.allowed_room_ids, [room_id]);
829 }
830
831 #[test]
832 fn roundtrip_custom_allow_rule() {
833 let json = json!({ "type": "org.msc9000.something", "foo": "bar"});
834
835 let allow_rule: AllowRule = from_json_value(json.clone()).unwrap();
836 assert_eq!(allow_rule.rule_type(), "org.msc9000.something");
837 let data = &*allow_rule.data();
838 assert_eq!(data.len(), 1);
839 assert_eq!(data["foo"], "bar");
840
841 assert_to_canonical_json_eq!(allow_rule, json);
842 }
843
844 #[test]
845 fn invalid_allow_items() {
846 let json = r#"{
847 "join_rule": "restricted",
848 "allow": [
849 {
850 "type": "m.room_membership",
851 "room_id": "!mods:example.org"
852 },
853 {
854 "type": "m.room_membership",
855 "room_id": ""
856 },
857 {
858 "type": "m.room_membership",
859 "room_id": "not a room id"
860 },
861 {
862 "type": "org.example.custom",
863 "org.example.minimum_role": "developer"
864 },
865 {
866 "not even close": "to being correct",
867 "any object": "passes this test",
868 "only non-objects in this array": "cause deserialization to fail"
869 }
870 ]
871 }"#;
872 let join_rule: JoinRule = serde_json::from_str(json).unwrap();
873
874 assert_let!(JoinRule::Restricted(restricted) = join_rule);
875 assert_eq!(
876 restricted.allow,
877 &[
878 AllowRule::room_membership(owned_room_id!("!mods:example.org")),
879 AllowRule::_Custom(CustomAllowRule {
880 rule_type: "org.example.custom".into(),
881 data: JsonObject::from_iter([(
882 "org.example.minimum_role".to_owned(),
883 "developer".into()
884 )]),
885 })
886 ]
887 );
888 }
889}