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_matches2::{assert_let, assert_matches};
659 use js_int::uint;
660 use ruma_common::{OwnedRoomId, owned_room_id};
661 use serde_json::{Value as JsonValue, from_value as from_json_value, json};
662
663 use super::{
664 AllowRule, CustomAllowRule, JoinRule, JoinRuleSummary, Restricted, RestrictedSummary,
665 RoomMembership, RoomSummary,
666 };
667 use crate::{assert_to_canonical_json_eq, serde::JsonObject};
668
669 #[test]
670 fn deserialize_summary_no_join_rule() {
671 let json = json!({
672 "room_id": "!room:localhost",
673 "num_joined_members": 5,
674 "world_readable": false,
675 "guest_can_join": false,
676 });
677
678 let summary: RoomSummary = from_json_value(json).unwrap();
679 assert_eq!(summary.room_id, "!room:localhost");
680 assert_eq!(summary.num_joined_members, uint!(5));
681 assert!(!summary.world_readable);
682 assert!(!summary.guest_can_join);
683 assert_matches!(summary.join_rule, JoinRuleSummary::Public);
684 }
685
686 #[test]
687 fn deserialize_summary_private_join_rule() {
688 let json = json!({
689 "room_id": "!room:localhost",
690 "num_joined_members": 5,
691 "world_readable": false,
692 "guest_can_join": false,
693 "join_rule": "private",
694 });
695
696 let summary: RoomSummary = from_json_value(json).unwrap();
697 assert_eq!(summary.room_id, "!room:localhost");
698 assert_eq!(summary.num_joined_members, uint!(5));
699 assert!(!summary.world_readable);
700 assert!(!summary.guest_can_join);
701 assert_matches!(summary.join_rule, JoinRuleSummary::Private);
702 }
703
704 #[test]
705 fn deserialize_summary_restricted_join_rule() {
706 let json = json!({
707 "room_id": "!room:localhost",
708 "num_joined_members": 5,
709 "world_readable": false,
710 "guest_can_join": false,
711 "join_rule": "restricted",
712 "allowed_room_ids": ["!otherroom:localhost"],
713 });
714
715 let summary: RoomSummary = from_json_value(json).unwrap();
716 assert_eq!(summary.room_id, "!room:localhost");
717 assert_eq!(summary.num_joined_members, uint!(5));
718 assert!(!summary.world_readable);
719 assert!(!summary.guest_can_join);
720 assert_matches!(summary.join_rule, JoinRuleSummary::Restricted(restricted));
721 assert_eq!(restricted.allowed_room_ids.len(), 1);
722 }
723
724 #[test]
725 fn deserialize_summary_restricted_join_rule_no_allowed_room_ids() {
726 let json = json!({
727 "room_id": "!room:localhost",
728 "num_joined_members": 5,
729 "world_readable": false,
730 "guest_can_join": false,
731 "join_rule": "restricted",
732 });
733
734 let summary: RoomSummary = from_json_value(json).unwrap();
735 assert_eq!(summary.room_id, "!room:localhost");
736 assert_eq!(summary.num_joined_members, uint!(5));
737 assert!(!summary.world_readable);
738 assert!(!summary.guest_can_join);
739 assert_matches!(summary.join_rule, JoinRuleSummary::Restricted(restricted));
740 assert_eq!(restricted.allowed_room_ids.len(), 0);
741 }
742
743 #[test]
744 fn serialize_summary_knock_join_rule() {
745 let summary = RoomSummary::new(
746 owned_room_id!("!room:localhost"),
747 JoinRuleSummary::Knock,
748 false,
749 uint!(5),
750 false,
751 );
752
753 assert_to_canonical_json_eq!(
754 summary,
755 json!({
756 "room_id": "!room:localhost",
757 "num_joined_members": 5,
758 "world_readable": false,
759 "guest_can_join": false,
760 "join_rule": "knock",
761 })
762 );
763 }
764
765 #[test]
766 fn serialize_summary_restricted_join_rule() {
767 let summary = RoomSummary::new(
768 owned_room_id!("!room:localhost"),
769 JoinRuleSummary::Restricted(RestrictedSummary::new(vec![owned_room_id!(
770 "!otherroom:localhost"
771 )])),
772 false,
773 uint!(5),
774 false,
775 );
776
777 assert_to_canonical_json_eq!(
778 summary,
779 json!({
780 "room_id": "!room:localhost",
781 "num_joined_members": 5,
782 "world_readable": false,
783 "guest_can_join": false,
784 "join_rule": "restricted",
785 "allowed_room_ids": ["!otherroom:localhost"],
786 })
787 );
788 }
789
790 #[test]
791 fn custom_join_rule_serialize_roundtrip() {
792 let json = json!({
793 "join_rule": "local.dev.unicorns",
794 "rainbows": true,
795 });
796
797 let join_rule = from_json_value::<JoinRule>(json.clone()).unwrap();
798 assert_eq!(join_rule.kind().as_str(), "local.dev.unicorns");
799 let data = &*join_rule.data();
800 assert_eq!(data.len(), 1);
801 assert_let!(Some(JsonValue::Bool(value)) = data.get("rainbows"));
802 assert!(value);
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_matches!(
815 JoinRule::KnockRestricted(Restricted::default()).into(),
816 JoinRuleSummary::KnockRestricted(restricted)
817 );
818 assert_eq!(restricted.allowed_room_ids, &[] as &[OwnedRoomId]);
819
820 let room_id = owned_room_id!("!room:localhost");
821 assert_matches!(
822 JoinRule::Restricted(Restricted::new(vec![AllowRule::RoomMembership(
823 RoomMembership::new(room_id.clone())
824 )]))
825 .into(),
826 JoinRuleSummary::Restricted(restricted)
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_let!(Some(JsonValue::String(value)) = data.get("foo"));
840 assert_eq!(value, "bar");
841
842 assert_to_canonical_json_eq!(allow_rule, json);
843 }
844
845 #[test]
846 fn invalid_allow_items() {
847 let json = r#"{
848 "join_rule": "restricted",
849 "allow": [
850 {
851 "type": "m.room_membership",
852 "room_id": "!mods:example.org"
853 },
854 {
855 "type": "m.room_membership",
856 "room_id": ""
857 },
858 {
859 "type": "m.room_membership",
860 "room_id": "not a room id"
861 },
862 {
863 "type": "org.example.custom",
864 "org.example.minimum_role": "developer"
865 },
866 {
867 "not even close": "to being correct",
868 "any object": "passes this test",
869 "only non-objects in this array": "cause deserialization to fail"
870 }
871 ]
872 }"#;
873 let join_rule: JoinRule = serde_json::from_str(json).unwrap();
874
875 assert_matches!(join_rule, JoinRule::Restricted(restricted));
876 assert_eq!(
877 restricted.allow,
878 &[
879 AllowRule::room_membership(owned_room_id!("!mods:example.org")),
880 AllowRule::_Custom(CustomAllowRule {
881 rule_type: "org.example.custom".into(),
882 data: JsonObject::from_iter([(
883 "org.example.minimum_role".to_owned(),
884 "developer".into()
885 )]),
886 })
887 ]
888 );
889 }
890}