Add 'ruma-federation-api/' from commit '44a0f493d0ae119fb1175a5f61c2db52ef001fb7'

git-subtree-dir: ruma-federation-api
git-subtree-mainline: acff664671e3f53bd012d33228363780eb20cf35
git-subtree-split: 44a0f493d0ae119fb1175a5f61c2db52ef001fb7
This commit is contained in:
Jonas Platte 2020-06-07 16:21:23 +02:00
commit 10bd7d5f95
30 changed files with 1102 additions and 0 deletions

View File

@ -0,0 +1,27 @@
image: archlinux
packages:
- rustup
sources:
- https://github.com/ruma/ruma-federation-api
tasks:
- rustup: |
# We specify --profile minimal because we'd otherwise download docs
rustup toolchain install beta --profile minimal -c rustfmt -c clippy
rustup default beta
- test: |
cd ruma-federation-api
# We don't want the build to stop on individual failure of independent
# tools, so capture tool exit codes and set the task exit code manually
set +e
cargo fmt -- --check
fmt_exit=$?
cargo clippy --all-targets --all-features -- -D warnings
clippy_exit=$?
cargo test --verbose
test_exit=$?
exit $(( $fmt_exit || $clippy_exit || $test_exit ))

View File

@ -0,0 +1,16 @@
image: archlinux
packages:
- rustup
sources:
- https://github.com/ruma/ruma-federation-api
tasks:
- rustup: |
# We specify --profile minimal because we'd otherwise download docs
rustup toolchain install 1.40.0 --profile minimal
rustup default 1.40.0
- test: |
cd ruma-federation-api
# Only make sure the code builds with the MSRV. Tests can require later
# Rust versions, don't compile or run them.
cargo build --verbose

View File

@ -0,0 +1,32 @@
image: archlinux
packages:
- rustup
sources:
- https://github.com/ruma/ruma-federation-api
tasks:
- rustup: |
rustup toolchain install nightly --profile minimal
rustup default nightly
# Try installing rustfmt & clippy for nightly, but don't fail the build
# if they are not available
rustup component add rustfmt || true
rustup component add clippy || true
- test: |
cd ruma-federation-api
# We don't want the build to stop on individual failure of independent
# tools, so capture tool exit codes and set the task exit code manually
set +e
if ( rustup component list | grep -q rustfmt ); then
cargo fmt -- --check
fi
fmt_exit=$?
if ( rustup component list | grep -q clippy ); then
cargo clippy --all-targets --all-features -- -D warnings
fi
clippy_exit=$?
exit $(( $fmt_exit || $clippy_exit ))

View File

@ -0,0 +1,29 @@
image: archlinux
packages:
- rustup
sources:
- https://github.com/ruma/ruma-federation-api
tasks:
- rustup: |
# We specify --profile minimal because we'd otherwise download docs
rustup toolchain install stable --profile minimal -c rustfmt -c clippy
rustup default stable
- test: |
cd ruma-federation-api
# We don't want the build to stop on individual failure of independent
# tools, so capture tool exit codes and set the task exit code manually
set +e
cargo fmt -- --check
fmt_exit=$?
cargo clippy --all-targets --all-features -- -D warnings
clippy_exit=$?
cargo test --verbose
test_exit=$?
exit $(( $fmt_exit || $clippy_exit || $test_exit ))
# TODO: Add audit task once cargo-audit binary releases are available.
# See https://github.com/RustSec/cargo-audit/issues/66

2
ruma-federation-api/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
Cargo.lock
target

View File

@ -0,0 +1,25 @@
# [unreleased]
Improvements:
* Add endpoints:
```
directory::get_public_rooms::v1,
discovery::{
discover_homeserver,
get_server_keys::v2,
get_server_version::v1
},
membership::{
create_join_event::v1,
create_join_event_template::v1
},
query::get_room_information::v1,
version::get_server_version::v1
```
# 0.0.1
Improvements:
* Provide `RoomV3Pdu` type for room versions 3 and above

View File

@ -0,0 +1,181 @@
Welcome! Thanks for looking into contributing to our project!
# Table of Contents
- [Looking for Help?](#looking-for-help)
- [Documentation](#documentation)
- [Chat Rooms](#chat-rooms)
- [Reporting Issues](#reporting-issues)
- [Submitting Code](#submitting-code)
- [Coding Style](#coding-style)
- [Modifying Endpoints](#modifying-endpoints)
- [Submitting PRs](#submitting-prs)
- [Where do I start?](#where-do-i-start)
- [Testing](#testing)
- [Contact](#contact)
# Looking for Help?
Here is a list of helpful resources you can consult:
## Documentation
- [Matrix Federation API specification](https://matrix.org/docs/spec/server_server/latest)
- Documentation to other Ruma modules:
- [ruma-events](https://docs.rs/ruma-events/)
- [ruma-api](https://docs.rs/ruma-api/)
- [ruma-client](https://docs.rs/ruma-client/)
## Chat Rooms
- Ruma Matrix room: [#ruma:matrix.org](https://matrix.to/#/#ruma:matrix.org)
- Matrix Developer room: [#matrix-dev:matrix.org](https://matrix.to/#/#matrix-dev:matrix.org)
- Matrix Homeserver developers room: [#homeservers-dev:matrix.org](https://matrix.to/#/#homerservers-dev:matrix.org)
# Reporting Issues
If you find any bugs, inconsistencies or other problems, feel free to submit
a GitHub [issue](issues).
If you have a quick question, it may be easier to leave a message on
[#ruma:matrix.org](https://matrix.to/#/#ruma:matrix.org).
Also, if you have trouble getting on board, let us know so we can help future
contributors to the project overcome that hurdle too.
# Submitting Code
Ready to write some code? Great! Here are some guidelines to follow to
help you on your way:
## Coding Style
### Import Formatting
Organize your imports into three groups separated by blank lines:
1. `std` imports
1. External imports (from other crates)
1. Local imports (`self::`, `super::`, `crate::` and things like `LocalType::*`)
For example,
```rust
use std::collections::HashMap;
use ruma_api::ruma_api;
use super::MyType;
```
Also, group imports by module. For example, do this:
```rust
use std::{
collections::HashMap,
convert::TryFrom,
fmt::{Debug, Display, Error as FmtError, Formatter},
};
```
as opposed to:
```rust
use std::collections::HashMap;
use std::convert::TryFrom;
use std::fmt::{Debug, Display, Error as FmtError, Formatter};
```
### Code Formatting and Linting
Use `rustfmt` to format your code and `clippy` to lint your code. Before
committing your changes, go ahead and run `cargo fmt` and `cargo clippy
--all-targets --all-features` on the repository to make sure that the
formatting and linting checks pass in CI. Note that `clippy` warnings are
reported as errors in CI builds, so make sure to handle those before
comitting as well. (To install the tools, run `rustup component add rustfmt
clippy`.)
#### Git hooks
Tip: You may want to add these commands to your pre-commit git hook so you don't get bitten by CI.
```sh
# ./git/hooks/pre-commit
cargo fmt && cargo clippy --all-targets --allfeatures
```
### Commit Messages
Write commit messages using the imperative mood, as if completing the sentence:
"If applied, this commit will \_\_\_." For example, use "Fix some bug" instead
of "Fixed some bug" or "Add a feature" instead of "Added a feature".
(Take a look at this
[blog post](https://www.freecodecamp.org/news/writing-good-commit-messages-a-practical-guide/)
for more information on writing good commit messages.)
## Modifying Endpoints
### Matrix Spec Version
Use the latest r0.x.x documentation when adding or modifying code. We target
the latest minor version of the Matrix specification. (Note: We might
reconsider this when the Federation API hits r1.0.0.)
### Endpoint Documentation Header
Add a comment to the top of each endpoint file that includes the path
and a link to the documentation of the spec. You can use the latest
version at the time of the commit. For example:
```rust
//! [GET /.well-known/matrix/server](https://matrix.org/docs/spec/server_server/r0.1.3#get-well-known-matrix-server)
```
### Naming Endpoints
When adding new endpoints, select the module that fits the purpose of the
endpoint. When naming the endpoint itself, you can use the following
guidelines:
- The name should be a verb describing what the client is requesting, e.g.
`get_some_resource`.
- Endpoints which are basic CRUD operations should use the prefixes
`create`, `get`, `update`, and `delete`.
- The prefix `set` is preferred to create if the resource is a singleton.
In other words, when there's no distinction between `create` and `update`.
- Try to use names that are as descriptive as possible and distinct from
other endpoints in all other modules. (For example, instead of
`membership::create_event::v1`, use `membership::create_join_event::v1`).
- If you're not sure what to name it, pick any name and we can help you
with it.
### Tracking Changes
Add your changes to the [change log](CHANGELOG.md). If possible, try to
find and denote the version of the spec that included the change you are
making.
## Submitting PRs
Once you're ready to submit your code, create a pull request, and one of our
maintainers will review it. Once your PR has passed review, a maintainer will
merge the request and you're done! 🎉
## Where do I start?
If this is your first contribution to the project, we recommend taking a look
at one of the [open issues][] we've marked for new contributors.
It may be helpful to peruse some of the documentation for `ruma-events` and
`ruma-api` listed above for some context.
[open issues]: https://github.com/ruma/ruma-federation-api/issues?q=is%3Aopen+is%3Aissue+label%3Aeffort%2Feasy
# Testing
Before committing, run `cargo check` to make sure that your changes can build, as well as running the formatting and linting tools [mentioned above](#code-formatting-and-linting).
# Contact
Thanks again for being a contributor! If you have any questions, join us at
[#ruma:matrix.org](https://matrix.to/#/#ruma:matrix.org).

View File

@ -0,0 +1,23 @@
[package]
authors = ["Jonas Platte <jplatte+git@posteo.de>"]
categories = ["api-bindings", "web-programming"]
description = "Types for the endpoints in the Matrix server-server API."
documentation = "https://docs.rs/ruma-federation-api"
edition = "2018"
homepage = "https://github.com/ruma/ruma-federation-api"
keywords = ["matrix", "chat", "messaging", "ruma"]
license = "MIT"
name = "ruma-federation-api"
readme = "README.md"
repository = "https://github.com/ruma/ruma-federation-api"
version = "0.0.2"
[dependencies]
js_int = "0.1.5"
matches = "0.1.8"
ruma-api = "0.16.1"
ruma-events = "0.21.3"
ruma-identifiers = "0.16.2"
ruma-serde = "0.2.2"
serde = { version = "1.0.111", features = ["derive"] }
serde_json = "1.0.53"

View File

@ -0,0 +1,19 @@
Copyright (c) 2019 Jimmy Cuadra
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,18 @@
# ruma-federation-api
**ruma-federation-api** contains serializable types for the requests and responses for each endpoint in the [Matrix](https://matrix.org/) Federation API specification.
These types can be shared by client and server code.
## Minimum Rust version
ruma-federation-api requires Rust 1.40.0 or later.
## Documentation
[https://docs.rs/ruma-federation-api](https://docs.rs/ruma-federation-api)
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md).
## License
[MIT](http://opensource.org/licenses/MIT)

View File

@ -0,0 +1,3 @@
//! Room directory endpoints.
pub mod get_public_rooms;

View File

@ -0,0 +1,3 @@
//! Endpoint to query a homeserver's public rooms.
pub mod v1;

View File

@ -0,0 +1,177 @@
//! [GET /_matrix/federation/v1/publicRooms](https://matrix.org/docs/spec/server_server/r0.1.3#get-matrix-federation-v1-publicrooms)
use std::fmt;
use js_int::UInt;
use ruma_api::ruma_api;
use ruma_identifiers::{RoomAliasId, RoomId};
use serde::{
de::{MapAccess, Visitor},
ser::SerializeStruct,
Deserialize, Deserializer, Serialize, Serializer,
};
ruma_api! {
metadata {
description: "Gets all the public rooms for the homeserver.",
method: GET,
name: "get_public_rooms",
path: "/_matrix/federation/v1/publicRooms",
rate_limited: false,
requires_authentication: true,
}
request {
/// The maximum number of rooms to return. Default is no limit.
#[serde(skip_serializing_if = "Option::is_none")]
#[ruma_api(query)]
pub limit: Option<UInt>,
/// Pagination token from a previous request.
#[serde(skip_serializing_if = "Option::is_none")]
#[ruma_api(query)]
pub since: Option<String>,
/// Network to fetch the public room lists from.
#[serde(flatten, skip_serializing_if = "ruma_serde::is_default")]
#[ruma_api(query)]
pub room_network: RoomNetwork,
}
response {
/// A paginated chunk of public rooms.
pub chunk: Vec<PublicRoomsChunk>,
/// A pagination token for the response.
#[serde(skip_serializing_if = "Option::is_none")]
pub next_batch: Option<String>,
/// A pagination token that allows fetching previous results.
#[serde(skip_serializing_if = "Option::is_none")]
pub prev_batch: Option<String>,
/// An estimate on the total number of public rooms, if the server has an estimate.
pub total_room_count_estimate: Option<UInt>,
}
}
/// A chunk of a room list response, describing one room
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct PublicRoomsChunk {
/// Aliases of the room.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub aliases: Vec<RoomAliasId>,
/// The canonical alias of the room, if any.
#[serde(skip_serializing_if = "Option::is_none")]
pub canonical_alias: Option<String>,
/// The name of the room, if any.
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
/// The number of members joined to the room.
pub num_joined_members: UInt,
/// The ID of the room.
pub room_id: RoomId,
/// The topic of the room, if any.
#[serde(skip_serializing_if = "Option::is_none")]
pub topic: Option<String>,
/// Whether the room may be viewed by guest users without joining.
pub world_readable: bool,
/// Whether guest users may join the room and participate in it.
///
/// If they can, they will be subject to ordinary power level rules like any other user.
pub guest_can_join: bool,
/// The URL for the room's avatar, if one is set.
#[serde(skip_serializing_if = "Option::is_none")]
pub avatar_url: Option<String>,
}
/// Information about which networks/protocols from application services on the
/// homeserver from which to request rooms.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum RoomNetwork {
/// Return rooms from the Matrix network.
Matrix,
/// Return rooms from all the networks/protocols the homeserver knows about.
All,
/// Return rooms from a specific third party network/protocol.
ThirdParty(String),
}
impl Default for RoomNetwork {
fn default() -> Self {
RoomNetwork::Matrix
}
}
impl Serialize for RoomNetwork {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut state;
match self {
Self::Matrix => {
state = serializer.serialize_struct("RoomNetwork", 0)?;
}
Self::All => {
state = serializer.serialize_struct("RoomNetwork", 1)?;
state.serialize_field("include_all_networks", &true)?;
}
Self::ThirdParty(network) => {
state = serializer.serialize_struct("RoomNetwork", 1)?;
state.serialize_field("third_party_instance_id", network)?;
}
}
state.end()
}
}
impl<'de> Deserialize<'de> for RoomNetwork {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_map(RoomNetworkVisitor)
}
}
struct RoomNetworkVisitor;
impl<'de> Visitor<'de> for RoomNetworkVisitor {
type Value = RoomNetwork;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("Network selection")
}
fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
where
M: MapAccess<'de>,
{
let mut include_all_networks = false;
let mut third_party_instance_id = None;
while let Some((key, value)) = access.next_entry::<String, serde_json::Value>()? {
match key.as_str() {
"include_all_networks" => {
include_all_networks = match value.as_bool() {
Some(b) => b,
_ => false,
}
}
"third_party_instance_id" => {
third_party_instance_id = value.as_str().map(|v| v.to_owned())
}
_ => {}
};
}
if include_all_networks {
if third_party_instance_id.is_none() {
Ok(RoomNetwork::All)
} else {
Err(M::Error::custom(
"`include_all_networks = true` and `third_party_instance_id` are mutually exclusive.",
))
}
} else {
Ok(match third_party_instance_id {
Some(network) => RoomNetwork::ThirdParty(network),
None => RoomNetwork::Matrix,
})
}
}
}

View File

@ -0,0 +1,5 @@
//! Server discovery endpoints.
pub mod discover_homeserver;
pub mod get_server_keys;
pub mod get_server_version;

View File

@ -0,0 +1,22 @@
//! [GET /.well-known/matrix/server](https://matrix.org/docs/spec/server_server/r0.1.3#get-well-known-matrix-server)
use ruma_api::ruma_api;
ruma_api! {
metadata {
description: "Get discovery information about the domain.",
method: GET,
name: "discover_homeserver",
path: "/.well-known/matrix/server",
rate_limited: false,
requires_authentication: false,
}
request {}
response {
/// The server name to delegate server-server communciations to, with optional port.
#[serde(rename = "m.homeserver")]
pub homeserver: String,
}
}

View File

@ -0,0 +1,3 @@
//! Endpdoint for retrieving a server's published signing keys.
pub mod v2;

View File

@ -0,0 +1,54 @@
//! [GET /_matrix/key/v2/server](https://matrix.org/docs/spec/server_server/r0.1.3#get-matrix-key-v2-server-keyid)
use std::{collections::BTreeMap, time::SystemTime};
use ruma_api::ruma_api;
use serde::{Deserialize, Serialize};
ruma_api! {
metadata {
description: "Gets the homeserver's published signing keys.",
method: GET,
name: "get_server_keys",
path: "/_matrix/key/v2/server",
rate_limited: false,
requires_authentication: false,
}
request {}
response {
// Spec is wrong, all fields are required (see
// https://github.com/matrix-org/matrix-doc/issues/2508)
/// DNS name of the homeserver.
pub server_name: String,
/// Public keys of the homeserver for verifying digital signatures.
pub verify_keys: BTreeMap<String, VerifyKey>,
/// Public keys that the homeserver used to use and when it stopped using them.
pub old_verify_keys: BTreeMap<String, OldVerifyKey>,
/// Digital signatures of this object signed using the verify_keys.
pub signatures: BTreeMap<String, BTreeMap<String, String>>,
/// Timestamp when the keys should be refreshed. This field MUST be ignored in room
/// versions 1, 2, 3, and 4.
#[serde(with = "ruma_serde::time::ms_since_unix_epoch")]
pub valid_until_ts: SystemTime,
}
}
/// Public key of the homeserver for verifying digital signatures.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct VerifyKey {
/// The Unpadded Base64 encoded key.
pub key: String,
}
/// A key the server used to use, but stopped using.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct OldVerifyKey {
/// Timestamp when this key expired.
#[serde(with = "ruma_serde::time::ms_since_unix_epoch")]
pub expired_ts: SystemTime,
/// The Unpadded Base64 encoded key.
pub key: String,
}

View File

@ -0,0 +1,3 @@
//! Endpoint to retrieve metadata about a server implementation.
pub mod v1;

View File

@ -0,0 +1,34 @@
//! [GET /_matrix/federation/v1/version](https://matrix.org/docs/spec/server_server/r0.1.3#get-matrix-federation-v1-version)
use ruma_api::ruma_api;
use serde::{Deserialize, Serialize};
ruma_api! {
metadata {
description: "Get the implementation name and version of this homeserver.",
method: GET,
name: "discover_homeserver",
path: "/.well-known/matrix/server",
rate_limited: false,
requires_authentication: false,
}
request {}
response {
/// Information about the homeserver implementation
#[serde(skip_serializing_if = "Option::is_none")]
pub server: Option<Server>,
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
/// Arbitrary values that identify this implementation.
pub struct Server {
/// Arbitrary name that identifies this implementation.
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
/// Version of this implementation. The version format depends on the implementation.
#[serde(skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
}

View File

@ -0,0 +1,70 @@
//! (De)serializable types for the Matrix Federation API.
#![warn(missing_docs)]
use std::{collections::BTreeMap, time::SystemTime};
use ::serde::{Deserialize, Serialize};
use js_int::UInt;
use ruma_events::EventType;
use ruma_identifiers::{EventId, RoomId, UserId};
use serde_json::Value as JsonValue;
mod serde;
pub mod directory;
pub mod discovery;
pub mod membership;
pub mod query;
/// A 'persistent data unit' (event) for room versions 3 and beyond.
#[derive(Deserialize, Serialize)]
pub struct RoomV3Pdu {
/// The room this event belongs to.
pub room_id: RoomId,
/// The user id of the user who sent this event.
pub sender: UserId,
/// The `server_name` of the homeserver that created this event.
pub origin: String,
/// Timestamp (milliseconds since the UNIX epoch) on originating homeserver
/// of when this event was created.
#[serde(with = "ruma_serde::time::ms_since_unix_epoch")]
pub origin_server_ts: SystemTime,
// TODO: Replace with event content collection from ruma-events once that exists
/// The event's type.
#[serde(rename = "type")]
pub kind: EventType,
/// The event's content.
pub content: JsonValue,
/// A key that determines which piece of room state the event represents.
#[serde(skip_serializing_if = "Option::is_none")]
pub state_key: Option<String>,
/// Event IDs for the most recent events in the room that the homeserver was
/// aware of when it created this event.
pub prev_events: Vec<EventId>,
/// The maximum depth of the `prev_events`, plus one.
pub depth: UInt,
/// Event IDs for the authorization events that would allow this event to be
/// in the room.
pub auth_events: Vec<EventId>,
/// For redaction events, the ID of the event being redacted.
#[serde(skip_serializing_if = "Option::is_none")]
pub redacts: Option<EventId>,
/// Additional data added by the origin server but not covered by the
/// signatures.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub unsigned: BTreeMap<String, JsonValue>,
/// Content hashes of the PDU.
pub hashes: EventHash,
/// Signatures for the PDU.
pub signatures: BTreeMap<String, BTreeMap<String, String>>,
}
/// Content hashes of a PDU.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct EventHash {
/// The SHA-256 hash.
pub sha256: String,
}

View File

@ -0,0 +1,4 @@
//! Room membership endpoints.
pub mod create_join_event;
pub mod create_join_event_template;

View File

@ -0,0 +1,20 @@
//! Endpoint to send join events to remote homeservers.
pub mod v1;
use ruma_events::EventJson;
use serde::{Deserialize, Serialize};
use crate::RoomV3Pdu;
/// Full state of the room.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct RoomState {
/// The resident server's DNS name.
pub origin: String,
/// The full set of authorization events that make up the state of the room,
/// and their authorization events, recursively.
pub auth_chain: Vec<EventJson<RoomV3Pdu>>,
/// The room state.
pub state: Vec<EventJson<RoomV3Pdu>>,
}

View File

@ -0,0 +1,104 @@
//! [PUT /_matrix/federation/v1/send_join/{roomId}/{eventId}](https://matrix.org/docs/spec/server_server/r0.1.3#put-matrix-federation-v1-send-join-roomid-eventid)
use std::{collections::BTreeMap, time::SystemTime};
use js_int::UInt;
use ruma_api::ruma_api;
use ruma_events::EventType;
use ruma_identifiers::{EventId, RoomId, UserId};
use serde_json::Value as JsonValue;
use super::RoomState;
use crate::{EventHash, RoomV3Pdu};
ruma_api! {
metadata {
description: "Send a join event to a resident server.",
name: "create_join_event",
method: PUT,
path: "/_matrix/federation/v1/send_join/:room_id/:event_id",
rate_limited: false,
requires_authentication: true,
}
request {
/// The room ID that is about to be joined.
#[ruma_api(path)]
pub room_id: RoomId,
/// The user ID the join event will be for.
#[ruma_api(path)]
pub event_id: EventId,
/// The user id of the user who sent this event.
pub sender: UserId,
/// The `server_name` of the homeserver that created this event.
pub origin: String,
/// Timestamp (milliseconds since the UNIX epoch) on originating homeserver
/// of when this event was created.
#[serde(with = "ruma_serde::time::ms_since_unix_epoch")]
pub origin_server_ts: SystemTime,
// TODO: Replace with event content collection from ruma-events once that exists
/// The event's type.
#[serde(rename = "type")]
pub kind: EventType,
/// The event's content.
pub content: JsonValue,
/// A key that determines which piece of room state the event represents.
#[serde(skip_serializing_if = "Option::is_none")]
pub state_key: Option<String>,
/// Event IDs for the most recent events in the room that the homeserver was
/// aware of when it created this event.
pub prev_events: Vec<EventId>,
/// The maximum depth of the `prev_events`, plus one.
pub depth: UInt,
/// Event IDs for the authorization events that would allow this event to be
/// in the room.
pub auth_events: Vec<EventId>,
/// For redaction events, the ID of the event being redacted.
#[serde(skip_serializing_if = "Option::is_none")]
pub redacts: Option<EventId>,
/// Additional data added by the origin server but not covered by the
/// signatures.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub unsigned: BTreeMap<String, JsonValue>,
/// Content hashes of the PDU.
pub hashes: EventHash,
/// Signatures for the PDU.
pub signatures: BTreeMap<String, BTreeMap<String, String>>,
}
response {
/// Full state of the room.
#[ruma_api(body)]
#[serde(with = "crate::serde::room_state")]
pub room_state: RoomState,
}
}
impl Request {
/// Helper method to get event ID and PDU (with room ID) from the request
/// parameters.
pub fn into_id_and_v3_pdu(self) -> (EventId, RoomV3Pdu) {
(
self.event_id,
RoomV3Pdu {
room_id: self.room_id,
sender: self.sender,
origin: self.origin,
origin_server_ts: self.origin_server_ts,
kind: self.kind,
content: self.content,
state_key: self.state_key,
prev_events: self.prev_events,
depth: self.depth,
auth_events: self.auth_events,
redacts: self.redacts,
unsigned: self.unsigned,
hashes: self.hashes,
signatures: self.signatures,
},
)
}
}

View File

@ -0,0 +1,3 @@
//! Endpoint to request a template for join events.
pub mod v1;

View File

@ -0,0 +1,39 @@
//! [GET /_matrix/federation/v1/make_join/{roomId}/{userId}](https://matrix.org/docs/spec/server_server/r0.1.3#get-matrix-federation-v1-make-join-roomid-userid)
use js_int::UInt;
use ruma_api::ruma_api;
use ruma_events::EventJson;
use ruma_identifiers::{RoomId, UserId};
use crate::RoomV3Pdu;
ruma_api! {
metadata {
description: "Send a request for a join event template to a resident server.",
name: "create_join_event_template",
method: GET,
path: "/_matrix/federation/v1/make_join/:room_id/:user_id",
rate_limited: false,
requires_authentication: true,
}
request {
/// The room ID that is about to be joined.
#[ruma_api(path)]
pub room_id: RoomId,
/// The user ID the join event will be for.
#[ruma_api(path)]
pub user_id: UserId,
#[ruma_api(query)]
/// The room versions the sending server has support for. Defaults to [1].
#[serde(skip_serializing_if = "Vec::is_empty")]
pub ver: Vec<UInt>,
}
response {
/// The version of the room where the server is trying to join.
pub room_version: Option<UInt>,
/// An unsigned template event.
pub event: EventJson<RoomV3Pdu>,
}
}

View File

@ -0,0 +1,3 @@
//! Endpoints to retrieve information from a homeserver about a resource.
pub mod get_room_information;

View File

@ -0,0 +1,2 @@
//! Endpoint to query room information with a room alias.
pub mod v1;

View File

@ -0,0 +1,28 @@
//! [GET /_matrix/federation/v1/query/directory](https://matrix.org/docs/spec/server_server/r0.1.3#get-matrix-federation-v1-query-directory)
use ruma_api::ruma_api;
use ruma_identifiers::RoomId;
ruma_api! {
metadata {
description: "Get mapped room ID and resident homeservers for a given room alias.",
name: "get_room_information",
method: GET,
path: "/_matrix/federation/v1/query/directory",
rate_limited: false,
requires_authentication: true,
}
request {
/// Room alias to query.
#[ruma_api(query)]
pub room_alias: String,
}
response {
/// Room ID mapped to queried alias.
pub room_id: RoomId,
/// An array of server names that are likely to hold the given room.
pub servers: Vec<String>,
}
}

View File

@ -0,0 +1,3 @@
//! Modules for custom serde de/-serialization implementations.
pub mod room_state;

View File

@ -0,0 +1,150 @@
//! A module to deserialize a RoomState struct from incorrectly specified v1
//! send_join endpoint.
//!
//! For more information, see this [GitHub issue](https://github.com/matrix-org/matrix-doc/issues/2541).
use std::fmt;
use serde::{
de::{Deserializer, Error, IgnoredAny, SeqAccess, Visitor},
ser::{SerializeSeq, Serializer},
};
use crate::membership::create_join_event::RoomState;
pub fn serialize<S>(room_state: &RoomState, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_seq(Some(2))?;
seq.serialize_element(&200)?;
seq.serialize_element(room_state)?;
seq.end()
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<RoomState, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_seq(RoomStateVisitor)
}
struct RoomStateVisitor;
impl<'de> Visitor<'de> for RoomStateVisitor {
type Value = RoomState;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("Room State response wrapped in an array.")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let expected = "a two-element list in the response";
if seq.next_element::<IgnoredAny>()?.is_none() {
return Err(A::Error::invalid_length(0, &expected));
}
let room_state = seq
.next_element()?
.ok_or_else(|| A::Error::invalid_length(1, &expected))?;
while let Some(IgnoredAny) = seq.next_element()? {
// ignore extra elements
}
Ok(room_state)
}
}
#[cfg(test)]
mod tests {
use matches::assert_matches;
use serde_json::{json, to_value as to_json_value};
use super::{deserialize, serialize, RoomState};
#[test]
fn test_deserialize_response() {
let response = json!([
200,
{
"origin": "example.com",
"auth_chain": [],
"state": []
}
]);
let parsed = deserialize(response).unwrap();
assert_matches!(
parsed,
RoomState { origin, auth_chain, state }
if origin == "example.com"
&& auth_chain.is_empty()
&& state.is_empty()
);
}
#[test]
fn test_serialize_response() {
let room_state = RoomState {
origin: "matrix.org".to_string(),
auth_chain: Vec::new(),
state: Vec::new(),
};
let serialized = serialize(&room_state, serde_json::value::Serializer).unwrap();
let expected = to_json_value(&json!(
[
200,
{
"origin": "matrix.org",
"auth_chain": [],
"state": []
}
]
))
.unwrap();
assert_eq!(serialized, expected);
}
#[test]
fn test_too_short_array() {
let json = json!([200]);
let failed_room_state = deserialize(json);
assert_eq!(
failed_room_state.unwrap_err().to_string(),
"invalid length 1, expected a two-element list in the response"
);
}
#[test]
fn test_not_an_array() {
let json = json!({
"origin": "matrix.org",
"auth_chain": [],
"state": []
});
let failed_room_state = deserialize(json);
assert_eq!(
failed_room_state.unwrap_err().to_string(),
"invalid type: map, expected Room State response wrapped in an array.",
)
}
#[test]
fn test_too_long_array() {
let json = json!([200, {"origin": "", "auth_chain": [], "state": []}, 200]);
assert_matches!(
deserialize(json).unwrap(),
RoomState { origin, auth_chain, state }
if origin == ""
&& auth_chain.is_empty()
&& state.is_empty()
);
}
}