├── .gitignore ├── src ├── key.rs ├── room │ ├── topic.rs │ ├── aliases.rs │ ├── tombstone.rs │ ├── redaction.rs │ ├── avatar.rs │ ├── encryption.rs │ ├── guest_access.rs │ ├── message │ │ └── feedback.rs │ ├── join_rules.rs │ ├── history_visibility.rs │ ├── third_party_invite.rs │ ├── pinned_events.rs │ ├── server_acl.rs │ ├── create.rs │ ├── canonical_alias.rs │ ├── encrypted.rs │ ├── power_levels.rs │ └── name.rs ├── tag.rs ├── key │ ├── verification │ │ ├── key.rs │ │ ├── mac.rs │ │ ├── request.rs │ │ ├── accept.rs │ │ └── cancel.rs │ └── verification.rs ├── call │ ├── answer.rs │ ├── invite.rs │ ├── candidates.rs │ └── hangup.rs ├── typing.rs ├── sticker.rs ├── fully_read.rs ├── call.rs ├── util.rs ├── from_raw.rs ├── dummy.rs ├── receipt.rs ├── forwarded_room_key.rs ├── room_key.rs ├── algorithm.rs ├── ignored_user_list.rs ├── room_key_request.rs ├── direct.rs ├── error.rs ├── json.rs ├── macros.rs ├── room.rs ├── presence.rs ├── collections │ ├── only.rs │ └── raw │ │ └── only.rs ├── custom.rs ├── lib.rs └── event_type.rs ├── .builds ├── msrv.yml ├── beta.yml ├── stable.yml └── nightly.yml ├── ruma-events-macros ├── README.md ├── CHANGELOG.md ├── Cargo.toml └── src │ ├── from_raw.rs │ ├── lib.rs │ └── parse.rs ├── README.md ├── Cargo.toml ├── LICENSE ├── tests └── ruma_events_macros.rs └── CHANGELOG.md /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target 3 | -------------------------------------------------------------------------------- /src/key.rs: -------------------------------------------------------------------------------- 1 | //! Modules for events in the *m.key* namespace. 2 | 3 | pub mod verification; 4 | -------------------------------------------------------------------------------- /src/room/topic.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.room.topic* event. 2 | 3 | use ruma_events_macros::ruma_event; 4 | 5 | ruma_event! { 6 | /// A topic is a short message detailing what is currently being discussed in the room. 7 | TopicEvent { 8 | kind: StateEvent, 9 | event_type: "m.room.topic", 10 | content: { 11 | /// The topic text. 12 | pub topic: String, 13 | }, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/room/aliases.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.room.aliases* event. 2 | 3 | use ruma_events_macros::ruma_event; 4 | use ruma_identifiers::RoomAliasId; 5 | 6 | ruma_event! { 7 | /// Informs the room about what room aliases it has been given. 8 | AliasesEvent { 9 | kind: StateEvent, 10 | event_type: "m.room.aliases", 11 | content: { 12 | /// A list of room aliases. 13 | pub aliases: Vec, 14 | }, 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.builds/msrv.yml: -------------------------------------------------------------------------------- 1 | image: archlinux 2 | packages: 3 | - rustup 4 | sources: 5 | - https://github.com/ruma/ruma-events 6 | tasks: 7 | - rustup: | 8 | # We specify --profile minimal because we'd otherwise download docs 9 | rustup toolchain install 1.40.0 --profile minimal 10 | rustup default 1.40.0 11 | - test: | 12 | cd ruma-events 13 | 14 | # Only make sure the code builds with the MSRV. Tests can require later 15 | # Rust versions, don't compile or run them. 16 | cargo build --verbose 17 | -------------------------------------------------------------------------------- /ruma-events-macros/README.md: -------------------------------------------------------------------------------- 1 | # ruma-events-macros 2 | 3 | [![Build Status](https://travis-ci.org/ruma/ruma-events-macros.svg?branch=master)](https://travis-ci.org/ruma/ruma-events-macros) 4 | 5 | **ruma-events-macros** provides a procedural macro for easily generating event types for [ruma-events](https://github.com/ruma/ruma-events). 6 | 7 | ## Documentation 8 | 9 | ruma-events-macros has [comprehensive documentation](https://docs.rs/ruma-events-macros) available on docs.rs. 10 | 11 | ## License 12 | 13 | [MIT](http://opensource.org/licenses/MIT) 14 | -------------------------------------------------------------------------------- /ruma-events-macros/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [unreleased] 2 | 3 | # 0.3.0 4 | 5 | Breaking changes: 6 | 7 | * Update `event_type` in `ruma_events!` to refer to the serialized form of the 8 | event type, not the variant of `ruma_events::EventType` 9 | 10 | Improvements: 11 | 12 | * Split `FromRaw` implementation generation from `ruma_event!` into a separate 13 | proc-macro 14 | 15 | # 0.2.0 16 | 17 | Improvements: 18 | 19 | * Code generation was updated to account for the changes in ruma-events 0.15 20 | * Dependencies were updated (notably to syn 1.0 and quote 1.0) 21 | -------------------------------------------------------------------------------- /src/room/tombstone.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.room.tombstone* event. 2 | 3 | use ruma_events_macros::ruma_event; 4 | use ruma_identifiers::RoomId; 5 | 6 | ruma_event! { 7 | /// A state event signifying that a room has been upgraded to a different room version, and that 8 | /// clients should go there. 9 | TombstoneEvent { 10 | kind: StateEvent, 11 | event_type: "m.room.tombstone", 12 | content: { 13 | /// A server-defined message. 14 | pub body: String, 15 | 16 | /// The new room the client should be visiting. 17 | pub replacement_room: RoomId, 18 | }, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/room/redaction.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.room.redaction* event. 2 | 3 | use ruma_events_macros::ruma_event; 4 | use ruma_identifiers::EventId; 5 | 6 | ruma_event! { 7 | /// A redaction of an event. 8 | RedactionEvent { 9 | kind: RoomEvent, 10 | event_type: "m.room.redaction", 11 | fields: { 12 | /// The ID of the event that was redacted. 13 | pub redacts: EventId, 14 | }, 15 | content: { 16 | /// The reason for the redaction, if any. 17 | #[serde(skip_serializing_if = "Option::is_none")] 18 | pub reason: Option, 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ruma-events 2 | 3 | [![crates.io page](https://img.shields.io/crates/v/ruma-events.svg)](https://crates.io/crates/ruma-events) 4 | [![docs.rs page](https://docs.rs/ruma-events/badge.svg)](https://docs.rs/ruma-events/) 5 | ![license: MIT](https://img.shields.io/crates/l/ruma-events.svg) 6 | 7 | **ruma-events** contains serializable types for the events in the [Matrix](https://matrix.org/) specification that can be shared by client and server code. 8 | 9 | ## Minimum Rust version 10 | 11 | ruma-events requires Rust 1.40.0 or later. 12 | 13 | ## Documentation 14 | 15 | ruma-events has [comprehensive documentation](https://docs.rs/ruma-events) available on docs.rs. 16 | -------------------------------------------------------------------------------- /src/room/avatar.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.room.avatar* event. 2 | 3 | use ruma_events_macros::ruma_event; 4 | 5 | use super::ImageInfo; 6 | 7 | ruma_event! { 8 | /// A picture that is associated with the room. 9 | /// 10 | /// This can be displayed alongside the room information. 11 | AvatarEvent { 12 | kind: StateEvent, 13 | event_type: "m.room.avatar", 14 | content: { 15 | /// Information about the avatar image. 16 | #[serde(skip_serializing_if = "Option::is_none")] 17 | pub info: Option, 18 | 19 | /// Information about the avatar thumbnail image. 20 | /// URL of the avatar image. 21 | pub url: String, 22 | }, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/tag.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.tag* event. 2 | 3 | use std::collections::BTreeMap; 4 | 5 | use ruma_events_macros::ruma_event; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | ruma_event! { 9 | /// Informs the client of tags on a room. 10 | TagEvent { 11 | kind: Event, 12 | event_type: "m.tag", 13 | content: { 14 | /// A map of tag names to tag info. 15 | pub tags: BTreeMap, 16 | }, 17 | } 18 | } 19 | 20 | /// Information about a tag. 21 | #[derive(Clone, Debug, Deserialize, Serialize)] 22 | pub struct TagInfo { 23 | /// Value to use for lexicographically ordering rooms with this tag. 24 | #[serde(skip_serializing_if = "Option::is_none")] 25 | pub order: Option, 26 | } 27 | -------------------------------------------------------------------------------- /src/key/verification/key.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.key.verification.key* event. 2 | 3 | use ruma_events_macros::ruma_event; 4 | 5 | ruma_event! { 6 | /// Sends the ephemeral public key for a device to the partner device. 7 | /// 8 | /// Typically sent as a to-device event. 9 | KeyEvent { 10 | kind: Event, 11 | event_type: "m.key.verification.key", 12 | content: { 13 | /// An opaque identifier for the verification process. 14 | /// 15 | /// Must be the same as the one used for the *m.key.verification.start* message. 16 | pub transaction_id: String, 17 | 18 | /// The device's ephemeral public key, encoded as unpadded Base64. 19 | pub key: String, 20 | }, 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/call/answer.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.call.answer* event. 2 | 3 | use js_int::UInt; 4 | use ruma_events_macros::ruma_event; 5 | 6 | use super::SessionDescription; 7 | 8 | ruma_event! { 9 | /// This event is sent by the callee when they wish to answer the call. 10 | AnswerEvent { 11 | kind: RoomEvent, 12 | event_type: "m.call.answer", 13 | content: { 14 | /// The VoIP session description object. The session description type must be *answer*. 15 | pub answer: SessionDescription, 16 | 17 | /// The ID of the call this event relates to. 18 | pub call_id: String, 19 | 20 | /// The version of the VoIP specification this messages adheres to. 21 | pub version: UInt, 22 | }, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/typing.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.typing* event. 2 | 3 | use ruma_events_macros::ruma_event; 4 | use ruma_identifiers::{RoomId, UserId}; 5 | 6 | ruma_event! { 7 | /// Informs the client of the list of users currently typing. 8 | TypingEvent { 9 | kind: Event, 10 | event_type: "m.typing", 11 | fields: { 12 | /// The unique identifier for the room associated with this event. 13 | /// 14 | /// `None` if the room is known through other means (such as this even being part of an 15 | /// event list scoped to a room in a `/sync` response) 16 | pub room_id: Option, 17 | }, 18 | content: { 19 | /// The list of user IDs typing in this room, if any. 20 | pub user_ids: Vec, 21 | }, 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.builds/beta.yml: -------------------------------------------------------------------------------- 1 | image: archlinux 2 | packages: 3 | - rustup 4 | sources: 5 | - https://github.com/ruma/ruma-events 6 | tasks: 7 | - rustup: | 8 | # We specify --profile minimal because we'd otherwise download docs 9 | rustup toolchain install beta --profile minimal -c rustfmt -c clippy 10 | rustup default beta 11 | - test: | 12 | cd ruma-events 13 | 14 | # We don't want the build to stop on individual failure of independent 15 | # tools, so capture tool exit codes and set the task exit code manually 16 | set +e 17 | 18 | cargo fmt -- --check 19 | fmt_exit=$? 20 | 21 | cargo clippy --all-targets --all-features -- -D warnings 22 | clippy_exit=$? 23 | 24 | cargo test --verbose 25 | test_exit=$? 26 | 27 | exit $(( $fmt_exit || $clippy_exit || $test_exit )) 28 | -------------------------------------------------------------------------------- /src/sticker.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.sticker* event. 2 | 3 | use ruma_events_macros::ruma_event; 4 | 5 | use crate::room::ImageInfo; 6 | 7 | ruma_event! { 8 | /// A sticker message. 9 | StickerEvent { 10 | kind: RoomEvent, 11 | event_type: "m.sticker", 12 | content: { 13 | /// A textual representation or associated description of the sticker image. This could 14 | /// be the alt text of the original image, or a message to accompany and further 15 | /// describe the sticker. 16 | pub body: String, 17 | 18 | /// Metadata about the image referred to in `url` including a thumbnail representation. 19 | pub info: ImageInfo, 20 | 21 | /// The URL to the sticker image. This must be a valid `mxc://` URI. 22 | pub url: String, 23 | }, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ruma-events-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jimmy Cuadra "] 3 | categories = ["api-bindings", "web-programming"] 4 | description = "A procedural macro used by the ruma-events crate." 5 | documentation = "https://docs.rs/ruma-events-macros" 6 | edition = "2018" 7 | homepage = "https://github.com/ruma/ruma-events-macros" 8 | keywords = ["matrix", "chat", "messaging", "ruma"] 9 | license = "MIT" 10 | name = "ruma-events-macros" 11 | readme = "README.md" 12 | repository = "https://github.com/ruma/ruma-api-macros" 13 | version = "0.21.3" 14 | 15 | [dependencies] 16 | syn = { version = "1.0.25", features = ["full"] } 17 | quote = "1.0.6" 18 | proc-macro2 = "1.0.17" 19 | 20 | [lib] 21 | proc-macro = true 22 | 23 | [dev-dependencies] 24 | ruma-identifiers = "0.16.1" 25 | serde_json = "1.0.53" 26 | js_int = { version = "0.1.5", features = ["serde"] } 27 | serde = { version = "1.0.110", features = ["derive"] } 28 | -------------------------------------------------------------------------------- /.builds/stable.yml: -------------------------------------------------------------------------------- 1 | image: archlinux 2 | packages: 3 | - rustup 4 | sources: 5 | - https://github.com/ruma/ruma-events 6 | tasks: 7 | - rustup: | 8 | # We specify --profile minimal because we'd otherwise download docs 9 | rustup toolchain install stable --profile minimal -c rustfmt -c clippy 10 | rustup default stable 11 | - test: | 12 | cd ruma-events 13 | 14 | # We don't want the build to stop on individual failure of independent 15 | # tools, so capture tool exit codes and set the task exit code manually 16 | set +e 17 | 18 | cargo fmt -- --check 19 | fmt_exit=$? 20 | 21 | cargo clippy --all-targets --all-features -- -D warnings 22 | clippy_exit=$? 23 | 24 | cargo test --verbose 25 | test_exit=$? 26 | 27 | exit $(( $fmt_exit || $clippy_exit || $test_exit )) 28 | # TODO: Add audit task once cargo-audit binary releases are available. 29 | # See https://github.com/RustSec/cargo-audit/issues/66 30 | -------------------------------------------------------------------------------- /src/fully_read.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.fully_read* event. 2 | 3 | use ruma_events_macros::ruma_event; 4 | use ruma_identifiers::{EventId, RoomId}; 5 | 6 | ruma_event! { 7 | /// The current location of the user's read marker in a room. 8 | /// 9 | /// This event appears in the user's room account data for the room the marker is applicable 10 | /// for. 11 | FullyReadEvent { 12 | kind: Event, 13 | event_type: "m.fully_read", 14 | fields: { 15 | /// The unique identifier for the room associated with this event. 16 | /// 17 | /// `None` if the room is known through other means (such as this even being part of an 18 | /// event list scoped to a room in a `/sync` response) 19 | pub room_id: Option, 20 | }, 21 | content: { 22 | /// The event the user's read marker is located at in the room. 23 | pub event_id: EventId, 24 | }, 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /.builds/nightly.yml: -------------------------------------------------------------------------------- 1 | image: archlinux 2 | packages: 3 | - rustup 4 | sources: 5 | - https://github.com/ruma/ruma-events 6 | tasks: 7 | - rustup: | 8 | rustup toolchain install nightly --profile minimal 9 | rustup default nightly 10 | 11 | # Try installing rustfmt & clippy for nightly, but don't fail the build 12 | # if they are not available 13 | rustup component add rustfmt || true 14 | rustup component add clippy || true 15 | - test: | 16 | cd ruma-events 17 | 18 | # We don't want the build to stop on individual failure of independent 19 | # tools, so capture tool exit codes and set the task exit code manually 20 | set +e 21 | 22 | if ( rustup component list | grep -q rustfmt ); then 23 | cargo fmt -- --check 24 | fi 25 | fmt_exit=$? 26 | 27 | if ( rustup component list | grep -q clippy ); then 28 | cargo clippy --all-targets --all-features -- -D warnings 29 | fi 30 | clippy_exit=$? 31 | 32 | exit $(( $fmt_exit || $clippy_exit )) 33 | -------------------------------------------------------------------------------- /src/room/encryption.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.room.encryption* event. 2 | 3 | use js_int::UInt; 4 | use ruma_events_macros::ruma_event; 5 | 6 | use crate::Algorithm; 7 | 8 | ruma_event! { 9 | /// Defines how messages sent in this room should be encrypted. 10 | EncryptionEvent { 11 | kind: StateEvent, 12 | event_type: "m.room.encryption", 13 | content: { 14 | /// The encryption algorithm to be used to encrypt messages sent in this room. 15 | /// 16 | /// Must be `m.megolm.v1.aes-sha2`. 17 | pub algorithm: Algorithm, 18 | 19 | /// How long the session should be used before changing it. 20 | /// 21 | /// 604800000 (a week) is the recommended default. 22 | pub rotation_period_ms: Option, 23 | 24 | /// How many messages should be sent before changing the session. 25 | /// 26 | /// 100 is the recommended default. 27 | pub rotation_period_msgs: Option, 28 | }, 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/call/invite.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.call.invite* event. 2 | 3 | use js_int::UInt; 4 | use ruma_events_macros::ruma_event; 5 | 6 | use super::SessionDescription; 7 | 8 | ruma_event! { 9 | /// This event is sent by the caller when they wish to establish a call. 10 | InviteEvent { 11 | kind: RoomEvent, 12 | event_type: "m.call.invite", 13 | content: { 14 | /// A unique identifer for the call. 15 | pub call_id: String, 16 | 17 | /// The time in milliseconds that the invite is valid for. Once the invite age exceeds this 18 | /// value, clients should discard it. They should also no longer show the call as awaiting an 19 | /// answer in the UI. 20 | pub lifetime: UInt, 21 | 22 | /// The session description object. The session description type must be *offer*. 23 | pub offer: SessionDescription, 24 | 25 | /// The version of the VoIP specification this messages adheres to. 26 | pub version: UInt, 27 | }, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/call.rs: -------------------------------------------------------------------------------- 1 | //! Modules for events in the *m.call* namespace. 2 | //! 3 | //! This module also contains types shared by events in its child namespaces. 4 | 5 | use serde::{Deserialize, Serialize}; 6 | 7 | pub mod answer; 8 | pub mod candidates; 9 | pub mod hangup; 10 | pub mod invite; 11 | 12 | /// A VoIP session description. 13 | #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] 14 | pub struct SessionDescription { 15 | /// The type of session description. 16 | #[serde(rename = "type")] 17 | pub session_type: SessionDescriptionType, 18 | 19 | /// The SDP text of the session description. 20 | pub sdp: String, 21 | } 22 | 23 | /// The type of VoIP session description. 24 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 25 | #[non_exhaustive] 26 | #[serde(rename_all = "lowercase")] 27 | pub enum SessionDescriptionType { 28 | /// An answer. 29 | Answer, 30 | 31 | /// An offer. 32 | Offer, 33 | } 34 | 35 | impl_enum! { 36 | SessionDescriptionType { 37 | Answer => "answer", 38 | Offer => "offer", 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Jimmy Cuadra "] 3 | categories = ["api-bindings"] 4 | description = "Serializable types for the events in the Matrix specification." 5 | documentation = "https://docs.rs/ruma-events" 6 | homepage = "https://github.com/ruma/ruma-events" 7 | keywords = ["matrix", "chat", "messaging", "ruma"] 8 | license = "MIT" 9 | name = "ruma-events" 10 | readme = "README.md" 11 | repository = "https://github.com/ruma/ruma-events" 12 | version = "0.21.3" 13 | edition = "2018" 14 | 15 | [dependencies] 16 | js_int = { version = "0.1.5", features = ["serde"] } 17 | ruma-common = "0.1.1" 18 | ruma-events-macros = { path = "ruma-events-macros", version = "=0.21.3" } 19 | ruma-identifiers = "0.16.1" 20 | ruma-serde = "0.2.1" 21 | serde = { version = "1.0.110", features = ["derive"] } 22 | serde_json = { version = "1.0.53", features = ["raw_value"] } 23 | 24 | [dev-dependencies] 25 | maplit = "1.0.2" 26 | matches = "0.1.8" 27 | ruma-identifiers = { version = "0.16.1", features = ["rand"] } 28 | 29 | [workspace] 30 | members = [ 31 | "ruma-events-macros", 32 | ] 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Jimmy Cuadra 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /src/key/verification/mac.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.key.verification.mac* event. 2 | 3 | use std::collections::BTreeMap; 4 | 5 | use ruma_events_macros::ruma_event; 6 | 7 | ruma_event! { 8 | /// Sends the MAC of a device's key to the partner device. 9 | /// 10 | /// Typically sent as a to-device event. 11 | MacEvent { 12 | kind: Event, 13 | event_type: "m.key.verification.mac", 14 | content: { 15 | /// An opaque identifier for the verification process. 16 | /// 17 | /// Must be the same as the one used for the *m.key.verification.start* message. 18 | pub transaction_id: String, 19 | 20 | /// A map of the key ID to the MAC of the key, using the algorithm in the verification process. 21 | /// 22 | /// The MAC is encoded as unpadded Base64. 23 | pub mac: BTreeMap, 24 | 25 | /// The MAC of the comma-separated, sorted, list of key IDs given in the `mac` property, encoded 26 | /// as unpadded Base64. 27 | pub keys: String, 28 | }, 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/room/guest_access.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.room.guest_access* event. 2 | 3 | use ruma_events_macros::ruma_event; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | ruma_event! { 7 | /// Controls whether guest users are allowed to join rooms. 8 | /// 9 | /// This event controls whether guest users are allowed to join rooms. If this event is absent, 10 | /// servers should act as if it is present and has the value `GuestAccess::Forbidden`. 11 | GuestAccessEvent { 12 | kind: StateEvent, 13 | event_type: "m.room.guest_access", 14 | content: { 15 | /// A policy for guest user access to a room. 16 | pub guest_access: GuestAccess, 17 | }, 18 | } 19 | } 20 | 21 | /// A policy for guest user access to a room. 22 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 23 | #[non_exhaustive] 24 | #[serde(rename_all = "snake_case")] 25 | pub enum GuestAccess { 26 | /// Guests are allowed to join the room. 27 | CanJoin, 28 | 29 | /// Guests are not allowed to join the room. 30 | Forbidden, 31 | } 32 | 33 | impl_enum! { 34 | GuestAccess { 35 | CanJoin => "can_join", 36 | Forbidden => "forbidden", 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use serde::de::DeserializeOwned; 2 | use serde_json::Value as JsonValue; 3 | 4 | use crate::TryFromRaw; 5 | 6 | pub fn try_convert_variant( 7 | variant: fn(Content) -> Enum, 8 | raw: Content::Raw, 9 | ) -> Result { 10 | Content::try_from_raw(raw) 11 | .map(variant) 12 | .map_err(|err| err.to_string()) 13 | } 14 | 15 | pub fn try_variant_from_value(value: JsonValue, variant: fn(T) -> U) -> Result 16 | where 17 | T: DeserializeOwned, 18 | E: serde::de::Error, 19 | { 20 | serde_json::from_value(value) 21 | .map(variant) 22 | .map_err(serde_json_error_to_generic_de_error) 23 | } 24 | 25 | pub fn serde_json_error_to_generic_de_error(error: serde_json::Error) -> E { 26 | E::custom(error.to_string()) 27 | } 28 | 29 | pub fn get_field(value: &JsonValue, field: &'static str) -> Result 30 | where 31 | T: DeserializeOwned, 32 | E: serde::de::Error, 33 | { 34 | serde_json::from_value( 35 | value 36 | .get(field) 37 | .cloned() 38 | .ok_or_else(|| E::missing_field(field))?, 39 | ) 40 | .map_err(serde_json_error_to_generic_de_error) 41 | } 42 | -------------------------------------------------------------------------------- /src/room/message/feedback.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.room.message.feedback* event. 2 | 3 | use ruma_events_macros::ruma_event; 4 | use ruma_identifiers::EventId; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | ruma_event! { 8 | /// An acknowledgement of a message. 9 | /// 10 | /// N.B.: Usage of this event is discouraged in favor of the receipts module. Most clients will 11 | /// not recognize this event. 12 | FeedbackEvent { 13 | kind: RoomEvent, 14 | event_type: "m.room.message.feedback", 15 | content: { 16 | /// The event that this feedback is related to. 17 | pub target_event_id: EventId, 18 | 19 | /// The type of feedback. 20 | #[serde(rename = "type")] 21 | pub feedback_type: FeedbackType, 22 | }, 23 | } 24 | } 25 | 26 | /// A type of feedback. 27 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 28 | #[serde(rename_all = "lowercase")] 29 | pub enum FeedbackType { 30 | /// Sent when a message is received. 31 | Delivered, 32 | 33 | /// Sent when a message has been observed by the end user. 34 | Read, 35 | } 36 | 37 | impl_enum! { 38 | FeedbackType { 39 | Delivered => "delivered", 40 | Read => "read", 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/room/join_rules.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.room.join_rules* event. 2 | 3 | use ruma_events_macros::ruma_event; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | ruma_event! { 7 | /// Describes how users are allowed to join the room. 8 | JoinRulesEvent { 9 | kind: StateEvent, 10 | event_type: "m.room.join_rules", 11 | content: { 12 | /// The type of rules used for users wishing to join this room. 13 | pub join_rule: JoinRule, 14 | }, 15 | } 16 | } 17 | 18 | /// The rule used for users wishing to join this room. 19 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 20 | #[serde(rename_all = "lowercase")] 21 | pub enum JoinRule { 22 | /// A user who wishes to join the room must first receive an invite to the room from someone 23 | /// already inside of the room. 24 | Invite, 25 | 26 | /// Reserved but not yet implemented by the Matrix specification. 27 | Knock, 28 | 29 | /// Reserved but not yet implemented by the Matrix specification. 30 | Private, 31 | 32 | /// Anyone can join the room without any prior action. 33 | Public, 34 | } 35 | 36 | impl_enum! { 37 | JoinRule { 38 | Invite => "invite", 39 | Knock => "knock", 40 | Private => "private", 41 | Public => "public", 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/call/candidates.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.call.candidates* event. 2 | 3 | use js_int::UInt; 4 | use ruma_events_macros::ruma_event; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | ruma_event! { 8 | /// This event is sent by callers after sending an invite and by the callee after answering. 9 | /// Its purpose is to give the other party additional ICE candidates to try using to 10 | /// communicate. 11 | CandidatesEvent { 12 | kind: RoomEvent, 13 | event_type: "m.call.candidates", 14 | content: { 15 | /// The ID of the call this event relates to. 16 | pub call_id: String, 17 | 18 | /// A list of candidates. 19 | pub candidates: Vec, 20 | 21 | /// The version of the VoIP specification this messages adheres to. 22 | pub version: UInt, 23 | }, 24 | } 25 | } 26 | 27 | /// An ICE (Interactive Connectivity Establishment) candidate. 28 | #[derive(Clone, Debug, Deserialize, Serialize)] 29 | #[serde(rename_all = "camelCase")] 30 | pub struct Candidate { 31 | /// The SDP "a" line of the candidate. 32 | pub candidate: String, 33 | 34 | /// The SDP media type this candidate is intended for. 35 | pub sdp_mid: String, 36 | 37 | /// The index of the SDP "m" line this candidate is intended for. 38 | pub sdp_m_line_index: UInt, 39 | } 40 | -------------------------------------------------------------------------------- /src/key/verification/request.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.key.verification.request* event. 2 | 3 | use std::time::SystemTime; 4 | 5 | use ruma_events_macros::ruma_event; 6 | use ruma_identifiers::DeviceId; 7 | 8 | use super::VerificationMethod; 9 | 10 | ruma_event! { 11 | /// Requests a key verification with another user's devices. 12 | /// 13 | /// Typically sent as a to-device event. 14 | RequestEvent { 15 | kind: Event, 16 | event_type: "m.key.verification.request", 17 | content: { 18 | /// The device ID which is initiating the request. 19 | pub from_device: DeviceId, 20 | 21 | /// An opaque identifier for the verification request. 22 | /// 23 | /// Must be unique with respect to the devices involved. 24 | pub transaction_id: String, 25 | 26 | /// The verification methods supported by the sender. 27 | pub methods: Vec, 28 | 29 | /// The time in milliseconds for when the request was made. 30 | /// 31 | /// If the request is in the future by more than 5 minutes or more than 10 minutes in 32 | /// the past, the message should be ignored by the receiver. 33 | #[serde(with = "ruma_serde::time::ms_since_unix_epoch")] 34 | pub timestamp: SystemTime, 35 | }, 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/call/hangup.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.call.hangup* event. 2 | 3 | use js_int::UInt; 4 | use ruma_events_macros::ruma_event; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | ruma_event! { 8 | /// Sent by either party to signal their termination of the call. This can be sent either once 9 | /// the call has has been established or before to abort the call. 10 | HangupEvent { 11 | kind: RoomEvent, 12 | event_type: "m.call.hangup", 13 | content: { 14 | /// The ID of the call this event relates to. 15 | pub call_id: String, 16 | 17 | /// The version of the VoIP specification this messages adheres to. 18 | pub version: UInt, 19 | 20 | /// Optional error reason for the hangup. 21 | #[serde(skip_serializing_if = "Option::is_none")] 22 | pub reason: Option, 23 | }, 24 | } 25 | } 26 | 27 | /// A reason for a hangup. 28 | /// 29 | /// This should not be provided when the user naturally ends or rejects the call. When there was an 30 | /// error in the call negotiation, this should be `ice_failed` for when ICE negotiation fails or 31 | /// `invite_timeout` for when the other party did not answer in time. 32 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 33 | #[serde(rename_all = "snake_case")] 34 | pub enum Reason { 35 | /// ICE negotiation failure. 36 | IceFailed, 37 | 38 | /// Party did not answer in time. 39 | InviteTimeout, 40 | } 41 | 42 | impl_enum! { 43 | Reason { 44 | IceFailed => "ice_failed", 45 | InviteTimeout => "invite_timeout", 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /ruma-events-macros/src/from_raw.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of the `FromRaw` derive macro 2 | 3 | use proc_macro2::TokenStream; 4 | use quote::{quote, quote_spanned}; 5 | use syn::{spanned::Spanned, Data, DeriveInput, Fields}; 6 | 7 | /// Create a `FromRaw` implementation for a struct 8 | pub fn expand_from_raw(input: DeriveInput) -> syn::Result { 9 | let fields = match input.data { 10 | Data::Struct(s) => match s.fields { 11 | Fields::Named(fs) => fs.named, 12 | _ => panic!("#[derive(FromRaw)] only supports structs with named fields!"), 13 | }, 14 | _ => panic!("#[derive(FromRaw)] only supports structs!"), 15 | }; 16 | let ident = &input.ident; 17 | 18 | let init_list = fields.iter().map(|field| { 19 | let field_ident = field.ident.as_ref().unwrap(); 20 | let field_span = field.span(); 21 | 22 | if field_ident == "content" { 23 | quote_spanned! {field_span=> 24 | content: ::ruma_events::FromRaw::from_raw(raw.content), 25 | } 26 | } else if field_ident == "prev_content" { 27 | quote_spanned! {field_span=> 28 | prev_content: raw.prev_content.map(::ruma_events::FromRaw::from_raw), 29 | } 30 | } else { 31 | quote_spanned! {field_span=> 32 | #field_ident: raw.#field_ident, 33 | } 34 | } 35 | }); 36 | 37 | Ok(quote! { 38 | impl ::ruma_events::FromRaw for #ident { 39 | type Raw = raw::#ident; 40 | 41 | fn from_raw(raw: raw::#ident) -> Self { 42 | Self { 43 | #(#init_list)* 44 | } 45 | } 46 | } 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /src/room/history_visibility.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.room.history_visibility* event. 2 | 3 | use ruma_events_macros::ruma_event; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | ruma_event! { 7 | /// This event controls whether a member of a room can see the events that happened in a room 8 | /// from before they joined. 9 | HistoryVisibilityEvent { 10 | kind: StateEvent, 11 | event_type: "m.room.history_visibility", 12 | content: { 13 | /// Who can see the room history. 14 | pub history_visibility: HistoryVisibility, 15 | }, 16 | } 17 | } 18 | 19 | /// Who can see a room's history. 20 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 21 | #[serde(rename_all = "snake_case")] 22 | pub enum HistoryVisibility { 23 | /// Previous events are accessible to newly joined members from the point they were invited 24 | /// onwards. Events stop being accessible when the member's state changes to something other 25 | /// than *invite* or *join*. 26 | Invited, 27 | 28 | /// Previous events are accessible to newly joined members from the point they joined the room 29 | /// onwards. Events stop being accessible when the member's state changes to something other 30 | /// than *join*. 31 | Joined, 32 | 33 | /// Previous events are always accessible to newly joined members. All events in the room are 34 | /// accessible, even those sent when the member was not a part of the room. 35 | Shared, 36 | 37 | /// All events while this is the `HistoryVisibility` value may be shared by any 38 | /// participating homeserver with anyone, regardless of whether they have ever joined the room. 39 | WorldReadable, 40 | } 41 | 42 | impl_enum! { 43 | HistoryVisibility { 44 | Invited => "invited", 45 | Joined => "joined", 46 | Shared => "shared", 47 | WorldReadable => "world_readable", 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/from_raw.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::BTreeMap, convert::Infallible, fmt::Display}; 2 | 3 | use serde::de::DeserializeOwned; 4 | 5 | /// See [`TryFromRaw`][try]. This trait is merely a convenience that is be implemented instead of 6 | /// [`TryFromRaw`][try] to get a [`TryFromRaw`][try] implementation with slightly less code if the 7 | /// conversion can't fail, that is, the raw type and `Self` are identical in definition. 8 | /// 9 | /// [try]: trait.TryFromRaw.html 10 | pub trait FromRaw: Sized { 11 | /// The raw type. 12 | type Raw: DeserializeOwned; 13 | 14 | /// Converts the raw type to `Self`. 15 | fn from_raw(_: Self::Raw) -> Self; 16 | } 17 | 18 | /// Types corresponding to some item in the matrix spec. Types that implement this trait have a 19 | /// corresponding 'raw' type, a potentially invalid representation that can be converted to `Self`. 20 | pub trait TryFromRaw: Sized { 21 | /// The raw type. 22 | type Raw: DeserializeOwned; 23 | /// The error type returned if conversion fails. 24 | type Err: Display; 25 | 26 | /// Tries to convert the raw type to `Self`. 27 | fn try_from_raw(_: Self::Raw) -> Result; 28 | } 29 | 30 | impl FromRaw for ruma_serde::empty::Empty { 31 | type Raw = Self; 32 | 33 | fn from_raw(raw: Self) -> Self { 34 | raw 35 | } 36 | } 37 | 38 | impl FromRaw for serde_json::Value { 39 | type Raw = Self; 40 | 41 | fn from_raw(raw: Self) -> Self { 42 | raw 43 | } 44 | } 45 | 46 | impl FromRaw for BTreeMap 47 | where 48 | Self: DeserializeOwned, 49 | { 50 | type Raw = Self; 51 | 52 | fn from_raw(raw: Self) -> Self { 53 | raw 54 | } 55 | } 56 | 57 | impl TryFromRaw for T { 58 | type Raw = ::Raw; 59 | type Err = Infallible; 60 | 61 | fn try_from_raw(raw: Self::Raw) -> Result { 62 | Ok(Self::from_raw(raw)) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/dummy.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.dummy* event. 2 | 3 | use ruma_events_macros::ruma_event; 4 | use ruma_serde::empty::Empty; 5 | 6 | ruma_event! { 7 | /// This event type is used to indicate new Olm sessions for end-to-end encryption. 8 | /// 9 | /// Typically it is encrypted as an *m.room.encrypted* event, then sent as a to-device event. 10 | /// 11 | /// The event does not have any content associated with it. The sending client is expected to 12 | /// send a key share request shortly after this message, causing the receiving client to process 13 | /// this *m.dummy* event as the most recent event and using the keyshare request to set up the 14 | /// session. The keyshare request and *m.dummy* combination should result in the original 15 | /// sending client receiving keys over the newly established session. 16 | DummyEvent { 17 | kind: Event, 18 | event_type: "m.dummy", 19 | content_type_alias: { 20 | /// The payload for `DummyEvent`. 21 | Empty 22 | } 23 | } 24 | } 25 | 26 | #[cfg(test)] 27 | mod tests { 28 | use super::{DummyEvent, Empty}; 29 | use crate::EventJson; 30 | 31 | use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; 32 | 33 | #[test] 34 | fn serialization() { 35 | let dummy_event = DummyEvent { content: Empty }; 36 | let actual = to_json_value(dummy_event).unwrap(); 37 | 38 | let expected = json!({ 39 | "content": {}, 40 | "type": "m.dummy" 41 | }); 42 | 43 | assert_eq!(actual, expected); 44 | } 45 | 46 | #[test] 47 | fn deserialization() { 48 | let json = json!({ 49 | "content": {}, 50 | "type": "m.dummy" 51 | }); 52 | 53 | assert!(from_json_value::>(json) 54 | .unwrap() 55 | .deserialize() 56 | .is_ok()); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/receipt.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.receipt* event. 2 | 3 | use std::{collections::BTreeMap, time::SystemTime}; 4 | 5 | use ruma_events_macros::ruma_event; 6 | use ruma_identifiers::{EventId, RoomId, UserId}; 7 | use serde::{Deserialize, Serialize}; 8 | 9 | ruma_event! { 10 | /// Informs the client of new receipts. 11 | ReceiptEvent { 12 | kind: Event, 13 | event_type: "m.receipt", 14 | fields: { 15 | /// The unique identifier for the room associated with this event. 16 | /// 17 | /// `None` if the room is known through other means (such as this even being part of an 18 | /// event list scoped to a room in a `/sync` response) 19 | pub room_id: Option, 20 | }, 21 | content_type_alias: { 22 | /// The payload for `ReceiptEvent`. 23 | /// 24 | /// A mapping of event ID to a collection of receipts for this event ID. The event ID is the ID of 25 | /// the event being acknowledged and *not* an ID for the receipt itself. 26 | BTreeMap 27 | }, 28 | } 29 | } 30 | 31 | /// A collection of receipts. 32 | #[derive(Clone, Debug, Deserialize, Serialize)] 33 | pub struct Receipts { 34 | /// A collection of users who have sent *m.read* receipts for this event. 35 | #[serde(default, rename = "m.read")] 36 | pub read: Option, 37 | } 38 | 39 | /// A mapping of user ID to receipt. 40 | /// 41 | /// The user ID is the entity who sent this receipt. 42 | pub type UserReceipts = BTreeMap; 43 | 44 | /// An acknowledgement of an event. 45 | #[derive(Clone, Debug, Deserialize, Serialize)] 46 | pub struct Receipt { 47 | /// The time when the receipt was sent. 48 | #[serde( 49 | with = "ruma_serde::time::opt_ms_since_unix_epoch", 50 | default, 51 | skip_serializing_if = "Option::is_none" 52 | )] 53 | pub ts: Option, 54 | } 55 | -------------------------------------------------------------------------------- /src/room/third_party_invite.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.room.third_party_invite* event. 2 | 3 | use ruma_events_macros::ruma_event; 4 | use serde::{Deserialize, Serialize}; 5 | 6 | ruma_event! { 7 | /// An invitation to a room issued to a third party identifier, rather than a matrix user ID. 8 | /// 9 | /// Acts as an *m.room.member* invite event, where there isn't a target user_id to invite. This 10 | /// event contains a token and a public key whose private key must be used to sign the token. 11 | /// Any user who can present that signature may use this invitation to join the target room. 12 | ThirdPartyInviteEvent { 13 | kind: StateEvent, 14 | event_type: "m.room.third_party_invite", 15 | content: { 16 | /// A user-readable string which represents the user who has been invited. 17 | pub display_name: String, 18 | 19 | /// A URL which can be fetched to validate whether the key has been revoked. 20 | pub key_validity_url: String, 21 | 22 | /// A Base64-encoded Ed25519 key with which the token must be signed. 23 | pub public_key: String, 24 | 25 | /// Keys with which the token may be signed. 26 | #[serde(skip_serializing_if = "Option::is_none")] 27 | pub public_keys: Option>, 28 | }, 29 | } 30 | } 31 | 32 | /// A public key for signing a third party invite token. 33 | #[derive(Clone, Debug, Deserialize, Serialize)] 34 | pub struct PublicKey { 35 | /// An optional URL which can be fetched to validate whether the key has been revoked. 36 | /// 37 | /// The URL must return a JSON object containing a boolean property named 'valid'. 38 | /// If this URL is absent, the key must be considered valid indefinitely. 39 | #[serde(skip_serializing_if = "Option::is_none")] 40 | pub key_validity_url: Option, 41 | 42 | /// A Base64-encoded Ed25519 key with which the token must be signed. 43 | pub public_key: String, 44 | } 45 | -------------------------------------------------------------------------------- /src/forwarded_room_key.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.forwarded_room_key* event. 2 | 3 | use ruma_events_macros::ruma_event; 4 | use ruma_identifiers::RoomId; 5 | 6 | use super::Algorithm; 7 | 8 | ruma_event! { 9 | /// This event type is used to forward keys for end-to-end encryption. 10 | /// 11 | /// Typically it is encrypted as an *m.room.encrypted* event, then sent as a to-device event. 12 | ForwardedRoomKeyEvent { 13 | kind: Event, 14 | event_type: "m.forwarded_room_key", 15 | content: { 16 | /// The encryption algorithm the key in this event is to be used with. 17 | pub algorithm: Algorithm, 18 | 19 | /// The room where the key is used. 20 | pub room_id: RoomId, 21 | 22 | /// The Curve25519 key of the device which initiated the session originally. 23 | pub sender_key: String, 24 | 25 | /// The ID of the session that the key is for. 26 | pub session_id: String, 27 | 28 | /// The key to be exchanged. 29 | pub session_key: String, 30 | 31 | /// The Ed25519 key of the device which initiated the session originally. 32 | /// 33 | /// It is "claimed" because the receiving device has no way to tell that the original 34 | /// room_key actually came from a device which owns the private part of this key unless 35 | /// they have done device verification. 36 | pub sender_claimed_ed25519_key: String, 37 | 38 | /// Chain of Curve25519 keys. 39 | /// 40 | /// It starts out empty, but each time the key is forwarded to another device, the 41 | /// previous sender in the chain is added to the end of the list. For example, if the 42 | /// key is forwarded from A to B to C, this field is empty between A and B, and contains 43 | /// A's Curve25519 key between B and C. 44 | pub forwarding_curve25519_key_chain: Vec, 45 | }, 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/room_key.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.room_key* event. 2 | 3 | use ruma_events_macros::ruma_event; 4 | use ruma_identifiers::RoomId; 5 | 6 | use super::Algorithm; 7 | 8 | ruma_event! { 9 | /// This event type is used to exchange keys for end-to-end encryption. 10 | /// 11 | /// Typically it is encrypted as an *m.room.encrypted* event, then sent as a to-device event. 12 | RoomKeyEvent { 13 | kind: Event, 14 | event_type: "m.room_key", 15 | content: { 16 | /// The encryption algorithm the key in this event is to be used with. 17 | /// 18 | /// Must be `m.megolm.v1.aes-sha2`. 19 | pub algorithm: Algorithm, 20 | 21 | /// The room where the key is used. 22 | pub room_id: RoomId, 23 | 24 | /// The ID of the session that the key is for. 25 | pub session_id: String, 26 | 27 | /// The key to be exchanged. 28 | pub session_key: String, 29 | } 30 | } 31 | } 32 | 33 | #[cfg(test)] 34 | mod tests { 35 | use std::convert::TryFrom; 36 | 37 | use ruma_identifiers::RoomId; 38 | use serde_json::{json, to_value as to_json_value}; 39 | 40 | use super::{RoomKeyEvent, RoomKeyEventContent}; 41 | use crate::Algorithm; 42 | 43 | #[test] 44 | fn serialization() { 45 | let ev = RoomKeyEvent { 46 | content: RoomKeyEventContent { 47 | algorithm: Algorithm::MegolmV1AesSha2, 48 | room_id: RoomId::try_from("!testroomid:example.org").unwrap(), 49 | session_id: "SessId".into(), 50 | session_key: "SessKey".into(), 51 | }, 52 | }; 53 | 54 | assert_eq!( 55 | to_json_value(ev).unwrap(), 56 | json!({ 57 | "type": "m.room_key", 58 | "content": { 59 | "algorithm": "m.megolm.v1.aes-sha2", 60 | "room_id": "!testroomid:example.org", 61 | "session_id": "SessId", 62 | "session_key": "SessKey", 63 | }, 64 | }) 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/algorithm.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter, Result as FmtResult}; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// An encryption algorithm to be used to encrypt messages sent to a room. 6 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 7 | #[non_exhaustive] 8 | #[serde(from = "String", into = "String")] 9 | pub enum Algorithm { 10 | /// Olm version 1 using Curve25519, AES-256, and SHA-256. 11 | OlmV1Curve25519AesSha2, 12 | 13 | /// Megolm version 1 using AES-256 and SHA-256. 14 | MegolmV1AesSha2, 15 | 16 | /// Any algorithm that is not part of the specification. 17 | Custom(String), 18 | } 19 | 20 | impl Display for Algorithm { 21 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 22 | let algorithm_str = match *self { 23 | Algorithm::OlmV1Curve25519AesSha2 => "m.olm.v1.curve25519-aes-sha2", 24 | Algorithm::MegolmV1AesSha2 => "m.megolm.v1.aes-sha2", 25 | Algorithm::Custom(ref algorithm) => algorithm, 26 | }; 27 | 28 | write!(f, "{}", algorithm_str) 29 | } 30 | } 31 | 32 | impl From for Algorithm 33 | where 34 | T: Into + AsRef, 35 | { 36 | fn from(s: T) -> Algorithm { 37 | match s.as_ref() { 38 | "m.olm.v1.curve25519-aes-sha2" => Algorithm::OlmV1Curve25519AesSha2, 39 | "m.megolm.v1.aes-sha2" => Algorithm::MegolmV1AesSha2, 40 | _ => Algorithm::Custom(s.into()), 41 | } 42 | } 43 | } 44 | 45 | impl From for String { 46 | fn from(algorithm: Algorithm) -> String { 47 | algorithm.to_string() 48 | } 49 | } 50 | 51 | #[cfg(test)] 52 | mod tests { 53 | use ruma_serde::test::serde_json_eq; 54 | use serde_json::json; 55 | 56 | use super::*; 57 | 58 | #[test] 59 | fn serialize_and_deserialize_from_display_form() { 60 | serde_json_eq(Algorithm::MegolmV1AesSha2, json!("m.megolm.v1.aes-sha2")); 61 | serde_json_eq( 62 | Algorithm::OlmV1Curve25519AesSha2, 63 | json!("m.olm.v1.curve25519-aes-sha2"), 64 | ); 65 | serde_json_eq( 66 | Algorithm::Custom("io.ruma.test".to_string()), 67 | json!("io.ruma.test"), 68 | ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/key/verification/accept.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.key.verification.accept* event. 2 | 3 | use ruma_events_macros::ruma_event; 4 | 5 | use super::{ 6 | HashAlgorithm, KeyAgreementProtocol, MessageAuthenticationCode, ShortAuthenticationString, 7 | VerificationMethod, 8 | }; 9 | 10 | ruma_event! { 11 | /// Accepts a previously sent *m.key.verification.start* messge. 12 | /// 13 | /// Typically sent as a to-device event. 14 | AcceptEvent { 15 | kind: Event, 16 | event_type: "m.key.verification.accept", 17 | content: { 18 | /// An opaque identifier for the verification process. 19 | /// 20 | /// Must be the same as the one used for the *m.key.verification.start* message. 21 | pub transaction_id: String, 22 | 23 | /// The verification method to use. 24 | /// 25 | /// Must be `m.sas.v1`. 26 | pub method: VerificationMethod, 27 | 28 | /// The key agreement protocol the device is choosing to use, out of the options in the 29 | /// *m.key.verification.start* message. 30 | pub key_agreement_protocol: KeyAgreementProtocol, 31 | 32 | /// The hash method the device is choosing to use, out of the options in the 33 | /// *m.key.verification.start* message. 34 | pub hash: HashAlgorithm, 35 | 36 | /// The message authentication code the device is choosing to use, out of the options in the 37 | /// *m.key.verification.start* message. 38 | pub message_authentication_code: MessageAuthenticationCode, 39 | 40 | /// The SAS methods both devices involved in the verification process understand. 41 | /// 42 | /// Must be a subset of the options in the *m.key.verification.start* message. 43 | pub short_authentication_string: Vec, 44 | 45 | /// The hash (encoded as unpadded base64) of the concatenation of the device's ephemeral public 46 | /// key (encoded as unpadded base64) and the canonical JSON representation of the 47 | /// *m.key.verification.start* message. 48 | pub commitment: String, 49 | }, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/ignored_user_list.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.ignored_user_list* event. 2 | 3 | use ruma_events_macros::ruma_event; 4 | use ruma_identifiers::UserId; 5 | 6 | ruma_event! { 7 | /// A list of users to ignore. 8 | IgnoredUserListEvent { 9 | kind: Event, 10 | event_type: "m.ignored_user_list", 11 | content: { 12 | /// A list of users to ignore. 13 | #[serde(with = "ruma_serde::vec_as_map_of_empty")] 14 | pub ignored_users: Vec, 15 | }, 16 | } 17 | } 18 | 19 | #[cfg(test)] 20 | mod tests { 21 | use std::convert::TryFrom; 22 | 23 | use matches::assert_matches; 24 | use ruma_identifiers::UserId; 25 | use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; 26 | 27 | use super::{IgnoredUserListEvent, IgnoredUserListEventContent}; 28 | use crate::EventJson; 29 | 30 | #[test] 31 | fn serialization() { 32 | let ignored_user_list_event = IgnoredUserListEvent { 33 | content: IgnoredUserListEventContent { 34 | ignored_users: vec![UserId::try_from("@carl:example.com").unwrap()], 35 | }, 36 | }; 37 | 38 | let json = json!({ 39 | "content": { 40 | "ignored_users": { 41 | "@carl:example.com": {} 42 | } 43 | }, 44 | "type": "m.ignored_user_list" 45 | }); 46 | 47 | assert_eq!(to_json_value(ignored_user_list_event).unwrap(), json); 48 | } 49 | 50 | #[test] 51 | fn deserialization() { 52 | let json = json!({ 53 | "content": { 54 | "ignored_users": { 55 | "@carl:example.com": {} 56 | } 57 | }, 58 | "type": "m.ignored_user_list" 59 | }); 60 | 61 | assert_matches!( 62 | from_json_value::>(json) 63 | .unwrap() 64 | .deserialize() 65 | .unwrap(), 66 | IgnoredUserListEvent { 67 | content: IgnoredUserListEventContent { ignored_users, }, 68 | } if ignored_users == vec![UserId::try_from("@carl:example.com").unwrap()] 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/key/verification.rs: -------------------------------------------------------------------------------- 1 | //! Modules for events in the *m.key.verification* namespace. 2 | //! 3 | //! This module also contains types shared by events in its child namespaces. 4 | 5 | use serde::{Deserialize, Serialize}; 6 | 7 | pub mod accept; 8 | pub mod cancel; 9 | pub mod key; 10 | pub mod mac; 11 | pub mod request; 12 | pub mod start; 13 | 14 | /// A hash algorithm. 15 | #[derive(Clone, Copy, Debug, Serialize, PartialEq, Deserialize)] 16 | pub enum HashAlgorithm { 17 | /// The SHA256 hash algorithm. 18 | #[serde(rename = "sha256")] 19 | Sha256, 20 | } 21 | 22 | impl_enum! { 23 | HashAlgorithm { 24 | Sha256 => "sha256", 25 | } 26 | } 27 | 28 | /// A key agreement protocol. 29 | #[derive(Clone, Copy, Debug, Serialize, PartialEq, Deserialize)] 30 | pub enum KeyAgreementProtocol { 31 | /// The [Curve25519](https://cr.yp.to/ecdh.html) key agreement protocol. 32 | #[serde(rename = "curve25519")] 33 | Curve25519, 34 | } 35 | 36 | impl_enum! { 37 | KeyAgreementProtocol { 38 | Curve25519 => "curve25519", 39 | } 40 | } 41 | 42 | /// A message authentication code algorithm. 43 | #[derive(Clone, Copy, Debug, Serialize, PartialEq, Deserialize)] 44 | pub enum MessageAuthenticationCode { 45 | /// The HKDF-HMAC-SHA256 MAC. 46 | #[serde(rename = "hkdf-hmac-sha256")] 47 | HkdfHmacSha256, 48 | } 49 | 50 | impl_enum! { 51 | MessageAuthenticationCode { 52 | HkdfHmacSha256 => "hkdf-hmac-sha256", 53 | } 54 | } 55 | 56 | /// A Short Authentication String method. 57 | #[derive(Clone, Copy, Debug, Serialize, PartialEq, Deserialize)] 58 | pub enum ShortAuthenticationString { 59 | /// The decimal method. 60 | #[serde(rename = "decimal")] 61 | Decimal, 62 | 63 | /// The emoji method. 64 | #[serde(rename = "emoji")] 65 | Emoji, 66 | } 67 | 68 | impl_enum! { 69 | ShortAuthenticationString { 70 | Decimal => "decimal", 71 | Emoji => "emoji", 72 | } 73 | } 74 | 75 | /// A Short Authentication String (SAS) verification method. 76 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 77 | pub enum VerificationMethod { 78 | /// The *m.sas.v1* verification method. 79 | #[serde(rename = "m.sas.v1")] 80 | MSasV1, 81 | } 82 | 83 | impl_enum! { 84 | VerificationMethod { 85 | MSasV1 => "m.sas.v1", 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/room_key_request.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.room_key_request* event. 2 | 3 | use ruma_events_macros::ruma_event; 4 | use ruma_identifiers::{DeviceId, RoomId}; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | use super::Algorithm; 8 | 9 | ruma_event! { 10 | /// This event type is used to request keys for end-to-end encryption. 11 | /// 12 | /// It is sent as an unencrypted to-device event. 13 | RoomKeyRequestEvent { 14 | kind: Event, 15 | event_type: "m.room_key_request", 16 | content: { 17 | /// Whether this is a new key request or a cancellation of a previous request. 18 | pub action: Action, 19 | 20 | /// Information about the requested key. 21 | /// 22 | /// Required when action is `request`. 23 | pub body: Option, 24 | 25 | /// ID of the device requesting the key. 26 | pub requesting_device_id: DeviceId, 27 | 28 | /// A random string uniquely identifying the request for a key. 29 | /// 30 | /// If the key is requested multiple times, it should be reused. It should also reused 31 | /// in order to cancel a request. 32 | pub request_id: String, 33 | }, 34 | } 35 | } 36 | 37 | /// A new key request or a cancellation of a previous request. 38 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 39 | pub enum Action { 40 | /// Request a key. 41 | #[serde(rename = "request")] 42 | Request, 43 | 44 | /// Cancel a request for a key. 45 | #[serde(rename = "request_cancellation")] 46 | CancelRequest, 47 | } 48 | 49 | impl_enum! { 50 | Action { 51 | Request => "request", 52 | CancelRequest => "cancel_request", 53 | } 54 | } 55 | 56 | /// Information about a requested key. 57 | #[derive(Clone, Debug, Deserialize, Serialize)] 58 | pub struct RequestedKeyInfo { 59 | /// The encryption algorithm the requested key in this event is to be used with. 60 | pub algorithm: Algorithm, 61 | 62 | /// The room where the key is used. 63 | pub room_id: RoomId, 64 | 65 | /// The Curve25519 key of the device which initiated the session originally. 66 | pub sender_key: String, 67 | 68 | /// The ID of the session that the key is for. 69 | pub session_id: String, 70 | } 71 | -------------------------------------------------------------------------------- /src/direct.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.direct* event. 2 | 3 | use std::collections::BTreeMap; 4 | 5 | use ruma_events_macros::ruma_event; 6 | use ruma_identifiers::{RoomId, UserId}; 7 | 8 | ruma_event! { 9 | /// Informs the client about the rooms that are considered direct by a user. 10 | DirectEvent { 11 | kind: Event, 12 | event_type: "m.direct", 13 | content_type_alias: { 14 | /// The payload for `DirectEvent`. 15 | /// 16 | /// A mapping of `UserId`s to a list of `RoomId`s which are considered *direct* for that 17 | /// particular user. 18 | BTreeMap> 19 | }, 20 | } 21 | } 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | use std::collections::BTreeMap; 26 | 27 | use ruma_identifiers::{RoomId, UserId}; 28 | use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; 29 | 30 | use super::{DirectEvent, DirectEventContent}; 31 | use crate::EventJson; 32 | 33 | #[test] 34 | fn serialization() { 35 | let mut content: DirectEventContent = BTreeMap::new(); 36 | let alice = UserId::new("ruma.io").unwrap(); 37 | let room = vec![RoomId::new("ruma.io").unwrap()]; 38 | 39 | content.insert(alice.clone(), room.clone()); 40 | 41 | let event = DirectEvent { content }; 42 | let json_data = json!({ 43 | "content": { 44 | alice.to_string(): vec![room[0].to_string()], 45 | }, 46 | "type": "m.direct" 47 | }); 48 | 49 | assert_eq!(to_json_value(&event).unwrap(), json_data); 50 | } 51 | 52 | #[test] 53 | fn deserialization() { 54 | let alice = UserId::new("ruma.io").unwrap(); 55 | let rooms = vec![ 56 | RoomId::new("ruma.io").unwrap(), 57 | RoomId::new("ruma.io").unwrap(), 58 | ]; 59 | 60 | let json_data = json!({ 61 | "content": { 62 | alice.to_string(): vec![rooms[0].to_string(), rooms[1].to_string()], 63 | }, 64 | "type": "m.direct" 65 | }); 66 | 67 | let event: DirectEvent = from_json_value::>(json_data) 68 | .unwrap() 69 | .deserialize() 70 | .unwrap(); 71 | let direct_rooms = event.content.get(&alice).unwrap(); 72 | 73 | assert!(direct_rooms.contains(&rooms[0])); 74 | assert!(direct_rooms.contains(&rooms[1])); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | error::Error, 3 | fmt::{self, Display, Formatter}, 4 | }; 5 | 6 | /// An event that is malformed or otherwise invalid. 7 | /// 8 | /// When attempting to deserialize an [`EventJson`](enum.EventJson.html), an error in the input 9 | /// data may cause deserialization to fail, or the JSON structure may be correct, but additional 10 | /// constraints defined in the matrix specification are not upheld. This type provides an error 11 | /// message and a flag for which type of error was encountered. 12 | #[derive(Clone, Debug)] 13 | pub struct InvalidEvent { 14 | pub(crate) message: String, 15 | pub(crate) kind: InvalidEventKind, 16 | } 17 | 18 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 19 | pub enum InvalidEventKind { 20 | Deserialization, 21 | Validation, 22 | } 23 | 24 | impl InvalidEvent { 25 | /// A message describing why the event is invalid. 26 | pub fn message(&self) -> String { 27 | self.message.clone() 28 | } 29 | 30 | /// Returns whether this is a deserialization error. 31 | pub fn is_deserialization(&self) -> bool { 32 | self.kind == InvalidEventKind::Deserialization 33 | } 34 | 35 | /// Returns whether this is a validation error. 36 | pub fn is_validation(&self) -> bool { 37 | self.kind == InvalidEventKind::Validation 38 | } 39 | } 40 | 41 | impl Display for InvalidEvent { 42 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 43 | write!(f, "{}", self.message()) 44 | } 45 | } 46 | 47 | impl Error for InvalidEvent {} 48 | 49 | /// An error returned when attempting to create an event with data that would make it invalid. 50 | /// 51 | /// This type is similar to [`InvalidEvent`](struct.InvalidEvent.html), but used during the 52 | /// construction of a new event, as opposed to deserialization of an existing event from JSON. 53 | #[derive(Clone, Debug, PartialEq)] 54 | pub struct InvalidInput(pub(crate) String); 55 | 56 | impl Display for InvalidInput { 57 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 58 | write!(f, "{}", self.0) 59 | } 60 | } 61 | 62 | impl Error for InvalidInput {} 63 | 64 | /// An error when attempting to create a value from a string via the `FromStr` trait. 65 | #[derive(Clone, Copy, Eq, Debug, Hash, PartialEq)] 66 | pub struct FromStrError; 67 | 68 | impl Display for FromStrError { 69 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 70 | write!(f, "failed to parse type from string") 71 | } 72 | } 73 | 74 | impl Error for FromStrError {} 75 | -------------------------------------------------------------------------------- /src/room/pinned_events.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.room.pinned_events* event. 2 | 3 | use ruma_events_macros::ruma_event; 4 | use ruma_identifiers::EventId; 5 | 6 | ruma_event! { 7 | /// Used to "pin" particular events in a room for other participants to review later. 8 | PinnedEventsEvent { 9 | kind: StateEvent, 10 | event_type: "m.room.pinned_events", 11 | content: { 12 | /// An ordered list of event IDs to pin. 13 | pub pinned: Vec, 14 | }, 15 | } 16 | } 17 | 18 | #[cfg(test)] 19 | mod tests { 20 | use std::time::{Duration, UNIX_EPOCH}; 21 | 22 | use ruma_identifiers::{EventId, RoomId, UserId}; 23 | use serde_json::to_string; 24 | 25 | use crate::{ 26 | room::pinned_events::{PinnedEventsEvent, PinnedEventsEventContent}, 27 | Event, EventJson, RoomEvent, StateEvent, UnsignedData, 28 | }; 29 | 30 | #[test] 31 | fn serialization_deserialization() { 32 | let mut content: PinnedEventsEventContent = PinnedEventsEventContent { pinned: Vec::new() }; 33 | 34 | content.pinned.push(EventId::new("example.com").unwrap()); 35 | content.pinned.push(EventId::new("example.com").unwrap()); 36 | 37 | let event = PinnedEventsEvent { 38 | content: content.clone(), 39 | event_id: EventId::new("example.com").unwrap(), 40 | origin_server_ts: UNIX_EPOCH + Duration::from_millis(1_432_804_485_886u64), 41 | prev_content: None, 42 | room_id: Some(RoomId::new("example.com").unwrap()), 43 | sender: UserId::new("example.com").unwrap(), 44 | state_key: "".to_string(), 45 | unsigned: UnsignedData::default(), 46 | }; 47 | 48 | let serialized_event = to_string(&event).unwrap(); 49 | let parsed_event: PinnedEventsEvent = 50 | serde_json::from_str::>(&serialized_event) 51 | .unwrap() 52 | .deserialize() 53 | .unwrap(); 54 | 55 | assert_eq!(parsed_event.event_id(), event.event_id()); 56 | assert_eq!(parsed_event.room_id(), event.room_id()); 57 | assert_eq!(parsed_event.sender(), event.sender()); 58 | assert_eq!(parsed_event.state_key(), event.state_key()); 59 | assert_eq!(parsed_event.origin_server_ts(), event.origin_server_ts()); 60 | 61 | assert_eq!(parsed_event.content().pinned, event.content.pinned); 62 | assert_eq!(parsed_event.content().pinned[0], content.pinned[0]); 63 | assert_eq!(parsed_event.content().pinned[1], content.pinned[1]); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/room/server_acl.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.room.server_acl* event. 2 | 3 | use ruma_events_macros::ruma_event; 4 | 5 | ruma_event! { 6 | /// An event to indicate which servers are permitted to participate in the room. 7 | ServerAclEvent { 8 | kind: StateEvent, 9 | event_type: "m.room.server_acl", 10 | content: { 11 | /// True to allow server names that are IP address literals. False to deny. 12 | /// 13 | /// This is strongly recommended to be set to false as servers running with IP literal 14 | /// names are strongly discouraged in order to require legitimate homeservers to be 15 | /// backed by a valid registered domain name. 16 | #[serde( 17 | default = "ruma_serde::default_true", 18 | skip_serializing_if = "ruma_serde::is_true" 19 | )] 20 | pub allow_ip_literals: bool, 21 | 22 | /// The server names to allow in the room, excluding any port information. Wildcards may 23 | /// be used to cover a wider range of hosts, where `*` matches zero or more characters 24 | /// and `?` matches exactly one character. 25 | /// 26 | /// **This defaults to an empty list when not provided, effectively disallowing every 27 | /// server.** 28 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 29 | pub allow: Vec, 30 | 31 | /// The server names to disallow in the room, excluding any port information. Wildcards may 32 | /// be used to cover a wider range of hosts, where * matches zero or more characters and ? 33 | /// matches exactly one character. 34 | /// 35 | /// This defaults to an empty list when not provided. 36 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 37 | pub deny: Vec, 38 | } 39 | } 40 | } 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | use serde_json::{from_value as from_json_value, json}; 45 | 46 | use super::ServerAclEvent; 47 | use crate::EventJson; 48 | 49 | #[test] 50 | fn default_values() { 51 | let json_data = json!({ 52 | "content": {}, 53 | "event_id": "$h29iv0s8:example.com","origin_server_ts":1, 54 | "sender": "@carl:example.com", 55 | "state_key": "", 56 | "type": "m.room.server_acl" 57 | }); 58 | let server_acl_event: ServerAclEvent = from_json_value::>(json_data) 59 | .unwrap() 60 | .deserialize() 61 | .unwrap(); 62 | 63 | assert_eq!(server_acl_event.content.allow_ip_literals, true); 64 | assert!(server_acl_event.content.allow.is_empty()); 65 | assert!(server_acl_event.content.deny.is_empty()); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/room/create.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.room.create* event. 2 | 3 | use std::convert::TryFrom; 4 | 5 | use ruma_events_macros::ruma_event; 6 | use ruma_identifiers::{EventId, RoomId, RoomVersionId, UserId}; 7 | use serde::{Deserialize, Serialize}; 8 | 9 | ruma_event! { 10 | /// This is the first event in a room and cannot be changed. It acts as the root of all other 11 | /// events. 12 | CreateEvent { 13 | kind: StateEvent, 14 | event_type: "m.room.create", 15 | content: { 16 | /// The `user_id` of the room creator. This is set by the homeserver. 17 | pub creator: UserId, 18 | 19 | /// Whether or not this room's data should be transferred to other homeservers. 20 | #[serde( 21 | rename = "m.federate", 22 | default = "ruma_serde::default_true", 23 | skip_serializing_if = "ruma_serde::is_true" 24 | )] 25 | pub federate: bool, 26 | 27 | /// The version of the room. Defaults to "1" if the key does not exist. 28 | #[serde(default = "default_room_version_id")] 29 | pub room_version: RoomVersionId, 30 | 31 | /// A reference to the room this room replaces, if the previous room was upgraded. 32 | #[serde(skip_serializing_if = "Option::is_none")] 33 | pub predecessor: Option, 34 | }, 35 | } 36 | } 37 | 38 | /// A reference to an old room replaced during a room version upgrade. 39 | #[derive(Clone, Debug, Deserialize, Serialize)] 40 | pub struct PreviousRoom { 41 | /// The ID of the old room. 42 | pub room_id: RoomId, 43 | 44 | /// The event ID of the last known event in the old room. 45 | pub event_id: EventId, 46 | } 47 | 48 | /// Used to default the `room_version` field to room version 1. 49 | fn default_room_version_id() -> RoomVersionId { 50 | RoomVersionId::try_from("1").unwrap() 51 | } 52 | 53 | #[cfg(test)] 54 | mod tests { 55 | use std::convert::TryFrom; 56 | 57 | use matches::assert_matches; 58 | use ruma_identifiers::{RoomVersionId, UserId}; 59 | use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; 60 | 61 | use super::CreateEventContent; 62 | use crate::EventJson; 63 | 64 | #[test] 65 | fn serialization() { 66 | let content = CreateEventContent { 67 | creator: UserId::try_from("@carl:example.com").unwrap(), 68 | federate: false, 69 | room_version: RoomVersionId::version_4(), 70 | predecessor: None, 71 | }; 72 | 73 | let json = json!({ 74 | "creator": "@carl:example.com", 75 | "m.federate": false, 76 | "room_version": "4" 77 | }); 78 | 79 | assert_eq!(to_json_value(&content).unwrap(), json); 80 | } 81 | 82 | #[test] 83 | fn deserialization() { 84 | let json = json!({ 85 | "creator": "@carl:example.com", 86 | "m.federate": true, 87 | "room_version": "4" 88 | }); 89 | 90 | assert_matches!( 91 | from_json_value::>(json) 92 | .unwrap() 93 | .deserialize() 94 | .unwrap(), 95 | CreateEventContent { 96 | creator, 97 | federate: true, 98 | room_version, 99 | predecessor: None, 100 | } if creator == "@carl:example.com" 101 | && room_version == RoomVersionId::version_4() 102 | ); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/json.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | clone::Clone, 3 | fmt::{self, Debug, Formatter}, 4 | marker::PhantomData, 5 | }; 6 | 7 | use serde::{ 8 | de::{Deserialize, Deserializer}, 9 | ser::{Serialize, Serializer}, 10 | }; 11 | use serde_json::value::RawValue; 12 | 13 | use crate::{ 14 | error::{InvalidEvent, InvalidEventKind}, 15 | TryFromRaw, 16 | }; 17 | 18 | /// A wrapper around `Box`, to be used in place of event [content] [collection] types in 19 | /// Matrix endpoint definition to allow request and response types to contain unknown events in 20 | /// addition to the known event(s) represented by the generic argument `Ev`. 21 | pub struct EventJson { 22 | json: Box, 23 | _ev: PhantomData, 24 | } 25 | 26 | impl EventJson { 27 | fn new(json: Box) -> Self { 28 | Self { 29 | json, 30 | _ev: PhantomData, 31 | } 32 | } 33 | 34 | /// Access the underlying json value. 35 | pub fn json(&self) -> &RawValue { 36 | &self.json 37 | } 38 | 39 | /// Convert `self` into the underlying json value. 40 | pub fn into_json(self) -> Box { 41 | self.json 42 | } 43 | } 44 | 45 | impl EventJson { 46 | /// Try to deserialize the JSON into the expected event type. 47 | pub fn deserialize(&self) -> Result { 48 | let raw_ev: T::Raw = match serde_json::from_str(self.json.get()) { 49 | Ok(raw) => raw, 50 | Err(error) => { 51 | return Err(InvalidEvent { 52 | message: error.to_string(), 53 | kind: InvalidEventKind::Deserialization, 54 | }); 55 | } 56 | }; 57 | 58 | match T::try_from_raw(raw_ev) { 59 | Ok(value) => Ok(value), 60 | Err(err) => Err(InvalidEvent { 61 | message: err.to_string(), 62 | kind: InvalidEventKind::Validation, 63 | }), 64 | } 65 | } 66 | } 67 | 68 | impl From<&T> for EventJson { 69 | fn from(val: &T) -> Self { 70 | Self::new(serde_json::value::to_raw_value(val).unwrap()) 71 | } 72 | } 73 | 74 | // Without the `TryFromRaw` bound, this would conflict with the next impl below 75 | // We could remove the `TryFromRaw` bound once specialization is stabilized. 76 | impl From for EventJson { 77 | fn from(val: T) -> Self { 78 | Self::from(&val) 79 | } 80 | } 81 | 82 | impl From> for EventJson { 83 | fn from(json: Box) -> Self { 84 | Self::new(json) 85 | } 86 | } 87 | 88 | impl Clone for EventJson { 89 | fn clone(&self) -> Self { 90 | Self::new(self.json.clone()) 91 | } 92 | } 93 | 94 | impl Debug for EventJson { 95 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 96 | use std::any::type_name; 97 | f.debug_struct(&format!("EventJson::<{}>", type_name::())) 98 | .field("json", &self.json) 99 | .finish() 100 | } 101 | } 102 | 103 | impl<'de, T> Deserialize<'de> for EventJson { 104 | fn deserialize(deserializer: D) -> Result 105 | where 106 | D: Deserializer<'de>, 107 | { 108 | Box::::deserialize(deserializer).map(Self::new) 109 | } 110 | } 111 | 112 | impl Serialize for EventJson { 113 | fn serialize(&self, serializer: S) -> Result 114 | where 115 | S: Serializer, 116 | { 117 | self.json.serialize(serializer) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! impl_enum { 2 | ($name:ident { $($variant:ident => $s:expr,)+ }) => { 3 | impl ::std::fmt::Display for $name { 4 | fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> { 5 | let variant = match *self { 6 | $($name::$variant => $s,)* 7 | }; 8 | 9 | write!(f, "{}", variant) 10 | } 11 | } 12 | 13 | impl ::std::str::FromStr for $name { 14 | type Err = $crate::FromStrError; 15 | 16 | fn from_str(s: &str) -> Result { 17 | match s { 18 | $($s => Ok($name::$variant),)* 19 | _ => Err($crate::FromStrError), 20 | } 21 | } 22 | } 23 | } 24 | } 25 | 26 | macro_rules! impl_event { 27 | ($name:ident, $content_name:ident, $event_type:path) => { 28 | impl crate::Event for $name { 29 | /// The type of this event's `content` field. 30 | type Content = $content_name; 31 | 32 | /// The event's content. 33 | fn content(&self) -> &Self::Content { 34 | &self.content 35 | } 36 | 37 | /// The type of the event. 38 | fn event_type(&self) -> crate::EventType { 39 | $event_type 40 | } 41 | } 42 | }; 43 | } 44 | 45 | macro_rules! impl_room_event { 46 | ($name:ident, $content_name:ident, $event_type:path) => { 47 | impl_event!($name, $content_name, $event_type); 48 | 49 | impl crate::RoomEvent for $name { 50 | /// The unique identifier for the event. 51 | fn event_id(&self) -> &EventId { 52 | &self.event_id 53 | } 54 | 55 | /// Time on originating homeserver when this event was sent. 56 | fn origin_server_ts(&self) -> ::std::time::SystemTime { 57 | self.origin_server_ts 58 | } 59 | 60 | /// The unique identifier for the room associated with this event. 61 | /// 62 | /// This can be `None` if the event came from a context where there is 63 | /// no ambiguity which room it belongs to, like a `/sync` response for example. 64 | fn room_id(&self) -> Option<&::ruma_identifiers::RoomId> { 65 | self.room_id.as_ref() 66 | } 67 | 68 | /// The unique identifier for the user who sent this event. 69 | fn sender(&self) -> &::ruma_identifiers::UserId { 70 | &self.sender 71 | } 72 | 73 | /// Additional key-value pairs not signed by the homeserver. 74 | fn unsigned(&self) -> &::ruma_events::UnsignedData { 75 | &self.unsigned 76 | } 77 | } 78 | }; 79 | } 80 | 81 | macro_rules! impl_state_event { 82 | ($name:ident, $content_name:ident, $event_type:path) => { 83 | impl_room_event!($name, $content_name, $event_type); 84 | 85 | impl crate::StateEvent for $name { 86 | /// The previous content for this state key, if any. 87 | fn prev_content(&self) -> Option<&Self::Content> { 88 | self.prev_content.as_ref() 89 | } 90 | 91 | /// A key that determines which piece of room state the event represents. 92 | fn state_key(&self) -> &str { 93 | &self.state_key 94 | } 95 | } 96 | }; 97 | } 98 | 99 | macro_rules! impl_from_for_enum { 100 | ($self_ty:ident, $inner_ty:ty, $variant:ident) => { 101 | impl ::std::convert::From<$inner_ty> for $self_ty { 102 | fn from(event: $inner_ty) -> Self { 103 | $self_ty::$variant(event) 104 | } 105 | } 106 | }; 107 | } 108 | -------------------------------------------------------------------------------- /src/room.rs: -------------------------------------------------------------------------------- 1 | //! Modules for events in the *m.room* namespace. 2 | //! 3 | //! This module also contains types shared by events in its child namespaces. 4 | 5 | use std::collections::BTreeMap; 6 | 7 | use js_int::UInt; 8 | use serde::{Deserialize, Serialize}; 9 | 10 | pub mod aliases; 11 | pub mod avatar; 12 | pub mod canonical_alias; 13 | pub mod create; 14 | pub mod encrypted; 15 | pub mod encryption; 16 | pub mod guest_access; 17 | pub mod history_visibility; 18 | pub mod join_rules; 19 | pub mod member; 20 | pub mod message; 21 | pub mod name; 22 | pub mod pinned_events; 23 | pub mod power_levels; 24 | pub mod redaction; 25 | pub mod server_acl; 26 | pub mod third_party_invite; 27 | pub mod tombstone; 28 | pub mod topic; 29 | 30 | /// Metadata about an image. 31 | #[derive(Clone, Debug, Deserialize, Serialize)] 32 | pub struct ImageInfo { 33 | /// The height of the image in pixels. 34 | #[serde(rename = "h", skip_serializing_if = "Option::is_none")] 35 | pub height: Option, 36 | 37 | /// The width of the image in pixels. 38 | #[serde(rename = "w", skip_serializing_if = "Option::is_none")] 39 | pub width: Option, 40 | 41 | /// The MIME type of the image, e.g. "image/png." 42 | #[serde(skip_serializing_if = "Option::is_none")] 43 | pub mimetype: Option, 44 | 45 | /// The file size of the image in bytes. 46 | #[serde(skip_serializing_if = "Option::is_none")] 47 | pub size: Option, 48 | 49 | /// Metadata about the image referred to in `thumbnail_url`. 50 | #[serde(skip_serializing_if = "Option::is_none")] 51 | pub thumbnail_info: Option, 52 | 53 | /// The URL to the thumbnail of the image. Only present if the thumbnail is unencrypted. 54 | #[serde(skip_serializing_if = "Option::is_none")] 55 | pub thumbnail_url: Option, 56 | 57 | /// Information on the encrypted thumbnail image. Only present if the thumbnail is encrypted. 58 | #[serde(skip_serializing_if = "Option::is_none")] 59 | pub thumbnail_file: Option, 60 | } 61 | 62 | /// Metadata about a thumbnail. 63 | #[derive(Clone, Debug, Deserialize, Serialize)] 64 | pub struct ThumbnailInfo { 65 | /// The height of the thumbnail in pixels. 66 | #[serde(rename = "h", skip_serializing_if = "Option::is_none")] 67 | pub height: Option, 68 | 69 | /// The width of the thumbnail in pixels. 70 | #[serde(rename = "w", skip_serializing_if = "Option::is_none")] 71 | pub width: Option, 72 | 73 | /// The MIME type of the thumbnail, e.g. "image/png." 74 | #[serde(skip_serializing_if = "Option::is_none")] 75 | pub mimetype: Option, 76 | 77 | /// The file size of the thumbnail in bytes. 78 | #[serde(skip_serializing_if = "Option::is_none")] 79 | pub size: Option, 80 | } 81 | 82 | /// A file sent to a room with end-to-end encryption enabled. 83 | #[derive(Clone, Debug, Deserialize, Serialize)] 84 | pub struct EncryptedFile { 85 | /// The URL to the file. 86 | pub url: String, 87 | 88 | /// A [JSON Web Key](https://tools.ietf.org/html/rfc7517#appendix-A.3) object. 89 | pub key: JsonWebKey, 90 | 91 | /// The initialization vector used by AES-CTR, encoded as unpadded base64. 92 | pub iv: String, 93 | 94 | /// A map from an algorithm name to a hash of the ciphertext, encoded as unpadded base64. 95 | /// Clients should support the SHA-256 hash, which uses the key sha256. 96 | pub hashes: BTreeMap, 97 | 98 | /// Version of the encrypted attachments protocol. Must be `v2`. 99 | pub v: String, 100 | } 101 | 102 | /// A [JSON Web Key](https://tools.ietf.org/html/rfc7517#appendix-A.3) object. 103 | #[derive(Clone, Debug, Deserialize, Serialize)] 104 | pub struct JsonWebKey { 105 | /// Key type. Must be `oct`. 106 | pub kty: String, 107 | 108 | /// Key operations. Must at least contain `encrypt` and `decrypt`. 109 | pub key_ops: Vec, 110 | 111 | /// Required. Algorithm. Must be `A256CTR`. 112 | pub alg: String, 113 | 114 | /// The key, encoded as urlsafe unpadded base64. 115 | pub k: String, 116 | 117 | /// Extractable. Must be `true`. This is a 118 | /// [W3C extension](https://w3c.github.io/webcrypto/#iana-section-jwk). 119 | pub ext: bool, 120 | } 121 | -------------------------------------------------------------------------------- /src/presence.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.presence* event. 2 | 3 | use js_int::UInt; 4 | use ruma_events_macros::ruma_event; 5 | use ruma_identifiers::UserId; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | ruma_event! { 9 | /// Informs the client of a user's presence state change. 10 | PresenceEvent { 11 | kind: Event, 12 | event_type: "m.presence", 13 | fields: { 14 | /// The unique identifier for the user associated with this event. 15 | pub sender: UserId, 16 | }, 17 | content: { 18 | /// The current avatar URL for this user. 19 | #[serde(skip_serializing_if = "Option::is_none")] 20 | pub avatar_url: Option, 21 | 22 | /// Whether or not the user is currently active. 23 | #[serde(skip_serializing_if = "Option::is_none")] 24 | pub currently_active: Option, 25 | 26 | /// The current display name for this user. 27 | #[serde(skip_serializing_if = "Option::is_none")] 28 | pub displayname: Option, 29 | 30 | /// The last time since this user performed some action, in milliseconds. 31 | #[serde(skip_serializing_if = "Option::is_none")] 32 | pub last_active_ago: Option, 33 | 34 | /// The presence state for this user. 35 | pub presence: PresenceState, 36 | 37 | /// An optional description to accompany the presence. 38 | #[serde(skip_serializing_if = "Option::is_none")] 39 | pub status_msg: Option, 40 | }, 41 | } 42 | } 43 | 44 | /// A description of a user's connectivity and availability for chat. 45 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 46 | pub enum PresenceState { 47 | /// Disconnected from the service. 48 | #[serde(rename = "offline")] 49 | Offline, 50 | 51 | /// Connected to the service. 52 | #[serde(rename = "online")] 53 | Online, 54 | 55 | /// Connected to the service but not available for chat. 56 | #[serde(rename = "unavailable")] 57 | Unavailable, 58 | } 59 | 60 | impl_enum! { 61 | PresenceState { 62 | Offline => "offline", 63 | Online => "online", 64 | Unavailable => "unavailable", 65 | } 66 | } 67 | 68 | #[cfg(test)] 69 | mod tests { 70 | use std::convert::TryFrom; 71 | 72 | use js_int::UInt; 73 | use matches::assert_matches; 74 | use ruma_identifiers::UserId; 75 | use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; 76 | 77 | use super::{PresenceEvent, PresenceEventContent, PresenceState}; 78 | use crate::EventJson; 79 | 80 | #[test] 81 | fn serialization() { 82 | let event = PresenceEvent { 83 | content: PresenceEventContent { 84 | avatar_url: Some("mxc://localhost:wefuiwegh8742w".to_string()), 85 | currently_active: Some(false), 86 | displayname: None, 87 | last_active_ago: Some(UInt::try_from(2_478_593).unwrap()), 88 | presence: PresenceState::Online, 89 | status_msg: Some("Making cupcakes".to_string()), 90 | }, 91 | sender: UserId::try_from("@example:localhost").unwrap(), 92 | }; 93 | 94 | let json = json!({ 95 | "content": { 96 | "avatar_url": "mxc://localhost:wefuiwegh8742w", 97 | "currently_active": false, 98 | "last_active_ago": 2_478_593, 99 | "presence": "online", 100 | "status_msg": "Making cupcakes" 101 | }, 102 | "sender": "@example:localhost", 103 | "type": "m.presence" 104 | }); 105 | 106 | assert_eq!(to_json_value(&event).unwrap(), json); 107 | } 108 | 109 | #[test] 110 | fn deserialization() { 111 | let json = json!({ 112 | "content": { 113 | "avatar_url": "mxc://localhost:wefuiwegh8742w", 114 | "currently_active": false, 115 | "last_active_ago": 2_478_593, 116 | "presence": "online", 117 | "status_msg": "Making cupcakes" 118 | }, 119 | "sender": "@example:localhost", 120 | "type": "m.presence" 121 | }); 122 | 123 | assert_matches!( 124 | from_json_value::>(json) 125 | .unwrap() 126 | .deserialize() 127 | .unwrap(), 128 | PresenceEvent { 129 | content: PresenceEventContent { 130 | avatar_url: Some(avatar_url), 131 | currently_active: Some(false), 132 | displayname: None, 133 | last_active_ago: Some(last_active_ago), 134 | presence: PresenceState::Online, 135 | status_msg: Some(status_msg), 136 | }, 137 | sender, 138 | } if avatar_url == "mxc://localhost:wefuiwegh8742w" 139 | && status_msg == "Making cupcakes" 140 | && sender == "@example:localhost" 141 | && last_active_ago == UInt::from(2_478_593u32) 142 | ); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/key/verification/cancel.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.key.verification.cancel* event. 2 | 3 | use std::fmt::{Display, Formatter, Result as FmtResult}; 4 | 5 | use ruma_events_macros::ruma_event; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | ruma_event! { 9 | /// Cancels a key verification process/request. 10 | /// 11 | /// Typically sent as a to-device event. 12 | CancelEvent { 13 | kind: Event, 14 | event_type: "m.key.verification.cancel", 15 | content: { 16 | /// The opaque identifier for the verification process/request. 17 | pub transaction_id: String, 18 | 19 | /// A human readable description of the `code`. 20 | /// 21 | /// The client should only rely on this string if it does not understand the `code`. 22 | pub reason: String, 23 | 24 | /// The error code for why the process/request was cancelled by the user. 25 | pub code: CancelCode, 26 | }, 27 | } 28 | } 29 | 30 | /// An error code for why the process/request was cancelled by the user. 31 | /// 32 | /// Custom error codes should use the Java package naming convention. 33 | #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] 34 | #[serde(from = "String", into = "String")] 35 | pub enum CancelCode { 36 | /// The user cancelled the verification. 37 | User, 38 | 39 | /// The verification process timed out. Verification processes can define their own timeout 40 | /// parameters. 41 | Timeout, 42 | 43 | /// The device does not know about the given transaction ID. 44 | UnknownTransaction, 45 | 46 | /// The device does not know how to handle the requested method. 47 | /// 48 | /// This should be sent for *m.key.verification.start* messages and messages defined by 49 | /// individual verification processes. 50 | UnknownMethod, 51 | 52 | /// The device received an unexpected message. 53 | /// 54 | /// Typically raised when one of the parties is handling the verification out of order. 55 | UnexpectedMessage, 56 | 57 | /// The key was not verified. 58 | KeyMismatch, 59 | 60 | /// The expected user did not match the user verified. 61 | UserMismatch, 62 | 63 | /// The message received was invalid. 64 | InvalidMessage, 65 | 66 | /// An *m.key.verification.request* was accepted by a different device. 67 | /// 68 | /// The device receiving this error can ignore the verification request. 69 | Accepted, 70 | 71 | /// Any code that is not part of the specification. 72 | Custom(String), 73 | } 74 | 75 | impl Display for CancelCode { 76 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 77 | let cancel_code_str = match *self { 78 | CancelCode::User => "m.user", 79 | CancelCode::Timeout => "m.timeout", 80 | CancelCode::UnknownTransaction => "m.unknown_transaction", 81 | CancelCode::UnknownMethod => "m.unknown_method", 82 | CancelCode::UnexpectedMessage => "m.unexpected_message", 83 | CancelCode::KeyMismatch => "m.key_mismatch", 84 | CancelCode::UserMismatch => "m.user_mismatch", 85 | CancelCode::InvalidMessage => "m.invalid_message", 86 | CancelCode::Accepted => "m.accepted", 87 | CancelCode::Custom(ref cancel_code) => cancel_code, 88 | }; 89 | 90 | write!(f, "{}", cancel_code_str) 91 | } 92 | } 93 | 94 | impl From for CancelCode 95 | where 96 | T: Into + AsRef, 97 | { 98 | fn from(s: T) -> CancelCode { 99 | match s.as_ref() { 100 | "m.user" => CancelCode::User, 101 | "m.timeout" => CancelCode::Timeout, 102 | "m.unknown_transaction" => CancelCode::UnknownTransaction, 103 | "m.unknown_method" => CancelCode::UnknownMethod, 104 | "m.unexpected_message" => CancelCode::UnexpectedMessage, 105 | "m.key_mismatch" => CancelCode::KeyMismatch, 106 | "m.user_mismatch" => CancelCode::UserMismatch, 107 | "m.invalid_message" => CancelCode::InvalidMessage, 108 | "m.accepted" => CancelCode::Accepted, 109 | _ => CancelCode::Custom(s.into()), 110 | } 111 | } 112 | } 113 | 114 | impl From for String { 115 | fn from(cancel_code: CancelCode) -> String { 116 | cancel_code.to_string() 117 | } 118 | } 119 | 120 | #[cfg(test)] 121 | mod tests { 122 | use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; 123 | 124 | use super::CancelCode; 125 | 126 | #[test] 127 | fn cancel_codes_serialize_to_display_form() { 128 | assert_eq!(to_json_value(&CancelCode::User).unwrap(), json!("m.user")); 129 | } 130 | 131 | #[test] 132 | fn custom_cancel_codes_serialize_to_display_form() { 133 | assert_eq!( 134 | to_json_value(&CancelCode::Custom("io.ruma.test".to_string())).unwrap(), 135 | json!("io.ruma.test") 136 | ); 137 | } 138 | 139 | #[test] 140 | fn cancel_codes_deserialize_from_display_form() { 141 | assert_eq!( 142 | from_json_value::(json!("m.user")).unwrap(), 143 | CancelCode::User 144 | ); 145 | } 146 | 147 | #[test] 148 | fn custom_cancel_codes_deserialize_from_display_form() { 149 | assert_eq!( 150 | from_json_value::(json!("io.ruma.test")).unwrap(), 151 | CancelCode::Custom("io.ruma.test".to_string()) 152 | ) 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /ruma-events-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Crate `ruma_events_macros` provides a procedural macro for generating 2 | //! [ruma-events](https://github.com/ruma/ruma-events) events. 3 | //! 4 | //! See the documentation for the `ruma_event!` macro for usage details. 5 | #![deny( 6 | missing_copy_implementations, 7 | missing_debug_implementations, 8 | // missing_docs, # Uncomment when https://github.com/rust-lang/rust/pull/60562 is released. 9 | )] 10 | #![warn( 11 | clippy::empty_line_after_outer_attr, 12 | clippy::expl_impl_clone_on_copy, 13 | clippy::if_not_else, 14 | clippy::items_after_statements, 15 | clippy::match_same_arms, 16 | clippy::mem_forget, 17 | clippy::missing_docs_in_private_items, 18 | clippy::multiple_inherent_impl, 19 | clippy::mut_mut, 20 | clippy::needless_borrow, 21 | clippy::needless_continue, 22 | clippy::single_match_else, 23 | clippy::unicode_not_nfc, 24 | clippy::use_self, 25 | clippy::used_underscore_binding, 26 | clippy::wrong_pub_self_convention, 27 | clippy::wrong_self_convention 28 | )] 29 | // Since we support Rust 1.36.0, we can't apply this suggestion yet 30 | #![allow(clippy::use_self)] 31 | #![recursion_limit = "128"] 32 | 33 | extern crate proc_macro; 34 | 35 | use proc_macro::TokenStream; 36 | use quote::ToTokens; 37 | use syn::{parse_macro_input, DeriveInput}; 38 | 39 | use self::{from_raw::expand_from_raw, gen::RumaEvent, parse::RumaEventInput}; 40 | 41 | mod from_raw; 42 | mod gen; 43 | mod parse; 44 | 45 | // A note about the `example` modules that appears in doctests: 46 | // 47 | // This is necessary because otherwise the expanded code appears in function context, which makes 48 | // the compiler interpret the output of the macro as a statement, and proc macros currently aren't 49 | // allowed to expand to statements, resulting in a compiler error. 50 | 51 | /// Generates a Rust type for a Matrix event. 52 | /// 53 | /// # Examples 54 | /// 55 | /// The most common form of event is a struct with all the standard fields for an event of its 56 | /// kind and a struct for its `content` field: 57 | /// 58 | /// ```ignore 59 | /// # pub mod example { 60 | /// # use ruma_events_macros::ruma_event; 61 | /// ruma_event! { 62 | /// /// Informs the room about what room aliases it has been given. 63 | /// AliasesEvent { 64 | /// kind: StateEvent, 65 | /// event_type: RoomAliases, 66 | /// content: { 67 | /// /// A list of room aliases. 68 | /// pub aliases: Vec, 69 | /// } 70 | /// } 71 | /// } 72 | /// # } 73 | /// ``` 74 | /// 75 | /// Occasionally an event will have non-standard fields at its top level (outside the `content` 76 | /// field). These extra fields are declared in block labeled with `fields`: 77 | /// 78 | /// ```ignore 79 | /// # pub mod example { 80 | /// # use ruma_events_macros::ruma_event; 81 | /// ruma_event! { 82 | /// /// A redaction of an event. 83 | /// RedactionEvent { 84 | /// kind: RoomEvent, 85 | /// event_type: RoomRedaction, 86 | /// fields: { 87 | /// /// The ID of the event that was redacted. 88 | /// pub redacts: ruma_identifiers::EventId 89 | /// }, 90 | /// content: { 91 | /// /// The reason for the redaction, if any. 92 | /// pub reason: Option, 93 | /// }, 94 | /// } 95 | /// } 96 | /// # } 97 | /// ``` 98 | /// 99 | /// Sometimes the type of the `content` should be a type alias rather than a struct or enum. This 100 | /// is designated with `content_type_alias`: 101 | /// 102 | /// ```ignore 103 | /// # pub mod example { 104 | /// # use ruma_events_macros::ruma_event; 105 | /// ruma_event! { 106 | /// /// Informs the client about the rooms that are considered direct by a user. 107 | /// DirectEvent { 108 | /// kind: Event, 109 | /// event_type: Direct, 110 | /// content_type_alias: { 111 | /// /// The payload of a `DirectEvent`. 112 | /// /// 113 | /// /// A mapping of `UserId`'s to a collection of `RoomId`'s which are considered 114 | /// /// *direct* for that particular user. 115 | /// std::collections::BTreeMap> 116 | /// } 117 | /// } 118 | /// } 119 | /// # } 120 | /// ``` 121 | /// 122 | /// If `content` and `content_type_alias` are both supplied, the second one listed will overwrite 123 | /// the first. 124 | /// 125 | /// The event type and content type will have copies generated inside a private `raw` module. These 126 | /// "raw" versions are the same, except they implement `serde::Deserialize`. An implementation of 127 | /// `FromRaw` will be provided, which will allow the user to deserialize the event type as 128 | /// `EventJson`. 129 | #[proc_macro] 130 | pub fn ruma_event(input: TokenStream) -> TokenStream { 131 | let ruma_event_input = syn::parse_macro_input!(input as RumaEventInput); 132 | 133 | let ruma_event = RumaEvent::from(ruma_event_input); 134 | 135 | ruma_event.into_token_stream().into() 136 | } 137 | 138 | /// Generates an implementation of `ruma_events::FromRaw`. Only usable inside of `ruma_events`. 139 | /// Requires there to be a `raw` module in the same scope, with a type with the same name and fields 140 | /// as the one that this macro is used on. 141 | #[proc_macro_derive(FromRaw)] 142 | pub fn derive_from_raw(input: TokenStream) -> TokenStream { 143 | let input = parse_macro_input!(input as DeriveInput); 144 | expand_from_raw(input) 145 | .unwrap_or_else(|err| err.to_compile_error()) 146 | .into() 147 | } 148 | -------------------------------------------------------------------------------- /src/room/canonical_alias.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.room.canonical_alias* event. 2 | 3 | use ruma_events_macros::ruma_event; 4 | use ruma_identifiers::RoomAliasId; 5 | 6 | ruma_event! { 7 | /// Informs the room as to which alias is the canonical one. 8 | CanonicalAliasEvent { 9 | kind: StateEvent, 10 | event_type: "m.room.canonical_alias", 11 | content: { 12 | /// The canonical alias. 13 | /// 14 | /// Rooms with `alias: None` should be treated the same as a room 15 | /// with no canonical alias. 16 | #[serde( 17 | default, deserialize_with = "ruma_serde::empty_string_as_none", 18 | skip_serializing_if = "Option::is_none" 19 | )] 20 | pub alias: Option, 21 | /// List of alternative aliases to the room. 22 | #[serde( 23 | default, 24 | skip_serializing_if = "Vec::is_empty" 25 | )] 26 | pub alt_aliases: Vec, 27 | }, 28 | } 29 | } 30 | 31 | #[cfg(test)] 32 | mod tests { 33 | use std::{ 34 | convert::TryFrom, 35 | time::{Duration, UNIX_EPOCH}, 36 | }; 37 | 38 | use ruma_identifiers::{EventId, RoomAliasId, UserId}; 39 | use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; 40 | 41 | use super::{CanonicalAliasEvent, CanonicalAliasEventContent}; 42 | use crate::{EventJson, UnsignedData}; 43 | 44 | #[test] 45 | fn serialization_with_optional_fields_as_none() { 46 | let canonical_alias_event = CanonicalAliasEvent { 47 | content: CanonicalAliasEventContent { 48 | alias: Some(RoomAliasId::try_from("#somewhere:localhost").unwrap()), 49 | alt_aliases: Vec::new(), 50 | }, 51 | event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(), 52 | origin_server_ts: UNIX_EPOCH + Duration::from_millis(1), 53 | prev_content: None, 54 | room_id: None, 55 | sender: UserId::try_from("@carl:example.com").unwrap(), 56 | state_key: "".to_string(), 57 | unsigned: UnsignedData::default(), 58 | }; 59 | 60 | let actual = to_json_value(&canonical_alias_event).unwrap(); 61 | let expected = json!({ 62 | "content": { 63 | "alias": "#somewhere:localhost" 64 | }, 65 | "event_id": "$h29iv0s8:example.com", 66 | "origin_server_ts": 1, 67 | "sender": "@carl:example.com", 68 | "state_key": "", 69 | "type": "m.room.canonical_alias" 70 | }); 71 | 72 | assert_eq!(actual, expected); 73 | } 74 | 75 | #[test] 76 | fn absent_field_as_none() { 77 | let json_data = json!({ 78 | "content": {}, 79 | "event_id": "$h29iv0s8:example.com", 80 | "origin_server_ts": 1, 81 | "sender": "@carl:example.com", 82 | "state_key": "", 83 | "type": "m.room.canonical_alias" 84 | }); 85 | 86 | assert_eq!( 87 | from_json_value::>(json_data) 88 | .unwrap() 89 | .deserialize() 90 | .unwrap() 91 | .content 92 | .alias, 93 | None 94 | ); 95 | } 96 | 97 | #[test] 98 | fn null_field_as_none() { 99 | let json_data = json!({ 100 | "content": { 101 | "alias": null 102 | }, 103 | "event_id": "$h29iv0s8:example.com", 104 | "origin_server_ts": 1, 105 | "sender": "@carl:example.com", 106 | "state_key": "", 107 | "type": "m.room.canonical_alias" 108 | }); 109 | assert_eq!( 110 | from_json_value::>(json_data) 111 | .unwrap() 112 | .deserialize() 113 | .unwrap() 114 | .content 115 | .alias, 116 | None 117 | ); 118 | } 119 | 120 | #[test] 121 | fn empty_field_as_none() { 122 | let json_data = json!({ 123 | "content": { 124 | "alias": "" 125 | }, 126 | "event_id": "$h29iv0s8:example.com", 127 | "origin_server_ts": 1, 128 | "sender": "@carl:example.com", 129 | "state_key": "", 130 | "type": "m.room.canonical_alias" 131 | }); 132 | assert_eq!( 133 | from_json_value::>(json_data) 134 | .unwrap() 135 | .deserialize() 136 | .unwrap() 137 | .content 138 | .alias, 139 | None 140 | ); 141 | } 142 | 143 | #[test] 144 | fn nonempty_field_as_some() { 145 | let alias = Some(RoomAliasId::try_from("#somewhere:localhost").unwrap()); 146 | let json_data = json!({ 147 | "content": { 148 | "alias": "#somewhere:localhost" 149 | }, 150 | "event_id": "$h29iv0s8:example.com", 151 | "origin_server_ts": 1, 152 | "sender": "@carl:example.com", 153 | "state_key": "", 154 | "type": "m.room.canonical_alias" 155 | }); 156 | assert_eq!( 157 | from_json_value::>(json_data) 158 | .unwrap() 159 | .deserialize() 160 | .unwrap() 161 | .content 162 | .alias, 163 | alias 164 | ); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/collections/only.rs: -------------------------------------------------------------------------------- 1 | //! Enums for heterogeneous collections of events, exclusive to event types that implement "at 2 | //! most" the trait of the same name. 3 | 4 | use serde::Serialize; 5 | 6 | pub use super::all::StateEvent; 7 | use super::raw::only as raw; 8 | use crate::{ 9 | call::{ 10 | answer::AnswerEvent, candidates::CandidatesEvent, hangup::HangupEvent, invite::InviteEvent, 11 | }, 12 | direct::DirectEvent, 13 | dummy::DummyEvent, 14 | forwarded_room_key::ForwardedRoomKeyEvent, 15 | fully_read::FullyReadEvent, 16 | ignored_user_list::IgnoredUserListEvent, 17 | key::verification::{ 18 | accept::AcceptEvent, cancel::CancelEvent, key::KeyEvent, mac::MacEvent, 19 | request::RequestEvent, start::StartEvent, 20 | }, 21 | presence::PresenceEvent, 22 | push_rules::PushRulesEvent, 23 | receipt::ReceiptEvent, 24 | room::{ 25 | encrypted::EncryptedEvent, 26 | message::{feedback::FeedbackEvent, MessageEvent}, 27 | redaction::RedactionEvent, 28 | }, 29 | room_key::RoomKeyEvent, 30 | room_key_request::RoomKeyRequestEvent, 31 | sticker::StickerEvent, 32 | tag::TagEvent, 33 | typing::TypingEvent, 34 | CustomEvent, CustomRoomEvent, TryFromRaw, 35 | }; 36 | 37 | /// A basic event. 38 | #[derive(Clone, Debug, Serialize)] 39 | #[serde(untagged)] 40 | #[allow(clippy::large_enum_variant)] 41 | pub enum Event { 42 | /// m.direct 43 | Direct(DirectEvent), 44 | 45 | /// m.dummy 46 | Dummy(DummyEvent), 47 | 48 | /// m.forwarded_room_key 49 | ForwardedRoomKey(ForwardedRoomKeyEvent), 50 | 51 | /// m.fully_read 52 | FullyRead(FullyReadEvent), 53 | 54 | /// m.key.verification.accept 55 | KeyVerificationAccept(AcceptEvent), 56 | 57 | /// m.key.verification.cancel 58 | KeyVerificationCancel(CancelEvent), 59 | 60 | /// m.key.verification.key 61 | KeyVerificationKey(KeyEvent), 62 | 63 | /// m.key.verification.mac 64 | KeyVerificationMac(MacEvent), 65 | 66 | /// m.key.verification.request 67 | KeyVerificationRequest(RequestEvent), 68 | 69 | /// m.key.verification.start 70 | KeyVerificationStart(StartEvent), 71 | 72 | /// m.ignored_user_list 73 | IgnoredUserList(IgnoredUserListEvent), 74 | 75 | /// m.presence 76 | Presence(PresenceEvent), 77 | 78 | /// m.push_rules 79 | PushRules(PushRulesEvent), 80 | 81 | /// m.room_key 82 | RoomKey(RoomKeyEvent), 83 | 84 | /// m.room_key_request 85 | RoomKeyRequest(RoomKeyRequestEvent), 86 | 87 | /// m.receipt 88 | Receipt(ReceiptEvent), 89 | 90 | /// m.tag 91 | Tag(TagEvent), 92 | 93 | /// m.typing 94 | Typing(TypingEvent), 95 | 96 | /// Any basic event that is not part of the specification. 97 | Custom(CustomEvent), 98 | } 99 | 100 | /// A room event. 101 | #[derive(Clone, Debug, Serialize)] 102 | #[serde(untagged)] 103 | #[allow(clippy::large_enum_variant)] 104 | pub enum RoomEvent { 105 | /// m.call.answer 106 | CallAnswer(AnswerEvent), 107 | 108 | /// m.call.candidates 109 | CallCandidates(CandidatesEvent), 110 | 111 | /// m.call.hangup 112 | CallHangup(HangupEvent), 113 | 114 | /// m.call.invite 115 | CallInvite(InviteEvent), 116 | 117 | /// m.room.encrypted 118 | RoomEncrypted(EncryptedEvent), 119 | 120 | /// m.room.message 121 | RoomMessage(MessageEvent), 122 | 123 | /// m.room.message.feedback 124 | RoomMessageFeedback(FeedbackEvent), 125 | 126 | /// m.room.redaction 127 | RoomRedaction(RedactionEvent), 128 | 129 | /// m.sticker 130 | Sticker(StickerEvent), 131 | 132 | /// Any room event that is not part of the specification. 133 | CustomRoom(CustomRoomEvent), 134 | } 135 | 136 | impl TryFromRaw for Event { 137 | type Raw = raw::Event; 138 | type Err = String; 139 | 140 | fn try_from_raw(raw: raw::Event) -> Result { 141 | use crate::util::try_convert_variant as conv; 142 | use raw::Event::*; 143 | 144 | match raw { 145 | Direct(c) => conv(Event::Direct, c), 146 | Dummy(c) => conv(Event::Dummy, c), 147 | ForwardedRoomKey(c) => conv(Event::ForwardedRoomKey, c), 148 | FullyRead(c) => conv(Event::FullyRead, c), 149 | KeyVerificationAccept(c) => conv(Event::KeyVerificationAccept, c), 150 | KeyVerificationCancel(c) => conv(Event::KeyVerificationCancel, c), 151 | KeyVerificationKey(c) => conv(Event::KeyVerificationKey, c), 152 | KeyVerificationMac(c) => conv(Event::KeyVerificationMac, c), 153 | KeyVerificationRequest(c) => conv(Event::KeyVerificationRequest, c), 154 | KeyVerificationStart(c) => conv(Event::KeyVerificationStart, c), 155 | IgnoredUserList(c) => conv(Event::IgnoredUserList, c), 156 | Presence(c) => conv(Event::Presence, c), 157 | PushRules(c) => conv(Event::PushRules, c), 158 | RoomKey(c) => conv(Event::RoomKey, c), 159 | RoomKeyRequest(c) => conv(Event::RoomKeyRequest, c), 160 | Receipt(c) => conv(Event::Receipt, c), 161 | Tag(c) => conv(Event::Tag, c), 162 | Typing(c) => conv(Event::Typing, c), 163 | Custom(c) => conv(Event::Custom, c), 164 | } 165 | } 166 | } 167 | 168 | impl TryFromRaw for RoomEvent { 169 | type Raw = raw::RoomEvent; 170 | type Err = String; 171 | 172 | fn try_from_raw(raw: raw::RoomEvent) -> Result { 173 | use crate::util::try_convert_variant as conv; 174 | use raw::RoomEvent::*; 175 | 176 | match raw { 177 | CallAnswer(c) => conv(RoomEvent::CallAnswer, c), 178 | CallCandidates(c) => conv(RoomEvent::CallCandidates, c), 179 | CallHangup(c) => conv(RoomEvent::CallHangup, c), 180 | CallInvite(c) => conv(RoomEvent::CallInvite, c), 181 | RoomEncrypted(c) => conv(RoomEvent::RoomEncrypted, c), 182 | RoomMessage(c) => conv(RoomEvent::RoomMessage, c), 183 | RoomMessageFeedback(c) => conv(RoomEvent::RoomMessageFeedback, c), 184 | RoomRedaction(c) => conv(RoomEvent::RoomRedaction, c), 185 | Sticker(c) => conv(RoomEvent::Sticker, c), 186 | CustomRoom(c) => conv(RoomEvent::CustomRoom, c), 187 | } 188 | } 189 | } 190 | 191 | impl_from_for_enum!(Event, DirectEvent, Direct); 192 | impl_from_for_enum!(Event, DummyEvent, Dummy); 193 | impl_from_for_enum!(Event, ForwardedRoomKeyEvent, ForwardedRoomKey); 194 | impl_from_for_enum!(Event, FullyReadEvent, FullyRead); 195 | impl_from_for_enum!(Event, AcceptEvent, KeyVerificationAccept); 196 | impl_from_for_enum!(Event, CancelEvent, KeyVerificationCancel); 197 | impl_from_for_enum!(Event, KeyEvent, KeyVerificationKey); 198 | impl_from_for_enum!(Event, MacEvent, KeyVerificationMac); 199 | impl_from_for_enum!(Event, RequestEvent, KeyVerificationRequest); 200 | impl_from_for_enum!(Event, StartEvent, KeyVerificationStart); 201 | impl_from_for_enum!(Event, IgnoredUserListEvent, IgnoredUserList); 202 | impl_from_for_enum!(Event, PresenceEvent, Presence); 203 | impl_from_for_enum!(Event, PushRulesEvent, PushRules); 204 | impl_from_for_enum!(Event, ReceiptEvent, Receipt); 205 | impl_from_for_enum!(Event, TagEvent, Tag); 206 | impl_from_for_enum!(Event, TypingEvent, Typing); 207 | impl_from_for_enum!(Event, CustomEvent, Custom); 208 | 209 | impl_from_for_enum!(RoomEvent, AnswerEvent, CallAnswer); 210 | impl_from_for_enum!(RoomEvent, CandidatesEvent, CallCandidates); 211 | impl_from_for_enum!(RoomEvent, HangupEvent, CallHangup); 212 | impl_from_for_enum!(RoomEvent, InviteEvent, CallInvite); 213 | impl_from_for_enum!(RoomEvent, EncryptedEvent, RoomEncrypted); 214 | impl_from_for_enum!(RoomEvent, MessageEvent, RoomMessage); 215 | impl_from_for_enum!(RoomEvent, FeedbackEvent, RoomMessageFeedback); 216 | impl_from_for_enum!(RoomEvent, RedactionEvent, RoomRedaction); 217 | impl_from_for_enum!(RoomEvent, StickerEvent, Sticker); 218 | impl_from_for_enum!(RoomEvent, CustomRoomEvent, CustomRoom); 219 | -------------------------------------------------------------------------------- /src/collections/raw/only.rs: -------------------------------------------------------------------------------- 1 | //! Enums for heterogeneous collections of events, exclusive to event types that implement "at 2 | //! most" the trait of the same name. 3 | 4 | use serde::{de::Error as _, Deserialize, Deserializer}; 5 | use serde_json::Value as JsonValue; 6 | 7 | pub use super::all::StateEvent; 8 | use crate::{ 9 | call::{ 10 | answer::raw::AnswerEvent, candidates::raw::CandidatesEvent, hangup::raw::HangupEvent, 11 | invite::raw::InviteEvent, 12 | }, 13 | custom::raw::{CustomEvent, CustomRoomEvent}, 14 | direct::raw::DirectEvent, 15 | dummy::raw::DummyEvent, 16 | forwarded_room_key::raw::ForwardedRoomKeyEvent, 17 | fully_read::raw::FullyReadEvent, 18 | ignored_user_list::raw::IgnoredUserListEvent, 19 | key::verification::{ 20 | accept::raw::AcceptEvent, cancel::raw::CancelEvent, key::raw::KeyEvent, mac::raw::MacEvent, 21 | request::raw::RequestEvent, start::raw::StartEvent, 22 | }, 23 | presence::raw::PresenceEvent, 24 | push_rules::raw::PushRulesEvent, 25 | receipt::raw::ReceiptEvent, 26 | room::{ 27 | encrypted::raw::EncryptedEvent, 28 | message::{feedback::raw::FeedbackEvent, raw::MessageEvent}, 29 | redaction::raw::RedactionEvent, 30 | }, 31 | room_key::raw::RoomKeyEvent, 32 | room_key_request::raw::RoomKeyRequestEvent, 33 | sticker::raw::StickerEvent, 34 | tag::raw::TagEvent, 35 | typing::raw::TypingEvent, 36 | util::get_field, 37 | EventType, 38 | }; 39 | 40 | /// A basic event. 41 | #[derive(Clone, Debug)] 42 | #[allow(clippy::large_enum_variant)] 43 | pub enum Event { 44 | /// m.direct 45 | Direct(DirectEvent), 46 | 47 | /// m.dummy 48 | Dummy(DummyEvent), 49 | 50 | /// m.forwarded_room_key 51 | ForwardedRoomKey(ForwardedRoomKeyEvent), 52 | 53 | /// m.fully_read 54 | FullyRead(FullyReadEvent), 55 | 56 | /// m.key.verification.accept 57 | KeyVerificationAccept(AcceptEvent), 58 | 59 | /// m.key.verification.cancel 60 | KeyVerificationCancel(CancelEvent), 61 | 62 | /// m.key.verification.key 63 | KeyVerificationKey(KeyEvent), 64 | 65 | /// m.key.verification.mac 66 | KeyVerificationMac(MacEvent), 67 | 68 | /// m.key.verification.request 69 | KeyVerificationRequest(RequestEvent), 70 | 71 | /// m.key.verification.start 72 | KeyVerificationStart(StartEvent), 73 | 74 | /// m.ignored_user_list 75 | IgnoredUserList(IgnoredUserListEvent), 76 | 77 | /// m.presence 78 | Presence(PresenceEvent), 79 | 80 | /// m.push_rules 81 | PushRules(PushRulesEvent), 82 | 83 | /// m.room_key 84 | RoomKey(RoomKeyEvent), 85 | 86 | /// m.room_key_request 87 | RoomKeyRequest(RoomKeyRequestEvent), 88 | 89 | /// m.receipt 90 | Receipt(ReceiptEvent), 91 | 92 | /// m.tag 93 | Tag(TagEvent), 94 | 95 | /// m.typing 96 | Typing(TypingEvent), 97 | 98 | /// Any basic event that is not part of the specification. 99 | Custom(CustomEvent), 100 | } 101 | 102 | /// A room event. 103 | #[derive(Clone, Debug)] 104 | #[allow(clippy::large_enum_variant)] 105 | pub enum RoomEvent { 106 | /// m.call.answer 107 | CallAnswer(AnswerEvent), 108 | 109 | /// m.call.candidates 110 | CallCandidates(CandidatesEvent), 111 | 112 | /// m.call.hangup 113 | CallHangup(HangupEvent), 114 | 115 | /// m.call.invite 116 | CallInvite(InviteEvent), 117 | 118 | /// m.room.encrypted 119 | RoomEncrypted(EncryptedEvent), 120 | 121 | /// m.room.message 122 | RoomMessage(MessageEvent), 123 | 124 | /// m.room.message.feedback 125 | RoomMessageFeedback(FeedbackEvent), 126 | 127 | /// m.room.redaction 128 | RoomRedaction(RedactionEvent), 129 | 130 | /// m.sticker 131 | Sticker(StickerEvent), 132 | 133 | /// Any room event that is not part of the specification. 134 | CustomRoom(CustomRoomEvent), 135 | } 136 | 137 | impl<'de> Deserialize<'de> for Event { 138 | fn deserialize(deserializer: D) -> Result 139 | where 140 | D: Deserializer<'de>, 141 | { 142 | use crate::util::try_variant_from_value as from_value; 143 | use EventType::*; 144 | 145 | let value = JsonValue::deserialize(deserializer)?; 146 | let event_type = get_field(&value, "type")?; 147 | 148 | match event_type { 149 | Direct => from_value(value, Event::Direct), 150 | Dummy => from_value(value, Event::Dummy), 151 | ForwardedRoomKey => from_value(value, Event::ForwardedRoomKey), 152 | FullyRead => from_value(value, Event::FullyRead), 153 | KeyVerificationAccept => from_value(value, Event::KeyVerificationAccept), 154 | KeyVerificationCancel => from_value(value, Event::KeyVerificationCancel), 155 | KeyVerificationKey => from_value(value, Event::KeyVerificationKey), 156 | KeyVerificationMac => from_value(value, Event::KeyVerificationMac), 157 | KeyVerificationRequest => from_value(value, Event::KeyVerificationRequest), 158 | KeyVerificationStart => from_value(value, Event::KeyVerificationStart), 159 | IgnoredUserList => from_value(value, Event::IgnoredUserList), 160 | Presence => from_value(value, Event::Presence), 161 | PushRules => from_value(value, Event::PushRules), 162 | RoomKey => from_value(value, Event::RoomKey), 163 | RoomKeyRequest => from_value(value, Event::RoomKeyRequest), 164 | Receipt => from_value(value, Event::Receipt), 165 | Tag => from_value(value, Event::Tag), 166 | Typing => from_value(value, Event::Typing), 167 | Custom(_event_type_name) => from_value(value, Event::Custom), 168 | CallAnswer 169 | | CallCandidates 170 | | CallHangup 171 | | CallInvite 172 | | RoomAliases 173 | | RoomAvatar 174 | | RoomCanonicalAlias 175 | | RoomCreate 176 | | RoomEncrypted 177 | | RoomEncryption 178 | | RoomGuestAccess 179 | | RoomHistoryVisibility 180 | | RoomJoinRules 181 | | RoomMember 182 | | RoomMessage 183 | | RoomMessageFeedback 184 | | RoomName 185 | | RoomPinnedEvents 186 | | RoomPowerLevels 187 | | RoomServerAcl 188 | | RoomThirdPartyInvite 189 | | RoomTombstone 190 | | RoomTopic 191 | | RoomRedaction 192 | | Sticker => Err(D::Error::custom("invalid event type")), 193 | } 194 | } 195 | } 196 | 197 | impl<'de> Deserialize<'de> for RoomEvent { 198 | fn deserialize(deserializer: D) -> Result 199 | where 200 | D: Deserializer<'de>, 201 | { 202 | use crate::util::try_variant_from_value as from_value; 203 | use EventType::*; 204 | 205 | let value = JsonValue::deserialize(deserializer)?; 206 | let event_type = get_field(&value, "type")?; 207 | 208 | match event_type { 209 | CallAnswer => from_value(value, RoomEvent::CallAnswer), 210 | CallCandidates => from_value(value, RoomEvent::CallCandidates), 211 | CallHangup => from_value(value, RoomEvent::CallHangup), 212 | CallInvite => from_value(value, RoomEvent::CallInvite), 213 | RoomEncrypted => from_value(value, RoomEvent::RoomEncrypted), 214 | RoomMessage => from_value(value, RoomEvent::RoomMessage), 215 | RoomMessageFeedback => from_value(value, RoomEvent::RoomMessageFeedback), 216 | RoomRedaction => from_value(value, RoomEvent::RoomRedaction), 217 | Sticker => from_value(value, RoomEvent::Sticker), 218 | Custom(_event_type_name) => from_value(value, RoomEvent::CustomRoom), 219 | Direct 220 | | Dummy 221 | | ForwardedRoomKey 222 | | FullyRead 223 | | IgnoredUserList 224 | | KeyVerificationAccept 225 | | KeyVerificationCancel 226 | | KeyVerificationKey 227 | | KeyVerificationMac 228 | | KeyVerificationRequest 229 | | KeyVerificationStart 230 | | Presence 231 | | PushRules 232 | | Receipt 233 | | RoomAvatar 234 | | RoomAliases 235 | | RoomCanonicalAlias 236 | | RoomCreate 237 | | RoomEncryption 238 | | RoomGuestAccess 239 | | RoomHistoryVisibility 240 | | RoomJoinRules 241 | | RoomKey 242 | | RoomKeyRequest 243 | | RoomMember 244 | | RoomName 245 | | RoomPinnedEvents 246 | | RoomPowerLevels 247 | | RoomServerAcl 248 | | RoomThirdPartyInvite 249 | | RoomTombstone 250 | | RoomTopic 251 | | Tag 252 | | Typing => Err(D::Error::custom("invalid event type")), 253 | } 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /tests/ruma_events_macros.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::BTreeMap, 3 | convert::TryFrom, 4 | time::{Duration, UNIX_EPOCH}, 5 | }; 6 | 7 | use js_int::Int; 8 | use maplit::btreemap; 9 | use matches::assert_matches; 10 | use ruma_events::{EventJson, UnsignedData}; 11 | use ruma_events_macros::ruma_event; 12 | use ruma_identifiers::{RoomAliasId, RoomId, UserId}; 13 | use serde_json::{from_value as from_json_value, json}; 14 | 15 | // See note about wrapping macro expansion in a module from `src/lib.rs` 16 | mod common_case { 17 | use super::*; 18 | 19 | ruma_event! { 20 | /// Informs the room about what room aliases it has been given. 21 | AliasesEvent { 22 | kind: StateEvent, 23 | event_type: "m.room.aliases", 24 | content: { 25 | /// A list of room aliases. 26 | pub aliases: Vec, 27 | } 28 | } 29 | } 30 | 31 | #[test] 32 | fn optional_fields_as_none() { 33 | let json = json!({ 34 | "content": { 35 | "aliases": [] 36 | }, 37 | "event_id": "$h29iv0s8:example.com", 38 | "origin_server_ts": 1, 39 | "sender": "@carl:example.com", 40 | "state_key": "example.com", 41 | "type": "m.room.aliases" 42 | }); 43 | 44 | assert_matches!( 45 | from_json_value::>(json) 46 | .unwrap() 47 | .deserialize() 48 | .unwrap(), 49 | AliasesEvent { 50 | content: AliasesEventContent { aliases }, 51 | event_id, 52 | origin_server_ts, 53 | prev_content: None, 54 | room_id: None, 55 | sender, 56 | state_key, 57 | unsigned, 58 | } if aliases.is_empty() 59 | && event_id == "$h29iv0s8:example.com" 60 | && origin_server_ts == UNIX_EPOCH + Duration::from_millis(1) 61 | && sender == "@carl:example.com" 62 | && state_key == "example.com" 63 | && unsigned.is_empty() 64 | ) 65 | } 66 | 67 | #[test] 68 | fn some_optional_fields_as_some() { 69 | let json = json!({ 70 | "content": { 71 | "aliases": ["#room:example.org"] 72 | }, 73 | "event_id": "$h29iv0s8:example.com", 74 | "origin_server_ts": 1, 75 | "prev_content": { 76 | "aliases": [] 77 | }, 78 | "room_id": "!n8f893n9:example.com", 79 | "sender": "@carl:example.com", 80 | "state_key": "example.com", 81 | "type": "m.room.aliases" 82 | }); 83 | 84 | assert_matches!( 85 | from_json_value::>(json) 86 | .unwrap() 87 | .deserialize() 88 | .unwrap(), 89 | AliasesEvent { 90 | content: AliasesEventContent { aliases, }, 91 | event_id, 92 | origin_server_ts, 93 | prev_content: Some(AliasesEventContent { aliases: prev_aliases }), 94 | room_id: Some(room_id), 95 | sender, 96 | state_key, 97 | unsigned, 98 | } if aliases == vec![RoomAliasId::try_from("#room:example.org").unwrap()] 99 | && event_id == "$h29iv0s8:example.com" 100 | && origin_server_ts == UNIX_EPOCH + Duration::from_millis(1) 101 | && prev_aliases.is_empty() 102 | && room_id == "!n8f893n9:example.com" 103 | && sender == "@carl:example.com" 104 | && state_key == "example.com" 105 | && unsigned.is_empty() 106 | ); 107 | } 108 | 109 | #[test] 110 | fn all_optional_fields_as_some() { 111 | let json = json!({ 112 | "content": { 113 | "aliases": ["#room:example.org"] 114 | }, 115 | "event_id": "$h29iv0s8:example.com", 116 | "origin_server_ts": 1, 117 | "prev_content": { 118 | "aliases": [] 119 | }, 120 | "room_id": "!n8f893n9:example.com", 121 | "sender": "@carl:example.com", 122 | "state_key": "example.com", 123 | "unsigned": { 124 | "age": 100 125 | }, 126 | "type": "m.room.aliases" 127 | }); 128 | 129 | assert_matches!( 130 | from_json_value::>(json) 131 | .unwrap() 132 | .deserialize() 133 | .unwrap(), 134 | AliasesEvent { 135 | content: AliasesEventContent { aliases }, 136 | event_id, 137 | origin_server_ts, 138 | prev_content: Some(AliasesEventContent { aliases: prev_aliases }), 139 | room_id: Some(room_id), 140 | sender, 141 | state_key, 142 | unsigned: UnsignedData { 143 | age: Some(age), 144 | redacted_because: None, 145 | transaction_id: None, 146 | }, 147 | } if aliases == vec![RoomAliasId::try_from("#room:example.org").unwrap()] 148 | && event_id == "$h29iv0s8:example.com" 149 | && origin_server_ts == UNIX_EPOCH + Duration::from_millis(1) 150 | && prev_aliases.is_empty() 151 | && room_id == "!n8f893n9:example.com" 152 | && sender == "@carl:example.com" 153 | && state_key == "example.com" 154 | && age == Int::from(100) 155 | ); 156 | } 157 | } 158 | 159 | mod extra_fields { 160 | use super::*; 161 | 162 | ruma_event! { 163 | /// A redaction of an event. 164 | RedactionEvent { 165 | kind: RoomEvent, 166 | event_type: "m.room.redaction", 167 | fields: { 168 | /// The ID of the event that was redacted. 169 | pub redacts: ruma_identifiers::EventId 170 | }, 171 | content: { 172 | /// The reason for the redaction, if any. 173 | pub reason: Option, 174 | }, 175 | } 176 | } 177 | 178 | #[test] 179 | fn field_serialization_deserialization() { 180 | let json = json!({ 181 | "content": { 182 | "reason": null 183 | }, 184 | "event_id": "$h29iv0s8:example.com", 185 | "origin_server_ts": 1, 186 | "redacts": "$h29iv0s8:example.com", 187 | "room_id": "!n8f893n9:example.com", 188 | "sender": "@carl:example.com", 189 | "unsigned": { 190 | "age": 100 191 | }, 192 | "type": "m.room.redaction" 193 | }); 194 | 195 | assert_matches!( 196 | from_json_value::>(json) 197 | .unwrap() 198 | .deserialize() 199 | .unwrap(), 200 | RedactionEvent { 201 | content: RedactionEventContent { reason: None }, 202 | redacts, 203 | event_id, 204 | origin_server_ts, 205 | room_id: Some(room_id), 206 | sender, 207 | unsigned: UnsignedData { 208 | age: Some(age), 209 | redacted_because: None, 210 | transaction_id: None, 211 | }, 212 | } if redacts == "$h29iv0s8:example.com" 213 | && event_id == "$h29iv0s8:example.com" 214 | && origin_server_ts == UNIX_EPOCH + Duration::from_millis(1) 215 | && room_id == "!n8f893n9:example.com" 216 | && sender == "@carl:example.com" 217 | && age == Int::from(100) 218 | ); 219 | } 220 | } 221 | 222 | mod type_alias { 223 | use super::*; 224 | 225 | ruma_event! { 226 | /// Informs the client about the rooms that are considered direct by a user. 227 | DirectEvent { 228 | kind: Event, 229 | event_type: "m.direct", 230 | content_type_alias: { 231 | /// The payload of a `DirectEvent`. 232 | /// 233 | /// A mapping of `UserId`'s to a collection of `RoomId`'s which are considered 234 | /// *direct* for that particular user. 235 | BTreeMap> 236 | } 237 | } 238 | } 239 | 240 | #[test] 241 | fn alias_is_not_empty() { 242 | let json = json!({ 243 | "content": { 244 | "@bob:example.com": ["!n8f893n9:example.com"] 245 | }, 246 | "type": "m.direct" 247 | }); 248 | 249 | let event = from_json_value::>(json) 250 | .unwrap() 251 | .deserialize() 252 | .unwrap(); 253 | 254 | assert_eq!( 255 | event.content, 256 | btreemap! { 257 | UserId::try_from("@bob:example.com").unwrap() => vec![ 258 | RoomId::try_from("!n8f893n9:example.com").unwrap() 259 | ] 260 | } 261 | ); 262 | } 263 | 264 | #[test] 265 | fn alias_empty() { 266 | let json = json!({ 267 | "content": {}, 268 | "type": "m.direct" 269 | }); 270 | 271 | let _ = from_json_value::>(json) 272 | .unwrap() 273 | .deserialize() 274 | .unwrap(); 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /src/custom.rs: -------------------------------------------------------------------------------- 1 | //! Types for custom events outside of the Matrix specification. 2 | 3 | use std::time::SystemTime; 4 | 5 | use crate::{Event, EventType, RoomEvent, StateEvent, UnsignedData}; 6 | 7 | use ruma_events_macros::FromRaw; 8 | use ruma_identifiers::{EventId, RoomId, UserId}; 9 | use serde::Serialize; 10 | use serde_json::Value as JsonValue; 11 | 12 | /// A custom event not covered by the Matrix specification. 13 | #[derive(Clone, Debug, FromRaw, Serialize)] 14 | pub struct CustomEvent { 15 | /// The event's content. 16 | pub content: CustomEventContent, 17 | /// The custom type of the event. 18 | #[serde(rename = "type")] 19 | pub event_type: String, 20 | } 21 | 22 | /// The payload for `CustomEvent`. 23 | pub type CustomEventContent = JsonValue; 24 | 25 | impl Event for CustomEvent { 26 | /// The type of this event's `content` field. 27 | type Content = CustomEventContent; 28 | 29 | /// The event's content. 30 | fn content(&self) -> &Self::Content { 31 | &self.content 32 | } 33 | 34 | /// The type of the event. 35 | fn event_type(&self) -> EventType { 36 | EventType::Custom(self.event_type.clone()) 37 | } 38 | } 39 | 40 | /// A custom room event not covered by the Matrix specification. 41 | #[derive(Clone, Debug, FromRaw, Serialize)] 42 | pub struct CustomRoomEvent { 43 | /// The event's content. 44 | pub content: CustomRoomEventContent, 45 | /// The unique identifier for the event. 46 | pub event_id: EventId, 47 | /// The custom type of the event. 48 | #[serde(rename = "type")] 49 | pub event_type: String, 50 | /// Time on originating homeserver when this event was sent. 51 | #[serde(with = "ruma_serde::time::ms_since_unix_epoch")] 52 | pub origin_server_ts: SystemTime, 53 | /// The unique identifier for the room associated with this event. 54 | pub room_id: Option, 55 | /// The unique identifier for the user who sent this event. 56 | pub sender: UserId, 57 | /// Additional key-value pairs not signed by the homeserver. 58 | #[serde(skip_serializing_if = "UnsignedData::is_empty")] 59 | pub unsigned: UnsignedData, 60 | } 61 | 62 | /// The payload for `CustomRoomEvent`. 63 | pub type CustomRoomEventContent = JsonValue; 64 | 65 | impl Event for CustomRoomEvent { 66 | /// The type of this event's `content` field. 67 | type Content = CustomRoomEventContent; 68 | 69 | /// The event's content. 70 | fn content(&self) -> &Self::Content { 71 | &self.content 72 | } 73 | 74 | /// The type of the event. 75 | fn event_type(&self) -> EventType { 76 | EventType::Custom(self.event_type.clone()) 77 | } 78 | } 79 | 80 | impl RoomEvent for CustomRoomEvent { 81 | /// The unique identifier for the event. 82 | fn event_id(&self) -> &EventId { 83 | &self.event_id 84 | } 85 | 86 | /// Time on originating homeserver when this event was sent. 87 | fn origin_server_ts(&self) -> SystemTime { 88 | self.origin_server_ts 89 | } 90 | 91 | /// The unique identifier for the room associated with this event. 92 | /// 93 | /// This can be `None` if the event came from a context where there is 94 | /// no ambiguity which room it belongs to, like a `/sync` response for example. 95 | fn room_id(&self) -> Option<&RoomId> { 96 | self.room_id.as_ref() 97 | } 98 | 99 | /// The unique identifier for the user who sent this event. 100 | fn sender(&self) -> &UserId { 101 | &self.sender 102 | } 103 | 104 | /// Additional key-value pairs not signed by the homeserver. 105 | fn unsigned(&self) -> &UnsignedData { 106 | &self.unsigned 107 | } 108 | } 109 | 110 | /// A custom state event not covered by the Matrix specification. 111 | #[derive(Clone, Debug, FromRaw, Serialize)] 112 | pub struct CustomStateEvent { 113 | /// The event's content. 114 | pub content: CustomStateEventContent, 115 | /// The unique identifier for the event. 116 | pub event_id: EventId, 117 | /// The custom type of the event. 118 | #[serde(rename = "type")] 119 | pub event_type: String, 120 | /// Time on originating homeserver when this event was sent. 121 | #[serde(with = "ruma_serde::time::ms_since_unix_epoch")] 122 | pub origin_server_ts: SystemTime, 123 | /// The previous content for this state key, if any. 124 | pub prev_content: Option, 125 | /// The unique identifier for the room associated with this event. 126 | pub room_id: Option, 127 | /// The unique identifier for the user who sent this event. 128 | pub sender: UserId, 129 | /// A key that determines which piece of room state the event represents. 130 | pub state_key: String, 131 | /// Additional key-value pairs not signed by the homeserver. 132 | #[serde(skip_serializing_if = "UnsignedData::is_empty")] 133 | pub unsigned: UnsignedData, 134 | } 135 | 136 | /// The payload for `CustomStateEvent`. 137 | pub type CustomStateEventContent = JsonValue; 138 | 139 | impl Event for CustomStateEvent { 140 | /// The type of this event's `content` field. 141 | type Content = CustomStateEventContent; 142 | 143 | /// The event's content. 144 | fn content(&self) -> &Self::Content { 145 | &self.content 146 | } 147 | 148 | /// The type of the event. 149 | fn event_type(&self) -> EventType { 150 | EventType::Custom(self.event_type.clone()) 151 | } 152 | } 153 | 154 | impl RoomEvent for CustomStateEvent { 155 | /// The unique identifier for the event. 156 | fn event_id(&self) -> &EventId { 157 | &self.event_id 158 | } 159 | 160 | /// Time on originating homeserver when this event was sent. 161 | fn origin_server_ts(&self) -> SystemTime { 162 | self.origin_server_ts 163 | } 164 | 165 | /// The unique identifier for the room associated with this event. 166 | /// 167 | /// This can be `None` if the event came from a context where there is 168 | /// no ambiguity which room it belongs to, like a `/sync` response for example. 169 | fn room_id(&self) -> Option<&RoomId> { 170 | self.room_id.as_ref() 171 | } 172 | 173 | /// The unique identifier for the user who sent this event. 174 | fn sender(&self) -> &UserId { 175 | &self.sender 176 | } 177 | 178 | /// Additional key-value pairs not signed by the homeserver. 179 | fn unsigned(&self) -> &UnsignedData { 180 | &self.unsigned 181 | } 182 | } 183 | 184 | impl StateEvent for CustomStateEvent { 185 | /// The previous content for this state key, if any. 186 | fn prev_content(&self) -> Option<&Self::Content> { 187 | self.prev_content.as_ref() 188 | } 189 | 190 | /// A key that determines which piece of room state the event represents. 191 | fn state_key(&self) -> &str { 192 | &self.state_key 193 | } 194 | } 195 | 196 | pub(crate) mod raw { 197 | use std::time::SystemTime; 198 | 199 | use ruma_identifiers::{EventId, RoomId, UserId}; 200 | use serde::Deserialize; 201 | 202 | use super::{ 203 | CustomEventContent, CustomRoomEventContent, CustomStateEventContent, UnsignedData, 204 | }; 205 | 206 | /// A custom event not covered by the Matrix specification. 207 | #[derive(Clone, Debug, Deserialize)] 208 | pub struct CustomEvent { 209 | /// The event's content. 210 | pub content: CustomEventContent, 211 | /// The custom type of the event. 212 | #[serde(rename = "type")] 213 | pub event_type: String, 214 | } 215 | 216 | /// A custom room event not covered by the Matrix specification. 217 | #[derive(Clone, Debug, Deserialize)] 218 | pub struct CustomRoomEvent { 219 | /// The event's content. 220 | pub content: CustomRoomEventContent, 221 | /// The unique identifier for the event. 222 | pub event_id: EventId, 223 | /// The custom type of the event. 224 | #[serde(rename = "type")] 225 | pub event_type: String, 226 | /// Time on originating homeserver when this event was sent. 227 | #[serde(with = "ruma_serde::time::ms_since_unix_epoch")] 228 | pub origin_server_ts: SystemTime, 229 | /// The unique identifier for the room associated with this event. 230 | pub room_id: Option, 231 | /// The unique identifier for the user who sent this event. 232 | pub sender: UserId, 233 | /// Additional key-value pairs not signed by the homeserver. 234 | #[serde(default)] 235 | pub unsigned: UnsignedData, 236 | } 237 | 238 | /// A custom state event not covered by the Matrix specification. 239 | #[derive(Clone, Debug, Deserialize)] 240 | pub struct CustomStateEvent { 241 | /// The event's content. 242 | pub content: CustomStateEventContent, 243 | /// The unique identifier for the event. 244 | pub event_id: EventId, 245 | /// The custom type of the event. 246 | #[serde(rename = "type")] 247 | pub event_type: String, 248 | /// Time on originating homeserver when this event was sent. 249 | #[serde(with = "ruma_serde::time::ms_since_unix_epoch")] 250 | pub origin_server_ts: SystemTime, 251 | /// The previous content for this state key, if any. 252 | pub prev_content: Option, 253 | /// The unique identifier for the room associated with this event. 254 | pub room_id: Option, 255 | /// The unique identifier for the user who sent this event. 256 | pub sender: UserId, 257 | /// A key that determines which piece of room state the event represents. 258 | pub state_key: String, 259 | /// Additional key-value pairs not signed by the homeserver. 260 | #[serde(default)] 261 | pub unsigned: UnsignedData, 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /src/room/encrypted.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.room.encrypted* event. 2 | 3 | use std::{collections::BTreeMap, time::SystemTime}; 4 | 5 | use js_int::UInt; 6 | use ruma_identifiers::{DeviceId, EventId, RoomId, UserId}; 7 | use serde::{Deserialize, Serialize}; 8 | 9 | use crate::{EventType, FromRaw, UnsignedData}; 10 | 11 | /// This event type is used when sending encrypted events. 12 | /// 13 | /// This type is to be used within a room. For a to-device event, use `EncryptedEventContent` 14 | /// directly. 15 | #[derive(Clone, Debug, Serialize)] 16 | #[serde(tag = "type", rename = "m.room.encrypted")] 17 | pub struct EncryptedEvent { 18 | /// The event's content. 19 | pub content: EncryptedEventContent, 20 | 21 | /// The unique identifier for the event. 22 | pub event_id: EventId, 23 | 24 | /// Time on originating homeserver when this event was sent. 25 | #[serde(with = "ruma_serde::time::ms_since_unix_epoch")] 26 | pub origin_server_ts: SystemTime, 27 | 28 | /// The unique identifier for the room associated with this event. 29 | #[serde(skip_serializing_if = "Option::is_none")] 30 | pub room_id: Option, 31 | 32 | /// The unique identifier for the user who sent this event. 33 | pub sender: UserId, 34 | 35 | /// Additional key-value pairs not signed by the homeserver. 36 | #[serde(skip_serializing_if = "UnsignedData::is_empty")] 37 | pub unsigned: UnsignedData, 38 | } 39 | 40 | /// The payload for `EncryptedEvent`. 41 | #[derive(Clone, Debug, Serialize)] 42 | #[non_exhaustive] 43 | #[serde(tag = "algorithm")] 44 | pub enum EncryptedEventContent { 45 | /// An event encrypted with *m.olm.v1.curve25519-aes-sha2*. 46 | #[serde(rename = "m.olm.v1.curve25519-aes-sha2")] 47 | OlmV1Curve25519AesSha2(OlmV1Curve25519AesSha2Content), 48 | 49 | /// An event encrypted with *m.megolm.v1.aes-sha2*. 50 | #[serde(rename = "m.megolm.v1.aes-sha2")] 51 | MegolmV1AesSha2(MegolmV1AesSha2Content), 52 | } 53 | 54 | impl FromRaw for EncryptedEvent { 55 | type Raw = raw::EncryptedEvent; 56 | 57 | fn from_raw(raw: raw::EncryptedEvent) -> Self { 58 | Self { 59 | content: FromRaw::from_raw(raw.content), 60 | event_id: raw.event_id, 61 | origin_server_ts: raw.origin_server_ts, 62 | room_id: raw.room_id, 63 | sender: raw.sender, 64 | unsigned: raw.unsigned, 65 | } 66 | } 67 | } 68 | 69 | impl FromRaw for EncryptedEventContent { 70 | type Raw = raw::EncryptedEventContent; 71 | 72 | fn from_raw(raw: raw::EncryptedEventContent) -> Self { 73 | use raw::EncryptedEventContent::*; 74 | 75 | match raw { 76 | OlmV1Curve25519AesSha2(content) => { 77 | EncryptedEventContent::OlmV1Curve25519AesSha2(content) 78 | } 79 | MegolmV1AesSha2(content) => EncryptedEventContent::MegolmV1AesSha2(content), 80 | } 81 | } 82 | } 83 | 84 | impl_room_event!( 85 | EncryptedEvent, 86 | EncryptedEventContent, 87 | EventType::RoomEncrypted 88 | ); 89 | 90 | pub(crate) mod raw { 91 | use std::time::SystemTime; 92 | 93 | use ruma_identifiers::{EventId, RoomId, UserId}; 94 | use serde::Deserialize; 95 | 96 | use super::{MegolmV1AesSha2Content, OlmV1Curve25519AesSha2Content}; 97 | use crate::UnsignedData; 98 | 99 | /// This event type is used when sending encrypted events. 100 | /// 101 | /// This type is to be used within a room. For a to-device event, use `EncryptedEventContent` 102 | /// directly. 103 | #[derive(Clone, Debug, Deserialize)] 104 | pub struct EncryptedEvent { 105 | /// The event's content. 106 | pub content: EncryptedEventContent, 107 | 108 | /// The unique identifier for the event. 109 | pub event_id: EventId, 110 | 111 | /// Time on originating homeserver when this event was sent. 112 | #[serde(with = "ruma_serde::time::ms_since_unix_epoch")] 113 | pub origin_server_ts: SystemTime, 114 | 115 | /// The unique identifier for the room associated with this event. 116 | pub room_id: Option, 117 | 118 | /// The unique identifier for the user who sent this event. 119 | pub sender: UserId, 120 | 121 | /// Additional key-value pairs not signed by the homeserver. 122 | #[serde(default)] 123 | pub unsigned: UnsignedData, 124 | } 125 | 126 | /// The payload for `EncryptedEvent`. 127 | #[derive(Clone, Debug, Deserialize)] 128 | #[serde(tag = "algorithm")] 129 | pub enum EncryptedEventContent { 130 | /// An event encrypted with *m.olm.v1.curve25519-aes-sha2*. 131 | #[serde(rename = "m.olm.v1.curve25519-aes-sha2")] 132 | OlmV1Curve25519AesSha2(OlmV1Curve25519AesSha2Content), 133 | 134 | /// An event encrypted with *m.megolm.v1.aes-sha2*. 135 | #[serde(rename = "m.megolm.v1.aes-sha2")] 136 | MegolmV1AesSha2(MegolmV1AesSha2Content), 137 | } 138 | } 139 | 140 | /// The payload for `EncryptedEvent` using the *m.olm.v1.curve25519-aes-sha2* algorithm. 141 | #[derive(Clone, Debug, Serialize, Deserialize)] 142 | pub struct OlmV1Curve25519AesSha2Content { 143 | /// A map from the recipient Curve25519 identity key to ciphertext information. 144 | pub ciphertext: BTreeMap, 145 | 146 | /// The Curve25519 key of the sender. 147 | pub sender_key: String, 148 | } 149 | 150 | /// Ciphertext information holding the ciphertext and message type. 151 | /// 152 | /// Used for messages encrypted with the *m.olm.v1.curve25519-aes-sha2* algorithm. 153 | #[derive(Clone, Debug, Deserialize, Serialize)] 154 | pub struct CiphertextInfo { 155 | /// The encrypted payload. 156 | pub body: String, 157 | 158 | /// The Olm message type. 159 | #[serde(rename = "type")] 160 | pub message_type: UInt, 161 | } 162 | 163 | /// The payload for `EncryptedEvent` using the *m.megolm.v1.aes-sha2* algorithm. 164 | #[derive(Clone, Debug, Serialize, Deserialize)] 165 | pub struct MegolmV1AesSha2Content { 166 | /// The encrypted content of the event. 167 | pub ciphertext: String, 168 | 169 | /// The Curve25519 key of the sender. 170 | pub sender_key: String, 171 | 172 | /// The ID of the sending device. 173 | pub device_id: DeviceId, 174 | 175 | /// The ID of the session used to encrypt the message. 176 | pub session_id: String, 177 | } 178 | 179 | #[cfg(test)] 180 | mod tests { 181 | use matches::assert_matches; 182 | use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; 183 | 184 | use super::{EncryptedEventContent, MegolmV1AesSha2Content}; 185 | use crate::EventJson; 186 | 187 | #[test] 188 | fn serialization() { 189 | let key_verification_start_content = 190 | EncryptedEventContent::MegolmV1AesSha2(MegolmV1AesSha2Content { 191 | ciphertext: "ciphertext".to_string(), 192 | sender_key: "sender_key".to_string(), 193 | device_id: "device_id".to_string(), 194 | session_id: "session_id".to_string(), 195 | }); 196 | 197 | let json_data = json!({ 198 | "algorithm": "m.megolm.v1.aes-sha2", 199 | "ciphertext": "ciphertext", 200 | "sender_key": "sender_key", 201 | "device_id": "device_id", 202 | "session_id": "session_id" 203 | }); 204 | 205 | assert_eq!( 206 | to_json_value(&key_verification_start_content).unwrap(), 207 | json_data 208 | ); 209 | } 210 | 211 | #[test] 212 | fn deserialization() { 213 | let json_data = json!({ 214 | "algorithm": "m.megolm.v1.aes-sha2", 215 | "ciphertext": "ciphertext", 216 | "sender_key": "sender_key", 217 | "device_id": "device_id", 218 | "session_id": "session_id" 219 | }); 220 | 221 | assert_matches!( 222 | from_json_value::>(json_data) 223 | .unwrap() 224 | .deserialize() 225 | .unwrap(), 226 | EncryptedEventContent::MegolmV1AesSha2(MegolmV1AesSha2Content { 227 | ciphertext, 228 | sender_key, 229 | device_id, 230 | session_id, 231 | }) if ciphertext == "ciphertext" 232 | && sender_key == "sender_key" 233 | && device_id == "device_id" 234 | && session_id == "session_id" 235 | ); 236 | } 237 | 238 | #[test] 239 | fn deserialization_olm() { 240 | let json_data = json!({ 241 | "sender_key": "test_key", 242 | "ciphertext": { 243 | "test_curve_key": { 244 | "body": "encrypted_body", 245 | "type": 1 246 | } 247 | }, 248 | "algorithm": "m.olm.v1.curve25519-aes-sha2" 249 | }); 250 | let content = from_json_value::>(json_data) 251 | .unwrap() 252 | .deserialize() 253 | .unwrap(); 254 | 255 | match content { 256 | EncryptedEventContent::OlmV1Curve25519AesSha2(c) => { 257 | assert_eq!(c.sender_key, "test_key"); 258 | assert_eq!(c.ciphertext.len(), 1); 259 | assert_eq!(c.ciphertext["test_curve_key"].body, "encrypted_body"); 260 | assert_eq!(c.ciphertext["test_curve_key"].message_type, 1u16.into()); 261 | } 262 | _ => panic!("Wrong content type, expected a OlmV1 content"), 263 | } 264 | } 265 | 266 | #[test] 267 | fn deserialization_failure() { 268 | assert!(from_json_value::>( 269 | json!({ "algorithm": "m.megolm.v1.aes-sha2" }) 270 | ) 271 | .unwrap() 272 | .deserialize() 273 | .is_err()); 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /ruma-events-macros/src/parse.rs: -------------------------------------------------------------------------------- 1 | //! Details of parsing input for the `ruma_event` procedural macro. 2 | 3 | use syn::{ 4 | braced, 5 | parse::{self, Parse, ParseStream}, 6 | token::Colon, 7 | Attribute, Expr, ExprLit, Field, FieldValue, Ident, Lit, LitStr, Member, Token, TypePath, 8 | }; 9 | 10 | /// The entire `ruma_event!` macro structure directly as it appears in the source code.. 11 | pub struct RumaEventInput { 12 | /// Outer attributes on the field, such as a docstring. 13 | pub attrs: Vec, 14 | 15 | /// The name of the event. 16 | pub name: Ident, 17 | 18 | /// The kind of event, determiend by the `kind` field. 19 | pub kind: EventKind, 20 | 21 | /// The value for the `type` field in the JSON representation of this event. There needs to be a 22 | /// corresponding variant in `ruma_events::EventType` for this event (converted to a valid 23 | /// Rust-style type name by stripping `m.`, replacing the remaining dots by underscores and then 24 | /// converting from snake_case to CamelCase). 25 | pub event_type: LitStr, 26 | 27 | /// Additional named struct fields in the top level event struct. 28 | pub fields: Option>, 29 | 30 | /// A struct definition or type alias to be used as the event's `content` field. 31 | pub content: Content, 32 | } 33 | 34 | impl Parse for RumaEventInput { 35 | fn parse(input: ParseStream<'_>) -> parse::Result { 36 | let attrs = input.call(Attribute::parse_outer)?; 37 | let name: Ident = input.parse()?; 38 | let body; 39 | braced!(body in input); 40 | 41 | let mut kind = None; 42 | let mut event_type = None; 43 | let mut fields = None; 44 | let mut content = None; 45 | 46 | for field_value_inline_struct in 47 | body.parse_terminated::(RumaEventField::parse)? 48 | { 49 | match field_value_inline_struct { 50 | RumaEventField::Block(field_block) => { 51 | let ident = match field_block.member { 52 | Member::Named(ident) => ident, 53 | Member::Unnamed(_) => panic!("fields with block values in `ruma_event!` must named `content_type_alias`"), 54 | }; 55 | 56 | if ident == "content_type_alias" { 57 | content = Some(Content::Typedef(field_block.typedef)); 58 | } 59 | } 60 | RumaEventField::InlineStruct(field_inline_struct) => { 61 | let ident = match field_inline_struct.member { 62 | Member::Named(ident) => ident, 63 | Member::Unnamed(_) => panic!("fields with inline struct values in `ruma_event!` must be named `fields` or `content`."), 64 | }; 65 | 66 | if ident == "fields" { 67 | fields = Some(field_inline_struct.fields); 68 | } else if ident == "content" { 69 | content = Some(Content::Struct(field_inline_struct.fields)); 70 | } 71 | } 72 | RumaEventField::Value(field_value) => { 73 | let ident = match field_value.member { 74 | Member::Named(ident) => ident, 75 | Member::Unnamed(_) => panic!("fields with expression values in `ruma_event!` must be named `kind` or `event_type`, ."), 76 | }; 77 | 78 | if ident == "kind" { 79 | let event_kind = match field_value.expr { 80 | Expr::Path(expr_path) => { 81 | if expr_path.path.is_ident("Event") { 82 | EventKind::Event 83 | } else if expr_path.path.is_ident("RoomEvent") { 84 | EventKind::RoomEvent 85 | } else if expr_path.path.is_ident("StateEvent") { 86 | EventKind::StateEvent 87 | } else { 88 | panic!("value of field `kind` must be one of `Event`, `RoomEvent`, or `StateEvent`"); 89 | } 90 | } 91 | _ => panic!( 92 | "value of field `kind` is required to be an ident by `ruma_event!`" 93 | ), 94 | }; 95 | 96 | kind = Some(event_kind); 97 | } else if ident == "event_type" { 98 | event_type = Some(match field_value.expr { 99 | Expr::Lit(ExprLit { lit: Lit::Str(s), .. }) => s, 100 | // TODO: Span info 101 | _ => panic!( 102 | "value of field `event_type` is required to be a string literal by `ruma_event!`" 103 | ), 104 | }) 105 | } else { 106 | panic!("unexpected field-value pair with field name `{}`", ident); 107 | } 108 | } 109 | } 110 | } 111 | 112 | if kind.is_none() { 113 | panic!("field `kind` is required by `ruma_event!`"); 114 | } else if event_type.is_none() { 115 | panic!("field `event_type` is required by `ruma_event!`"); 116 | } else if content.is_none() { 117 | panic!( 118 | "one field named `content` or `content_type_alias` is required by `ruma_event!`" 119 | ); 120 | } 121 | 122 | Ok(Self { 123 | attrs, 124 | name, 125 | kind: kind.unwrap(), 126 | event_type: event_type.unwrap(), 127 | fields, 128 | content: content.unwrap(), 129 | }) 130 | } 131 | } 132 | 133 | /// Which kind of event is being generated. 134 | /// 135 | /// Determined by the `kind` field in the macro body. 136 | #[derive(PartialEq)] 137 | pub enum EventKind { 138 | /// A basic event. 139 | Event, 140 | 141 | /// A room event. 142 | RoomEvent, 143 | 144 | /// A state event. 145 | StateEvent, 146 | } 147 | 148 | /// Information for generating the type used for the event's `content` field. 149 | pub enum Content { 150 | /// A struct, e.g. `ExampleEventContent { ... }`. 151 | Struct(Vec), 152 | 153 | /// A type alias, e.g. `type ExampleEventContent = SomeExistingType` 154 | Typedef(Typedef), 155 | } 156 | 157 | /// The style of field within the macro body. 158 | #[allow(clippy::large_enum_variant)] 159 | enum RumaEventField { 160 | /// The value of a field is a block with a type alias in it. 161 | /// 162 | /// Used for `content_type_alias`. 163 | Block(FieldBlock), 164 | 165 | /// The value of a field is a block with named struct fields in it. 166 | /// 167 | /// Used for `content`. 168 | InlineStruct(FieldInlineStruct), 169 | 170 | /// A standard named struct field. 171 | /// 172 | /// Used for `kind` and `event_type`. 173 | Value(FieldValue), 174 | } 175 | 176 | impl Parse for RumaEventField { 177 | fn parse(input: ParseStream<'_>) -> parse::Result { 178 | let ahead = input.fork(); 179 | let field_ident: Ident = ahead.parse()?; 180 | 181 | match field_ident.to_string().as_ref() { 182 | "content" | "fields" => { 183 | let attrs = input.call(Attribute::parse_outer)?; 184 | let member = input.parse()?; 185 | let colon_token = input.parse()?; 186 | let body; 187 | braced!(body in input); 188 | let fields = body 189 | .parse_terminated::(Field::parse_named)? 190 | .into_iter() 191 | .collect(); 192 | 193 | Ok(RumaEventField::InlineStruct(FieldInlineStruct { 194 | attrs, 195 | member, 196 | colon_token, 197 | fields, 198 | })) 199 | } 200 | "content_type_alias" => Ok(RumaEventField::Block(FieldBlock { 201 | attrs: input.call(Attribute::parse_outer)?, 202 | member: input.parse()?, 203 | colon_token: input.parse()?, 204 | typedef: input.parse()?, 205 | })), 206 | _ => Ok(RumaEventField::Value(input.parse()?)), 207 | } 208 | } 209 | } 210 | 211 | /// The value of a field is a block with a type alias in it. 212 | /// 213 | /// Used for `content_type_alias`. 214 | struct FieldBlock { 215 | /// Outer attributes on the field, such as a docstring. 216 | pub attrs: Vec, 217 | 218 | /// The name of the field. 219 | pub member: Member, 220 | 221 | /// The colon that appears between the field name and type. 222 | pub colon_token: Colon, 223 | 224 | /// The path to the type that will be used in a type alias for the event's `content` type. 225 | pub typedef: Typedef, 226 | } 227 | 228 | /// The value of a field is a block with named struct fields in it. 229 | /// 230 | /// Used for `content`. 231 | struct FieldInlineStruct { 232 | /// Outer attributes on the field, such as a docstring. 233 | pub attrs: Vec, 234 | 235 | /// The name of the field. 236 | pub member: Member, 237 | 238 | /// The colon that appears between the field name and type. 239 | pub colon_token: Colon, 240 | 241 | /// The fields that define the `content` struct. 242 | pub fields: Vec, 243 | } 244 | 245 | /// Path to a type to be used in a type alias for an event's `content` type. 246 | pub struct Typedef { 247 | /// Outer attributes on the field, such as a docstring. 248 | pub attrs: Vec, 249 | 250 | /// Path to the type. 251 | pub path: TypePath, 252 | } 253 | 254 | impl Parse for Typedef { 255 | fn parse(input: ParseStream<'_>) -> parse::Result { 256 | let body; 257 | braced!(body in input); 258 | 259 | Ok(Self { 260 | attrs: body.call(Attribute::parse_outer)?, 261 | path: body.parse()?, 262 | }) 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [unreleased] 2 | 3 | Breaking changes: 4 | 5 | * Add `alt_aliases` to `CanonicalAliasEventContent` 6 | 7 | # 0.21.3 8 | 9 | Bug fixes: 10 | 11 | * Fix `m.room.message` event serialization 12 | 13 | Improvements: 14 | 15 | * Skip serialization of `federate` field in `room::create::CreateEventContent` 16 | if it is `true` (the default value) 17 | * `room::power_levels::PowerLevelsEventContent` now implements `Default` 18 | 19 | # 0.21.2 20 | 21 | Improvements: 22 | 23 | * Update dependencies 24 | 25 | # 0.21.1 26 | 27 | Improvements: 28 | 29 | * Add `EventJson::into_json` 30 | 31 | # 0.21.0 32 | 33 | Breaking changes: 34 | 35 | * Replace `EventResult` with a new construct, `EventJson` 36 | * Instead of only capturing the json value if deserialization failed, we now 37 | now always capture it. To improve deserialization performance at the same 38 | time, we no longer use `serde_json::Value` internally and instead 39 | deserialize events as `Box`. `EventJson` is 40 | simply a wrapper around that owned value type that additionally holds a 41 | generic argument: the type as which clients will usually want to deserialize 42 | the raw value. 43 | * Add `struct UnsignedData` and update all `unsigned` fields types from 44 | `BTreeMap` to this new type. 45 | * To access any additional fields of the `unsigned` property of an event, 46 | deserialize the `EventJson` to another type that captures the field(s) you 47 | are interested in. 48 | * Add fields `format` and `formatted_body` to `room::message::NoticeMessageEventContent` 49 | * Remove `room::message::MessageType` 50 | * Remove useless `algorithm` fields from encrypted event content structs 51 | * Remove `PartialEq` implementations for most types 52 | * Since we're now using `serde_json::value::RawValue`, deriving no longer works 53 | * Update the representation of `push_rules::Tweak` 54 | * Raise minimum supported Rust version to 1.40.0 55 | 56 | # 0.20.0 57 | 58 | Improvements: 59 | 60 | * Update ruma-identifiers to 0.16.0 61 | 62 | # 0.19.0 63 | 64 | Breaking changes: 65 | 66 | * Update ruma-identifiers to 0.15.1 67 | * Change timestamps, including `origin_server_rs` from `UInt` to `SystemTime` 68 | * Change all usages of `HashMap` to `BTreeMap` 69 | * To support this, `EventType` now implements `PartialOrd` and `Ord` 70 | 71 | # 0.18.0 72 | 73 | Breaking changes: 74 | 75 | * Update unsigned field's type from `Option` to `Map` 76 | 77 | Improvements: 78 | 79 | * Add a convenience constructor to create a plain-text `TextMessageEventContent` 80 | * Add `m.dummy` events to the to-device event collection 81 | 82 | # 0.17.0 83 | 84 | Breaking changes: 85 | 86 | * `collections::only` no longer exports a `raw` submodule. It was never meant ot be exported in the first place. 87 | * Renamed `stripped::{StrippedState => AnyStrippedStateEvent, StrippedStateContent => StrippedStateEvent}` 88 | 89 | Improvements: 90 | 91 | * Added `to_device` module with to-device variants of events (as found in the `to_device` section of a sync response) 92 | * Added a helper method for computing the membership change from a `MemberEvent` 93 | 94 | Bug fixes: 95 | 96 | * Fixed missing `m.` in `m.relates_to` field of room messages 97 | * Fixed (de)serialization of encrypted events using `m.olm.v1.curve25519-aes-sha2` 98 | 99 | # 0.16.0 100 | 101 | Breaking changes: 102 | 103 | * `TryFromRaw::try_from_raw`'s signature has been simplified. The previous signature was a relict that was no longer sensible. 104 | * All remaining non-optional `room_id` event fields (not event content fields) have been made optional 105 | 106 | Improvements: 107 | 108 | * `NameEvent`s are now validated properly and will be rejected if the `name` field is longer than 255 bytes. 109 | 110 | # 0.15.1 111 | 112 | Bug fixes: 113 | 114 | * Deserialization of custom events as part of the types from `ruma_events::collections::{all, only}` was implemented (this was missing after the big fallible deserializion rewrite in 0.15.0) 115 | 116 | # 0.15.0 117 | 118 | Improvements: 119 | 120 | * `ruma-events` now exports a new type, `EventResult` 121 | * For any event or event content type `T` inside a larger type that should support deserialization you can use `EventResult` instead 122 | * Conceptually, it is the same as `Result` 123 | * `InvalidEvent` can represent either a deserialization error (the event's structure did not match) or a validation error (some additional constraints defined in the matrix spec were violated) 124 | * It also contains the original value that was attempted to be deserialized into `T` in `serde_json::Value` form 125 | 126 | Breaking changes: 127 | 128 | * The `FromStr` implementations for event types were removed (they were the previous implementation of fallible deserialization, but were never integrated in ruma-client-api because they didn't interoperate well with serde derives) 129 | 130 | # 0.14.0 131 | 132 | Breaking changes: 133 | 134 | * Updated to ruma-identifiers 0.14.0. 135 | 136 | Improvements: 137 | 138 | * ruma-events is now checked against the RustSec advisory database. 139 | 140 | # 0.13.0 141 | 142 | Breaking changes: 143 | 144 | * Events and their content types no longer implement `Deserialize` and instead implement `FromStr` and `TryFrom<&str>`, which take a `&str` of JSON data and return a new `InvalidEvent` type on error. 145 | * Integers are now represented using the `Int` and `UInt` types from the `js_int` crate to ensure they are within the JavaScript-interoperable range mandated by the Matrix specification. 146 | * Some event types have new fields or new default values for previous fields to bring them up to date with version r0.5.0 of the client-server specification. 147 | * Some event types no longer have public fields and instead use a constructor function to perform validations not represented by the type system. 148 | * All enums now include a "nonexhaustive" variant to prevent exhaustive pattern matching. This will change to use the `#[nonexhaustive]` attribute when it is stabilized. 149 | * `ParseError` has been renamed `FromStrError`. 150 | 151 | New features: 152 | 153 | * This release brings ruma-events completely up to date with version r0.5.0 of the client-server specification. All previously supported events have been updated as necessary and the following events have newly added support: 154 | * m.dummy 155 | * m.forwarded_room_key 156 | * m.fully_read 157 | * m.ignored_user_list 158 | * m.key.verification.accept 159 | * m.key.verification.cancel 160 | * m.key.verification.key 161 | * m.key.verification.mac 162 | * m.key.verification.request 163 | * m.key.verification.start 164 | * m.push_rules 165 | * m.key.encrypted 166 | * m.key.encryption 167 | * m.key.server_acl 168 | * m.key.tombstone 169 | * m.room_key 170 | * m.room_key_request 171 | * m.sticker 172 | 173 | Improvements: 174 | 175 | * Improved documentation for the crate and for many types. 176 | * Added many new tests. 177 | * rustfmt and clippy are now used to ensure consistent formatting and improved code quality. 178 | 179 | # 0.12.0 180 | 181 | Improvements: 182 | 183 | * ruma-events now runs on stable Rust, requiring version 1.34 or higher. 184 | 185 | Bug fixes: 186 | 187 | * `CanonicalAliasEvent` and `NameEvent` now allow content being absent, null, or empty, as per the spec. 188 | 189 | # 0.11.1 190 | 191 | Breaking changes: 192 | 193 | * `RoomId` is now optional in certain places where it may be absent, notably the responses of the `/sync` API endpoint. 194 | * A `sender` field has been added to the `StrippedStateContent` type. 195 | 196 | Improvements: 197 | 198 | * Depend on serde's derive feature rather than serde_derive directly for simplified imports. 199 | * Update to Rust 2018 idioms. 200 | 201 | # 0.11.0 202 | 203 | Breaking changes: 204 | 205 | * The presence event has been modified to match the latest version of the spec. The spec was corrected to match the behavior of the Synapse homeserver. 206 | 207 | Improvements: 208 | 209 | * Dependencies have been updated to the latest versions. 210 | 211 | # 0.10.0 212 | 213 | Breaking changes: 214 | 215 | * The `EventType`, and collections enums have new variants to support new events. 216 | * The `extra_content` method has been removed from the Event trait. 217 | * The `user_id` method from the `RoomEvent` trait has been renamed `sender` to match the specification. 218 | * The `origin_server_ts` value is now required for room events and is supported via a new `origin_server_ts` method on the `RoomEvent` trait. 219 | * `MemberEventContent` has a new `is_direct` field. 220 | * `FileMessageEventContent` has a new `filename` field. 221 | * File and thumbnail info have been moved from several message types to dedicated `FileInfo`, `ImageInfo`, and `ThumbnailInfo` types. 222 | * `LocationMessageEventContent` has a new info field. 223 | * `PresenceEventContent`'s `currently_active` field has changed from `bool` to `Option`. 224 | * `TypingEventContent` contains a vector of `UserId`s instead of `EventId`s. 225 | * Height and width fields named `h` and `w` in the spec now use the full names `height` and `width` for their struct field names, but continue to serialize to the single-letter names. 226 | 227 | New features: 228 | 229 | * ruma-events now supports all events according to r0.3.0 of the Matrix client-server specification. 230 | * Added new event: `m.room.pinned_events`. 231 | * Added new event: `m.direct`. 232 | 233 | Bug fixes: 234 | 235 | * Several places where struct fields used the wrong key when serialized to JSON have been corrected. 236 | * Fixed grammar issues in documentation. 237 | 238 | # 0.9.0 239 | 240 | Improvements: 241 | 242 | * Added default values for various power level attributes. 243 | * Removed Serde trait bounds on `StrippedStateContent`'s generic parameter. 244 | * Updated to version 0.4 of ruma-signatures. 245 | 246 | # 0.8.0 247 | 248 | Breaking changes 249 | 250 | * Updated serde to the 1.0 series. 251 | 252 | # 0.7.0 253 | 254 | Bug fixes: 255 | 256 | * Make the `federate` field optional when creating a room. 257 | 258 | # 0.6.0 259 | 260 | Breaking changes: 261 | 262 | * Updated ruma-identifiers to the 0.9 series. 263 | 264 | 265 | # 0.5.0 266 | 267 | Breaking changes: 268 | 269 | * Updated ruma-identifiers to the 0.8 series. 270 | 271 | # 0.4.1 272 | 273 | Improvements: 274 | 275 | * Relaxed version constraints on dependent crates to allow updating to new patch level versions. 276 | 277 | # 0.4.0 278 | 279 | Breaking changes: 280 | 281 | * Updated serde to the 0.9 series. 282 | 283 | The public API remains the same. 284 | 285 | # 0.3.0 286 | 287 | Improvements: 288 | 289 | * `ruma_events::presence::PresenceState` now implements `Display` and `FromStr`. 290 | 291 | # 0.2.0 292 | 293 | Improvements: 294 | 295 | * Added missing "stripped" versions of some state events. 296 | * All "stripped" versions of state events are now serializable. 297 | 298 | 299 | # 0.1.0 300 | 301 | Initial release. 302 | -------------------------------------------------------------------------------- /src/room/power_levels.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.room.power_levels* event. 2 | 3 | use std::collections::BTreeMap; 4 | 5 | use js_int::Int; 6 | use ruma_events_macros::ruma_event; 7 | use ruma_identifiers::UserId; 8 | use serde::{Deserialize, Serialize}; 9 | 10 | use crate::EventType; 11 | 12 | ruma_event! { 13 | /// Defines the power levels (privileges) of users in the room. 14 | PowerLevelsEvent { 15 | kind: StateEvent, 16 | event_type: "m.room.power_levels", 17 | content: { 18 | /// The level required to ban a user. 19 | #[serde( 20 | default = "default_power_level", 21 | skip_serializing_if = "is_default_power_level" 22 | )] 23 | pub ban: Int, 24 | 25 | /// The level required to send specific event types. 26 | /// 27 | /// This is a mapping from event type to power level required. 28 | #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] 29 | pub events: BTreeMap, 30 | 31 | /// The default level required to send message events. 32 | #[serde(default, skip_serializing_if = "ruma_serde::is_default")] 33 | pub events_default: Int, 34 | 35 | /// The level required to invite a user. 36 | #[serde( 37 | default = "default_power_level", 38 | skip_serializing_if = "is_default_power_level" 39 | )] 40 | pub invite: Int, 41 | 42 | /// The level required to kick a user. 43 | #[serde( 44 | default = "default_power_level", 45 | skip_serializing_if = "is_default_power_level" 46 | )] 47 | pub kick: Int, 48 | 49 | /// The level required to redact an event. 50 | #[serde( 51 | default = "default_power_level", 52 | skip_serializing_if = "is_default_power_level" 53 | )] 54 | pub redact: Int, 55 | 56 | /// The default level required to send state events. 57 | #[serde( 58 | default = "default_power_level", 59 | skip_serializing_if = "is_default_power_level" 60 | )] 61 | pub state_default: Int, 62 | 63 | /// The power levels for specific users. 64 | /// 65 | /// This is a mapping from `user_id` to power level for that user. 66 | #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] 67 | pub users: BTreeMap, 68 | 69 | /// The default power level for every user in the room. 70 | #[serde(default, skip_serializing_if = "ruma_serde::is_default")] 71 | pub users_default: Int, 72 | 73 | /// The power level requirements for specific notification types. 74 | /// 75 | /// This is a mapping from `key` to power level for that notifications key. 76 | #[serde(default, skip_serializing_if = "ruma_serde::is_default")] 77 | pub notifications: NotificationPowerLevels, 78 | }, 79 | } 80 | } 81 | 82 | impl Default for PowerLevelsEventContent { 83 | fn default() -> Self { 84 | // events_default and users_default having a default of 0 while the others have a default 85 | // of 50 is not an oversight, these defaults are from the Matrix specification. 86 | Self { 87 | ban: default_power_level(), 88 | events: BTreeMap::new(), 89 | events_default: Int::default(), 90 | invite: default_power_level(), 91 | kick: default_power_level(), 92 | redact: default_power_level(), 93 | state_default: default_power_level(), 94 | users: BTreeMap::new(), 95 | users_default: Int::default(), 96 | notifications: NotificationPowerLevels::default(), 97 | } 98 | } 99 | } 100 | 101 | /// The power level requirements for specific notification types. 102 | #[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)] 103 | pub struct NotificationPowerLevels { 104 | /// The level required to trigger an `@room` notification. 105 | #[serde(default = "default_power_level")] 106 | pub room: Int, 107 | } 108 | 109 | impl Default for NotificationPowerLevels { 110 | fn default() -> Self { 111 | Self { 112 | room: default_power_level(), 113 | } 114 | } 115 | } 116 | 117 | /// Used to default power levels to 50 during deserialization. 118 | fn default_power_level() -> Int { 119 | Int::from(50) 120 | } 121 | 122 | /// Used with #[serde(skip_serializing_if)] to omit default power levels. 123 | #[allow(clippy::trivially_copy_pass_by_ref)] 124 | fn is_default_power_level(l: &Int) -> bool { 125 | *l == Int::from(50) 126 | } 127 | 128 | #[cfg(test)] 129 | mod tests { 130 | use std::{ 131 | collections::BTreeMap, 132 | convert::TryFrom, 133 | time::{Duration, UNIX_EPOCH}, 134 | }; 135 | 136 | use js_int::Int; 137 | use maplit::btreemap; 138 | use ruma_identifiers::{EventId, RoomId, UserId}; 139 | use serde_json::{json, to_value as to_json_value}; 140 | 141 | use super::{ 142 | default_power_level, NotificationPowerLevels, PowerLevelsEvent, PowerLevelsEventContent, 143 | }; 144 | use crate::{EventType, UnsignedData}; 145 | 146 | #[test] 147 | fn serialization_with_optional_fields_as_none() { 148 | let default = default_power_level(); 149 | 150 | let power_levels_event = PowerLevelsEvent { 151 | content: PowerLevelsEventContent { 152 | ban: default, 153 | events: BTreeMap::new(), 154 | events_default: Int::from(0), 155 | invite: default, 156 | kick: default, 157 | redact: default, 158 | state_default: default, 159 | users: BTreeMap::new(), 160 | users_default: Int::from(0), 161 | notifications: NotificationPowerLevels::default(), 162 | }, 163 | event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(), 164 | origin_server_ts: UNIX_EPOCH + Duration::from_millis(1), 165 | prev_content: None, 166 | room_id: None, 167 | unsigned: UnsignedData::default(), 168 | sender: UserId::try_from("@carl:example.com").unwrap(), 169 | state_key: "".to_string(), 170 | }; 171 | 172 | let actual = to_json_value(&power_levels_event).unwrap(); 173 | let expected = json!({ 174 | "content": {}, 175 | "event_id": "$h29iv0s8:example.com", 176 | "origin_server_ts": 1, 177 | "sender": "@carl:example.com", 178 | "state_key": "", 179 | "type": "m.room.power_levels" 180 | }); 181 | 182 | assert_eq!(actual, expected); 183 | } 184 | 185 | #[test] 186 | fn serialization_with_all_fields() { 187 | let user = UserId::try_from("@carl:example.com").unwrap(); 188 | let power_levels_event = PowerLevelsEvent { 189 | content: PowerLevelsEventContent { 190 | ban: Int::from(23), 191 | events: btreemap! { 192 | EventType::Dummy => Int::from(23) 193 | }, 194 | events_default: Int::from(23), 195 | invite: Int::from(23), 196 | kick: Int::from(23), 197 | redact: Int::from(23), 198 | state_default: Int::from(23), 199 | users: btreemap! { 200 | user.clone() => Int::from(23) 201 | }, 202 | users_default: Int::from(23), 203 | notifications: NotificationPowerLevels { 204 | room: Int::from(23), 205 | }, 206 | }, 207 | event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(), 208 | origin_server_ts: UNIX_EPOCH + Duration::from_millis(1), 209 | prev_content: Some(PowerLevelsEventContent { 210 | // Make just one field different so we at least know they're two different objects. 211 | ban: Int::from(42), 212 | events: btreemap! { 213 | EventType::Dummy => Int::from(42) 214 | }, 215 | events_default: Int::from(42), 216 | invite: Int::from(42), 217 | kick: Int::from(42), 218 | redact: Int::from(42), 219 | state_default: Int::from(42), 220 | users: btreemap! { 221 | user.clone() => Int::from(42) 222 | }, 223 | users_default: Int::from(42), 224 | notifications: NotificationPowerLevels { 225 | room: Int::from(42), 226 | }, 227 | }), 228 | room_id: Some(RoomId::try_from("!n8f893n9:example.com").unwrap()), 229 | unsigned: UnsignedData { 230 | age: Some(Int::from(100)), 231 | ..UnsignedData::default() 232 | }, 233 | sender: user, 234 | state_key: "".to_string(), 235 | }; 236 | 237 | let actual = to_json_value(&power_levels_event).unwrap(); 238 | let expected = json!({ 239 | "content": { 240 | "ban": 23, 241 | "events": { 242 | "m.dummy": 23 243 | }, 244 | "events_default": 23, 245 | "invite": 23, 246 | "kick": 23, 247 | "redact": 23, 248 | "state_default": 23, 249 | "users": { 250 | "@carl:example.com": 23 251 | }, 252 | "users_default": 23, 253 | "notifications": { 254 | "room": 23 255 | } 256 | }, 257 | "event_id": "$h29iv0s8:example.com", 258 | "origin_server_ts": 1, 259 | "prev_content": { 260 | "ban": 42, 261 | "events": { 262 | "m.dummy": 42 263 | }, 264 | "events_default": 42, 265 | "invite": 42, 266 | "kick": 42, 267 | "redact": 42, 268 | "state_default": 42, 269 | "users": { 270 | "@carl:example.com": 42 271 | }, 272 | "users_default": 42, 273 | "notifications": { 274 | "room": 42 275 | } 276 | }, 277 | "room_id": "!n8f893n9:example.com", 278 | "sender": "@carl:example.com", 279 | "state_key": "", 280 | "type": "m.room.power_levels", 281 | "unsigned": { 282 | "age": 100 283 | } 284 | }); 285 | 286 | assert_eq!(actual, expected); 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Crate `ruma_events` contains serializable types for the events in the [Matrix](https://matrix.org) 2 | //! specification that can be shared by client and server code. 3 | //! 4 | //! All data exchanged over Matrix is expressed as an event. 5 | //! Different event types represent different actions, such as joining a room or sending a message. 6 | //! Events are stored and transmitted as simple JSON structures. 7 | //! While anyone can create a new event type for their own purposes, the Matrix specification 8 | //! defines a number of event types which are considered core to the protocol, and Matrix clients 9 | //! and servers must understand their semantics. 10 | //! ruma-events contains Rust types for each of the event types defined by the specification and 11 | //! facilities for extending the event system for custom event types. 12 | //! 13 | //! # Event types 14 | //! 15 | //! ruma-events includes a Rust enum called `EventType`, which provides a simple enumeration of 16 | //! all the event types defined by the Matrix specification. Matrix event types are serialized to 17 | //! JSON strings in [reverse domain name 18 | //! notation](https://en.wikipedia.org/wiki/Reverse_domain_name_notation), although the core event 19 | //! types all use the special "m" TLD, e.g. *m.room.message*. 20 | //! `EventType` also includes a variant called `Custom`, which is a catch-all that stores a string 21 | //! containing the name of any event type that isn't part of the specification. 22 | //! `EventType` is used throughout ruma-events to identify and differentiate between events of 23 | //! different types. 24 | //! 25 | //! # Event kinds 26 | //! 27 | //! Matrix defines three "kinds" of events: 28 | //! 29 | //! 1. **Events**, which are arbitrary JSON structures that have two required keys: 30 | //! * `type`, which specifies the event's type 31 | //! * `content`, which is a JSON object containing the "payload" of the event 32 | //! 2. **Room events**, which are a superset of events and represent actions that occurred within 33 | //! the context of a Matrix room. 34 | //! They have at least the following additional keys: 35 | //! * `event_id`, which is a unique identifier for the event 36 | //! * `room_id`, which is a unique identifier for the room in which the event occurred 37 | //! * `sender`, which is the unique identifier of the Matrix user who created the event 38 | //! * Optionally, `unsigned`, which is a JSON object containing arbitrary additional metadata 39 | //! that is not digitally signed by Matrix homeservers. 40 | //! 3. **State events**, which are a superset of room events and represent persistent state 41 | //! specific to a room, such as the room's member list or topic. 42 | //! Within a single room, state events of the same type and with the same "state key" will 43 | //! effectively "replace" the previous one, updating the room's state. 44 | //! They have at least the following additional keys: 45 | //! * `state_key`, a string which serves as a sort of "sub-type." 46 | //! The state key allows a room to persist multiple state events of the same type. 47 | //! You can think of a room's state events as being a `BTreeMap` where the keys are the tuple 48 | //! `(event_type, state_key)`. 49 | //! * Optionally, `prev_content`, a JSON object containing the `content` object from the 50 | //! previous event of the given `(event_type, state_key)` tuple in the given room. 51 | //! 52 | //! ruma-events represents these three event kinds as traits, allowing any Rust type to serve as a 53 | //! Matrix event so long as it upholds the contract expected of its kind. 54 | //! 55 | //! # Core event types 56 | //! 57 | //! ruma-events includes Rust types for every one of the event types in the Matrix specification. 58 | //! To better organize the crate, these types live in separate modules with a hierarchy that 59 | //! matches the reverse domain name notation of the event type. 60 | //! For example, the *m.room.message* event lives at `ruma_events::room::message::MessageEvent`. 61 | //! Each type's module also contains a Rust type for that event type's `content` field, and any 62 | //! other supporting types required by the event's other fields. 63 | //! 64 | //! # Custom event types 65 | //! 66 | //! Although any Rust type that implements `Event`, `RoomEvent`, or `StateEvent` can serve as a 67 | //! Matrix event type, ruma-events also includes a few convenience types for representing events 68 | //! that are not covered by the spec and not otherwise known by the application. 69 | //! `CustomEvent`, `CustomRoomEvent`, and `CustomStateEvent` are simple implementations of their 70 | //! respective event traits whose `content` field is simply a `serde_json::Value` value, which 71 | //! represents arbitrary JSON. 72 | //! 73 | //! # Serialization and deserialization 74 | //! 75 | //! All concrete event types in ruma-events can be serialized via the `Serialize` trait from 76 | //! [serde](https://serde.rs/) and can be deserialized from as `EventJson`. In order to 77 | //! handle incoming data that may not conform to `ruma-events`' strict definitions of event 78 | //! structures, deserialization will return `EventJson::Err` on error. This error covers both 79 | //! structurally invalid JSON data as well as structurally valid JSON that doesn't fulfill 80 | //! additional constraints the matrix specification defines for some event types. The error exposes 81 | //! the deserialized `serde_json::Value` so that developers can still work with the received 82 | //! event data. This makes it possible to deserialize a collection of events without the entire 83 | //! collection failing to deserialize due to a single invalid event. The "content" type for each 84 | //! event also implements `Serialize` and either `TryFromRaw` (enabling usage as 85 | //! `EventJson` for dedicated content types) or `Deserialize` (when the content is a 86 | //! type alias), allowing content to be converted to and from JSON indepedently of the surrounding 87 | //! event structure, if needed. 88 | //! 89 | //! # Collections 90 | //! 91 | //! With the trait-based approach to events, it's easy to write generic collection types like 92 | //! `Vec>`. 93 | //! However, there are APIs in the Matrix specification that involve heterogeneous collections of 94 | //! events, i.e. a list of events of different event types. 95 | //! Because Rust does not have a facility for arrays, vectors, or slices containing multiple 96 | //! concrete types, ruma-events provides special collection types for this purpose. 97 | //! The collection types are enums which effectively "wrap" each possible event type of a 98 | //! particular event "kind." 99 | //! 100 | //! Because of the hierarchical nature of event kinds in Matrix, these collection types are divied 101 | //! into two modules, `ruma_events::collections::all` and `ruma_events::collections::only`. 102 | //! The "all" versions include every event type that implements the relevant event trait as well as 103 | //! more specific event traits. 104 | //! The "only" versions include only the event types that implement "at most" the relevant event 105 | //! trait. 106 | //! 107 | //! For example, the `ruma_events::collections::all::Event` enum includes *m.room.message*, because 108 | //! that event type is both an event and a room event. 109 | //! However, the `ruma_events::collections::only::Event` enum does *not* include *m.room.message*, 110 | //! because *m.room.message* implements a *more specific* event trait than `Event`. 111 | 112 | #![recursion_limit = "1024"] 113 | #![warn(missing_debug_implementations, missing_docs, rust_2018_idioms)] 114 | // Since we support Rust 1.36.0, we can't apply this suggestion yet 115 | #![allow(clippy::use_self)] 116 | 117 | use std::{fmt::Debug, time::SystemTime}; 118 | 119 | use js_int::Int; 120 | use ruma_identifiers::{EventId, RoomId, UserId}; 121 | use serde::{Deserialize, Serialize}; 122 | 123 | use self::room::redaction::RedactionEvent; 124 | 125 | pub use self::custom::{CustomEvent, CustomRoomEvent, CustomStateEvent}; 126 | 127 | #[deprecated = "Use ruma_serde::empty::Empty directly instead."] 128 | pub use ruma_serde::empty::Empty; 129 | 130 | #[macro_use] 131 | mod macros; 132 | 133 | mod algorithm; 134 | mod error; 135 | mod event_type; 136 | mod from_raw; 137 | mod json; 138 | #[doc(hidden)] // only public for external tests 139 | pub mod util; 140 | 141 | // Hack to allow both ruma-events itself and external crates (or tests) to use procedural macros 142 | // that expect `ruma_events` to exist in the prelude. 143 | extern crate self as ruma_events; 144 | 145 | pub mod call; 146 | pub mod custom; 147 | /// Enums for heterogeneous collections of events. 148 | pub mod collections { 149 | pub mod all; 150 | pub mod only; 151 | 152 | mod raw { 153 | pub mod all; 154 | pub mod only; 155 | } 156 | } 157 | pub mod direct; 158 | pub mod dummy; 159 | pub mod forwarded_room_key; 160 | pub mod fully_read; 161 | pub mod ignored_user_list; 162 | pub mod key; 163 | pub mod presence; 164 | pub mod push_rules; 165 | pub mod receipt; 166 | pub mod room; 167 | pub mod room_key; 168 | pub mod room_key_request; 169 | pub mod sticker; 170 | pub mod stripped; 171 | pub mod tag; 172 | pub mod to_device; 173 | pub mod typing; 174 | 175 | pub use self::{ 176 | algorithm::Algorithm, 177 | error::{FromStrError, InvalidEvent, InvalidInput}, 178 | event_type::EventType, 179 | from_raw::{FromRaw, TryFromRaw}, 180 | json::EventJson, 181 | }; 182 | 183 | /// A basic event. 184 | pub trait Event: Debug + Serialize + Sized + TryFromRaw { 185 | /// The type of this event's `content` field. 186 | type Content: Debug + Serialize; 187 | 188 | /// The event's content. 189 | fn content(&self) -> &Self::Content; 190 | 191 | /// The type of the event. 192 | fn event_type(&self) -> EventType; 193 | } 194 | 195 | /// An event within the context of a room. 196 | pub trait RoomEvent: Event { 197 | /// The unique identifier for the event. 198 | fn event_id(&self) -> &EventId; 199 | 200 | /// Time on originating homeserver when this event was sent. 201 | fn origin_server_ts(&self) -> SystemTime; 202 | 203 | /// The unique identifier for the room associated with this event. 204 | /// 205 | /// This can be `None` if the event came from a context where there is 206 | /// no ambiguity which room it belongs to, like a `/sync` response for example. 207 | fn room_id(&self) -> Option<&RoomId>; 208 | 209 | /// The unique identifier for the user who sent this event. 210 | fn sender(&self) -> &UserId; 211 | 212 | /// Additional key-value pairs not signed by the homeserver. 213 | fn unsigned(&self) -> &UnsignedData; 214 | } 215 | 216 | /// An event that describes persistent state about a room. 217 | pub trait StateEvent: RoomEvent { 218 | /// The previous content for this state key, if any. 219 | fn prev_content(&self) -> Option<&Self::Content>; 220 | 221 | /// A key that determines which piece of room state the event represents. 222 | fn state_key(&self) -> &str; 223 | } 224 | 225 | /// Extra information about an event that is not incorporated into the event's 226 | /// hash. 227 | #[derive(Clone, Debug, Default, Deserialize, Serialize)] 228 | pub struct UnsignedData { 229 | /// The time in milliseconds that has elapsed since the event was sent. This 230 | /// field is generated by the local homeserver, and may be incorrect if the 231 | /// local time on at least one of the two servers is out of sync, which can 232 | /// cause the age to either be negative or greater than it actually is. 233 | #[serde(skip_serializing_if = "Option::is_none")] 234 | pub age: Option, 235 | 236 | /// The event that redacted this event, if any. 237 | #[serde(skip_serializing_if = "Option::is_none")] 238 | pub redacted_because: Option>, 239 | 240 | /// The client-supplied transaction ID, if the client being given the event 241 | /// is the same one which sent it. 242 | #[serde(skip_serializing_if = "Option::is_none")] 243 | pub transaction_id: Option, 244 | } 245 | 246 | impl UnsignedData { 247 | /// Whether this unsigned data is empty (all fields are `None`). 248 | /// 249 | /// This method is used to determine whether to skip serializing the 250 | /// `unsigned` field in room events. Do not use it to determine whether 251 | /// an incoming `unsigned` field was present - it could still have been 252 | /// present but contained none of the known fields. 253 | pub fn is_empty(&self) -> bool { 254 | self.age.is_none() && self.redacted_because.is_none() && self.transaction_id.is_none() 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /src/room/name.rs: -------------------------------------------------------------------------------- 1 | //! Types for the *m.room.name* event. 2 | 3 | use std::time::SystemTime; 4 | 5 | use ruma_identifiers::{EventId, RoomId, UserId}; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | use crate::{EventType, InvalidInput, TryFromRaw, UnsignedData}; 9 | 10 | /// A human-friendly room name designed to be displayed to the end-user. 11 | #[derive(Clone, Debug, Serialize)] 12 | #[serde(rename = "m.room.name", tag = "type")] 13 | pub struct NameEvent { 14 | /// The event's content. 15 | pub content: NameEventContent, 16 | 17 | /// The unique identifier for the event. 18 | pub event_id: EventId, 19 | 20 | /// Time on originating homeserver when this event was sent. 21 | #[serde(with = "ruma_serde::time::ms_since_unix_epoch")] 22 | pub origin_server_ts: SystemTime, 23 | 24 | /// The previous content for this state key, if any. 25 | #[serde(skip_serializing_if = "Option::is_none")] 26 | pub prev_content: Option, 27 | 28 | /// The unique identifier for the room associated with this event. 29 | #[serde(skip_serializing_if = "Option::is_none")] 30 | pub room_id: Option, 31 | 32 | /// The unique identifier for the user who sent this event. 33 | pub sender: UserId, 34 | 35 | /// A key that determines which piece of room state the event represents. 36 | pub state_key: String, 37 | 38 | /// Additional key-value pairs not signed by the homeserver. 39 | #[serde(skip_serializing_if = "UnsignedData::is_empty")] 40 | pub unsigned: UnsignedData, 41 | } 42 | 43 | /// The payload for `NameEvent`. 44 | #[derive(Clone, Debug, Serialize)] 45 | pub struct NameEventContent { 46 | /// The name of the room. This MUST NOT exceed 255 bytes. 47 | pub(crate) name: Option, 48 | } 49 | 50 | impl TryFromRaw for NameEvent { 51 | type Raw = raw::NameEvent; 52 | type Err = InvalidInput; 53 | 54 | fn try_from_raw(raw: Self::Raw) -> Result { 55 | let content = TryFromRaw::try_from_raw(raw.content)?; 56 | let prev_content = raw.prev_content.map(TryFromRaw::try_from_raw).transpose()?; 57 | 58 | Ok(NameEvent { 59 | content, 60 | event_id: raw.event_id, 61 | origin_server_ts: raw.origin_server_ts, 62 | prev_content, 63 | room_id: raw.room_id, 64 | sender: raw.sender, 65 | state_key: raw.state_key, 66 | unsigned: raw.unsigned, 67 | }) 68 | } 69 | } 70 | 71 | impl TryFromRaw for NameEventContent { 72 | type Raw = raw::NameEventContent; 73 | 74 | type Err = InvalidInput; 75 | 76 | fn try_from_raw(raw: raw::NameEventContent) -> Result { 77 | match raw.name { 78 | None => Ok(NameEventContent { name: None }), 79 | Some(name) => NameEventContent::new(name), 80 | } 81 | } 82 | } 83 | 84 | impl_state_event!(NameEvent, NameEventContent, EventType::RoomName); 85 | 86 | impl NameEventContent { 87 | /// Create a new `NameEventContent` with the given name. 88 | /// 89 | /// # Errors 90 | /// 91 | /// `InvalidInput` will be returned if the name is more than 255 bytes. 92 | pub fn new(name: String) -> Result { 93 | match name.len() { 94 | 0 => Ok(Self { name: None }), 95 | 1..=255 => Ok(Self { name: Some(name) }), 96 | _ => Err(InvalidInput( 97 | "a room name cannot be more than 255 bytes".to_string(), 98 | )), 99 | } 100 | } 101 | 102 | /// The name of the room, if any. 103 | pub fn name(&self) -> Option<&str> { 104 | self.name.as_ref().map(String::as_ref) 105 | } 106 | } 107 | 108 | pub(crate) mod raw { 109 | use super::*; 110 | 111 | /// A human-friendly room name designed to be displayed to the end-user. 112 | #[derive(Clone, Debug, Deserialize)] 113 | pub struct NameEvent { 114 | /// The event's content. 115 | pub content: NameEventContent, 116 | 117 | /// The unique identifier for the event. 118 | pub event_id: EventId, 119 | 120 | /// Time on originating homeserver when this event was sent. 121 | #[serde(with = "ruma_serde::time::ms_since_unix_epoch")] 122 | pub origin_server_ts: SystemTime, 123 | 124 | /// The previous content for this state key, if any. 125 | pub prev_content: Option, 126 | 127 | /// The unique identifier for the room associated with this event. 128 | pub room_id: Option, 129 | 130 | /// The unique identifier for the user who sent this event. 131 | pub sender: UserId, 132 | 133 | /// A key that determines which piece of room state the event represents. 134 | pub state_key: String, 135 | 136 | /// Additional key-value pairs not signed by the homeserver. 137 | #[serde(default)] 138 | pub unsigned: UnsignedData, 139 | } 140 | 141 | /// The payload of a `NameEvent`. 142 | #[derive(Clone, Debug, Deserialize)] 143 | pub struct NameEventContent { 144 | /// The name of the room. This MUST NOT exceed 255 bytes. 145 | // The spec says "A room with an m.room.name event with an absent, null, or empty name field 146 | // should be treated the same as a room with no m.room.name event." 147 | #[serde(default, deserialize_with = "ruma_serde::empty_string_as_none")] 148 | pub(crate) name: Option, 149 | } 150 | } 151 | 152 | #[cfg(test)] 153 | mod tests { 154 | use std::{ 155 | convert::TryFrom, 156 | iter::FromIterator, 157 | time::{Duration, UNIX_EPOCH}, 158 | }; 159 | 160 | use js_int::Int; 161 | use matches::assert_matches; 162 | use ruma_identifiers::{EventId, RoomId, UserId}; 163 | use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; 164 | 165 | use crate::{EventJson, UnsignedData}; 166 | 167 | use super::{NameEvent, NameEventContent}; 168 | 169 | #[test] 170 | fn serialization_with_optional_fields_as_none() { 171 | let name_event = NameEvent { 172 | content: NameEventContent { 173 | name: Some("The room name".to_string()), 174 | }, 175 | event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(), 176 | origin_server_ts: UNIX_EPOCH + Duration::from_millis(1), 177 | prev_content: None, 178 | room_id: None, 179 | sender: UserId::try_from("@carl:example.com").unwrap(), 180 | state_key: "".to_string(), 181 | unsigned: UnsignedData::default(), 182 | }; 183 | 184 | let actual = to_json_value(&name_event).unwrap(); 185 | let expected = json!({ 186 | "content": { 187 | "name": "The room name" 188 | }, 189 | "event_id": "$h29iv0s8:example.com", 190 | "origin_server_ts": 1, 191 | "sender": "@carl:example.com", 192 | "state_key": "", 193 | "type": "m.room.name" 194 | }); 195 | 196 | assert_eq!(actual, expected); 197 | } 198 | 199 | #[test] 200 | fn serialization_with_all_fields() { 201 | let name_event = NameEvent { 202 | content: NameEventContent { 203 | name: Some("The room name".to_string()), 204 | }, 205 | event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(), 206 | origin_server_ts: UNIX_EPOCH + Duration::from_millis(1), 207 | prev_content: Some(NameEventContent { 208 | name: Some("The old name".to_string()), 209 | }), 210 | room_id: Some(RoomId::try_from("!n8f893n9:example.com").unwrap()), 211 | sender: UserId::try_from("@carl:example.com").unwrap(), 212 | state_key: "".to_string(), 213 | unsigned: UnsignedData { 214 | age: Some(Int::from(100)), 215 | ..UnsignedData::default() 216 | }, 217 | }; 218 | 219 | let actual = to_json_value(&name_event).unwrap(); 220 | let expected = json!({ 221 | "content": { 222 | "name": "The room name" 223 | }, 224 | "event_id": "$h29iv0s8:example.com", 225 | "origin_server_ts": 1, 226 | "prev_content": { "name": "The old name" }, 227 | "room_id": "!n8f893n9:example.com", 228 | "sender": "@carl:example.com", 229 | "state_key": "", 230 | "type": "m.room.name", 231 | "unsigned": { 232 | "age": 100 233 | } 234 | }); 235 | 236 | assert_eq!(actual, expected); 237 | } 238 | 239 | #[test] 240 | fn absent_field_as_none() { 241 | let json_data = json!({ 242 | "content": {}, 243 | "event_id": "$h29iv0s8:example.com", 244 | "origin_server_ts": 1, 245 | "sender": "@carl:example.com", 246 | "state_key": "", 247 | "type": "m.room.name" 248 | }); 249 | assert_eq!( 250 | from_json_value::>(json_data) 251 | .unwrap() 252 | .deserialize() 253 | .unwrap() 254 | .content 255 | .name, 256 | None 257 | ); 258 | } 259 | 260 | #[test] 261 | fn name_fails_validation_when_too_long() { 262 | // "XXXX .." 256 times 263 | let long_string: String = String::from_iter(std::iter::repeat('X').take(256)); 264 | assert_eq!(long_string.len(), 256); 265 | 266 | let long_content_json = json!({ "name": &long_string }); 267 | let from_raw: EventJson = from_json_value(long_content_json).unwrap(); 268 | 269 | let result = from_raw.deserialize(); 270 | assert!(result.is_err(), "Result should be invalid: {:?}", result); 271 | } 272 | 273 | #[test] 274 | fn json_with_empty_name_creates_content_as_none() { 275 | let long_content_json = json!({ "name": "" }); 276 | let from_raw: EventJson = from_json_value(long_content_json).unwrap(); 277 | assert_matches!( 278 | from_raw.deserialize().unwrap(), 279 | NameEventContent { name: None } 280 | ); 281 | } 282 | 283 | #[test] 284 | fn new_with_empty_name_creates_content_as_none() { 285 | assert_matches!( 286 | NameEventContent::new(String::new()).unwrap(), 287 | NameEventContent { name: None } 288 | ); 289 | } 290 | 291 | #[test] 292 | fn null_field_as_none() { 293 | let json_data = json!({ 294 | "content": { 295 | "name": null 296 | }, 297 | "event_id": "$h29iv0s8:example.com", 298 | "origin_server_ts": 1, 299 | "sender": "@carl:example.com", 300 | "state_key": "", 301 | "type": "m.room.name" 302 | }); 303 | assert_eq!( 304 | from_json_value::>(json_data) 305 | .unwrap() 306 | .deserialize() 307 | .unwrap() 308 | .content 309 | .name, 310 | None 311 | ); 312 | } 313 | 314 | #[test] 315 | fn empty_string_as_none() { 316 | let json_data = json!({ 317 | "content": { 318 | "name": "" 319 | }, 320 | "event_id": "$h29iv0s8:example.com", 321 | "origin_server_ts": 1, 322 | "sender": "@carl:example.com", 323 | "state_key": "", 324 | "type": "m.room.name" 325 | }); 326 | assert_eq!( 327 | from_json_value::>(json_data) 328 | .unwrap() 329 | .deserialize() 330 | .unwrap() 331 | .content 332 | .name, 333 | None 334 | ); 335 | } 336 | 337 | #[test] 338 | fn nonempty_field_as_some() { 339 | let name = Some("The room name".to_string()); 340 | let json_data = json!({ 341 | "content": { 342 | "name": "The room name" 343 | }, 344 | "event_id": "$h29iv0s8:example.com", 345 | "origin_server_ts": 1, 346 | "sender": "@carl:example.com", 347 | "state_key": "", 348 | "type": "m.room.name" 349 | }); 350 | 351 | assert_eq!( 352 | from_json_value::>(json_data) 353 | .unwrap() 354 | .deserialize() 355 | .unwrap() 356 | .content 357 | .name, 358 | name 359 | ); 360 | } 361 | } 362 | -------------------------------------------------------------------------------- /src/event_type.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{Display, Formatter, Result as FmtResult}; 2 | 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// The type of an event. 6 | #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] 7 | #[non_exhaustive] 8 | #[serde(from = "String", into = "String")] 9 | pub enum EventType { 10 | /// m.call.answer 11 | CallAnswer, 12 | 13 | /// m.call.candidates 14 | CallCandidates, 15 | 16 | /// m.call.hangup 17 | CallHangup, 18 | 19 | /// m.call.invite 20 | CallInvite, 21 | 22 | /// m.direct 23 | Direct, 24 | 25 | /// m.dummy 26 | Dummy, 27 | 28 | /// m.forwarded_room_key 29 | ForwardedRoomKey, 30 | 31 | /// m.fully_read 32 | FullyRead, 33 | 34 | /// m.key.verification.accept 35 | KeyVerificationAccept, 36 | 37 | /// m.key.verification.cancel 38 | KeyVerificationCancel, 39 | 40 | /// m.key.verification.key 41 | KeyVerificationKey, 42 | 43 | /// m.key.verification.mac 44 | KeyVerificationMac, 45 | 46 | /// m.key.verification.request 47 | KeyVerificationRequest, 48 | 49 | /// m.key.verification.start 50 | KeyVerificationStart, 51 | 52 | /// m.ignored_user_list 53 | IgnoredUserList, 54 | 55 | /// m.presence 56 | Presence, 57 | 58 | /// m.push_rules 59 | PushRules, 60 | 61 | /// m.receipt 62 | Receipt, 63 | 64 | /// m.room.aliases 65 | RoomAliases, 66 | 67 | /// m.room.avatar 68 | RoomAvatar, 69 | 70 | /// m.room.canonical_alias 71 | RoomCanonicalAlias, 72 | 73 | /// m.room.create 74 | RoomCreate, 75 | 76 | /// m.room.encrypted 77 | RoomEncrypted, 78 | 79 | /// m.room.encryption 80 | RoomEncryption, 81 | 82 | /// m.room.guest_access 83 | RoomGuestAccess, 84 | 85 | /// m.room.history_visibility 86 | RoomHistoryVisibility, 87 | 88 | /// m.room.join_rules 89 | RoomJoinRules, 90 | 91 | /// m.room.member 92 | RoomMember, 93 | 94 | /// m.room.message 95 | RoomMessage, 96 | 97 | /// m.room.message.feedback 98 | RoomMessageFeedback, 99 | 100 | /// m.room.name 101 | RoomName, 102 | 103 | /// m.room.pinned_events 104 | RoomPinnedEvents, 105 | 106 | /// m.room.power_levels 107 | RoomPowerLevels, 108 | 109 | /// m.room.redaction 110 | RoomRedaction, 111 | 112 | /// m.room.server_acl 113 | RoomServerAcl, 114 | 115 | /// m.room.third_party_invite 116 | RoomThirdPartyInvite, 117 | 118 | /// m.room.tombstone 119 | RoomTombstone, 120 | 121 | /// m.room.topic 122 | RoomTopic, 123 | 124 | /// m.room_key 125 | RoomKey, 126 | 127 | /// m.room_key_request 128 | RoomKeyRequest, 129 | 130 | /// m.sticker 131 | Sticker, 132 | 133 | /// m.tag 134 | Tag, 135 | 136 | /// m.typing 137 | Typing, 138 | 139 | /// Any event that is not part of the specification. 140 | Custom(String), 141 | } 142 | 143 | impl Display for EventType { 144 | fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { 145 | let event_type_str = match *self { 146 | EventType::CallAnswer => "m.call.answer", 147 | EventType::CallCandidates => "m.call.candidates", 148 | EventType::CallHangup => "m.call.hangup", 149 | EventType::CallInvite => "m.call.invite", 150 | EventType::Direct => "m.direct", 151 | EventType::Dummy => "m.dummy", 152 | EventType::ForwardedRoomKey => "m.forwarded_room_key", 153 | EventType::FullyRead => "m.fully_read", 154 | EventType::KeyVerificationAccept => "m.key.verification.accept", 155 | EventType::KeyVerificationCancel => "m.key.verification.cancel", 156 | EventType::KeyVerificationKey => "m.key.verification.key", 157 | EventType::KeyVerificationMac => "m.key.verification.mac", 158 | EventType::KeyVerificationRequest => "m.key.verification.request", 159 | EventType::KeyVerificationStart => "m.key.verification.start", 160 | EventType::IgnoredUserList => "m.ignored_user_list", 161 | EventType::Presence => "m.presence", 162 | EventType::PushRules => "m.push_rules", 163 | EventType::Receipt => "m.receipt", 164 | EventType::RoomAliases => "m.room.aliases", 165 | EventType::RoomAvatar => "m.room.avatar", 166 | EventType::RoomCanonicalAlias => "m.room.canonical_alias", 167 | EventType::RoomCreate => "m.room.create", 168 | EventType::RoomEncrypted => "m.room.encrypted", 169 | EventType::RoomEncryption => "m.room.encryption", 170 | EventType::RoomGuestAccess => "m.room.guest_access", 171 | EventType::RoomHistoryVisibility => "m.room.history_visibility", 172 | EventType::RoomJoinRules => "m.room.join_rules", 173 | EventType::RoomMember => "m.room.member", 174 | EventType::RoomMessage => "m.room.message", 175 | EventType::RoomMessageFeedback => "m.room.message.feedback", 176 | EventType::RoomName => "m.room.name", 177 | EventType::RoomPinnedEvents => "m.room.pinned_events", 178 | EventType::RoomPowerLevels => "m.room.power_levels", 179 | EventType::RoomRedaction => "m.room.redaction", 180 | EventType::RoomServerAcl => "m.room.server_acl", 181 | EventType::RoomThirdPartyInvite => "m.room.third_party_invite", 182 | EventType::RoomTombstone => "m.room.tombstone", 183 | EventType::RoomTopic => "m.room.topic", 184 | EventType::RoomKey => "m.room_key", 185 | EventType::RoomKeyRequest => "m.room_key_request", 186 | EventType::Sticker => "m.sticker", 187 | EventType::Tag => "m.tag", 188 | EventType::Typing => "m.typing", 189 | EventType::Custom(ref event_type) => event_type, 190 | }; 191 | 192 | write!(f, "{}", event_type_str) 193 | } 194 | } 195 | 196 | impl From for EventType 197 | where 198 | T: Into + AsRef, 199 | { 200 | fn from(s: T) -> EventType { 201 | match s.as_ref() { 202 | "m.call.answer" => EventType::CallAnswer, 203 | "m.call.candidates" => EventType::CallCandidates, 204 | "m.call.hangup" => EventType::CallHangup, 205 | "m.call.invite" => EventType::CallInvite, 206 | "m.direct" => EventType::Direct, 207 | "m.dummy" => EventType::Dummy, 208 | "m.forwarded_room_key" => EventType::ForwardedRoomKey, 209 | "m.fully_read" => EventType::FullyRead, 210 | "m.key.verification.accept" => EventType::KeyVerificationAccept, 211 | "m.key.verification.cancel" => EventType::KeyVerificationCancel, 212 | "m.key.verification.key" => EventType::KeyVerificationKey, 213 | "m.key.verification.mac" => EventType::KeyVerificationMac, 214 | "m.key.verification.request" => EventType::KeyVerificationRequest, 215 | "m.key.verification.start" => EventType::KeyVerificationStart, 216 | "m.ignored_user_list" => EventType::IgnoredUserList, 217 | "m.presence" => EventType::Presence, 218 | "m.push_rules" => EventType::PushRules, 219 | "m.receipt" => EventType::Receipt, 220 | "m.room.aliases" => EventType::RoomAliases, 221 | "m.room.avatar" => EventType::RoomAvatar, 222 | "m.room.canonical_alias" => EventType::RoomCanonicalAlias, 223 | "m.room.create" => EventType::RoomCreate, 224 | "m.room.encrypted" => EventType::RoomEncrypted, 225 | "m.room.encryption" => EventType::RoomEncryption, 226 | "m.room.guest_access" => EventType::RoomGuestAccess, 227 | "m.room.history_visibility" => EventType::RoomHistoryVisibility, 228 | "m.room.join_rules" => EventType::RoomJoinRules, 229 | "m.room.member" => EventType::RoomMember, 230 | "m.room.message" => EventType::RoomMessage, 231 | "m.room.message.feedback" => EventType::RoomMessageFeedback, 232 | "m.room.name" => EventType::RoomName, 233 | "m.room.pinned_events" => EventType::RoomPinnedEvents, 234 | "m.room.power_levels" => EventType::RoomPowerLevels, 235 | "m.room.redaction" => EventType::RoomRedaction, 236 | "m.room.server_acl" => EventType::RoomServerAcl, 237 | "m.room.third_party_invite" => EventType::RoomThirdPartyInvite, 238 | "m.room.tombstone" => EventType::RoomTombstone, 239 | "m.room.topic" => EventType::RoomTopic, 240 | "m.room_key" => EventType::RoomKey, 241 | "m.room_key_request" => EventType::RoomKeyRequest, 242 | "m.sticker" => EventType::Sticker, 243 | "m.tag" => EventType::Tag, 244 | "m.typing" => EventType::Typing, 245 | _ => EventType::Custom(s.into()), 246 | } 247 | } 248 | } 249 | 250 | impl From for String { 251 | fn from(event_type: EventType) -> String { 252 | event_type.to_string() 253 | } 254 | } 255 | 256 | #[cfg(test)] 257 | mod tests { 258 | use ruma_serde::test::serde_json_eq; 259 | use serde_json::json; 260 | 261 | use super::*; 262 | 263 | #[allow(clippy::cognitive_complexity)] 264 | #[test] 265 | fn serialize_and_deserialize_from_display_form() { 266 | serde_json_eq(EventType::CallAnswer, json!("m.call.answer")); 267 | serde_json_eq(EventType::CallCandidates, json!("m.call.candidates")); 268 | serde_json_eq(EventType::CallHangup, json!("m.call.hangup")); 269 | serde_json_eq(EventType::CallInvite, json!("m.call.invite")); 270 | serde_json_eq(EventType::Direct, json!("m.direct")); 271 | serde_json_eq(EventType::Dummy, json!("m.dummy")); 272 | serde_json_eq(EventType::ForwardedRoomKey, json!("m.forwarded_room_key")); 273 | serde_json_eq(EventType::FullyRead, json!("m.fully_read")); 274 | serde_json_eq( 275 | EventType::KeyVerificationAccept, 276 | json!("m.key.verification.accept"), 277 | ); 278 | serde_json_eq( 279 | EventType::KeyVerificationCancel, 280 | json!("m.key.verification.cancel"), 281 | ); 282 | serde_json_eq( 283 | EventType::KeyVerificationKey, 284 | json!("m.key.verification.key"), 285 | ); 286 | serde_json_eq( 287 | EventType::KeyVerificationMac, 288 | json!("m.key.verification.mac"), 289 | ); 290 | serde_json_eq( 291 | EventType::KeyVerificationRequest, 292 | json!("m.key.verification.request"), 293 | ); 294 | serde_json_eq( 295 | EventType::KeyVerificationStart, 296 | json!("m.key.verification.start"), 297 | ); 298 | serde_json_eq(EventType::IgnoredUserList, json!("m.ignored_user_list")); 299 | serde_json_eq(EventType::Presence, json!("m.presence")); 300 | serde_json_eq(EventType::PushRules, json!("m.push_rules")); 301 | serde_json_eq(EventType::Receipt, json!("m.receipt")); 302 | serde_json_eq(EventType::RoomAliases, json!("m.room.aliases")); 303 | serde_json_eq(EventType::RoomAvatar, json!("m.room.avatar")); 304 | serde_json_eq( 305 | EventType::RoomCanonicalAlias, 306 | json!("m.room.canonical_alias"), 307 | ); 308 | serde_json_eq(EventType::RoomCreate, json!("m.room.create")); 309 | serde_json_eq(EventType::RoomEncrypted, json!("m.room.encrypted")); 310 | serde_json_eq(EventType::RoomEncryption, json!("m.room.encryption")); 311 | serde_json_eq(EventType::RoomGuestAccess, json!("m.room.guest_access")); 312 | serde_json_eq( 313 | EventType::RoomHistoryVisibility, 314 | json!("m.room.history_visibility"), 315 | ); 316 | serde_json_eq(EventType::RoomJoinRules, json!("m.room.join_rules")); 317 | serde_json_eq(EventType::RoomMember, json!("m.room.member")); 318 | serde_json_eq(EventType::RoomMessage, json!("m.room.message")); 319 | serde_json_eq( 320 | EventType::RoomMessageFeedback, 321 | json!("m.room.message.feedback"), 322 | ); 323 | serde_json_eq(EventType::RoomName, json!("m.room.name")); 324 | serde_json_eq(EventType::RoomPinnedEvents, json!("m.room.pinned_events")); 325 | serde_json_eq(EventType::RoomPowerLevels, json!("m.room.power_levels")); 326 | serde_json_eq(EventType::RoomRedaction, json!("m.room.redaction")); 327 | serde_json_eq(EventType::RoomServerAcl, json!("m.room.server_acl")); 328 | serde_json_eq( 329 | EventType::RoomThirdPartyInvite, 330 | json!("m.room.third_party_invite"), 331 | ); 332 | serde_json_eq(EventType::RoomTombstone, json!("m.room.tombstone")); 333 | serde_json_eq(EventType::RoomTopic, json!("m.room.topic")); 334 | serde_json_eq(EventType::RoomKey, json!("m.room_key")); 335 | serde_json_eq(EventType::RoomKeyRequest, json!("m.room_key_request")); 336 | serde_json_eq(EventType::Sticker, json!("m.sticker")); 337 | serde_json_eq(EventType::Tag, json!("m.tag")); 338 | serde_json_eq(EventType::Typing, json!("m.typing")); 339 | serde_json_eq( 340 | EventType::Custom("io.ruma.test".to_string()), 341 | json!("io.ruma.test"), 342 | ); 343 | } 344 | } 345 | --------------------------------------------------------------------------------