From aead9fa852091a9e035a974d2f40edb08a05ee1e Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 8 Aug 2020 20:47:38 +0200 Subject: [PATCH] Use AnyMessageEventContent in send_message_event --- ruma-client-api/Cargo.toml | 1 + .../src/r0/message/send_message_event.rs | 235 +++++++++++++++--- ruma-client/examples/hello_world.rs | 30 +-- 3 files changed, 209 insertions(+), 57 deletions(-) diff --git a/ruma-client-api/Cargo.toml b/ruma-client-api/Cargo.toml index 6667d78e..4f19b3f4 100644 --- a/ruma-client-api/Cargo.toml +++ b/ruma-client-api/Cargo.toml @@ -20,6 +20,7 @@ edition = "2018" assign = "1.1.0" http = "0.2.1" js_int = { version = "0.1.9", features = ["serde"] } +percent-encoding = "2.1.0" ruma-api = { version = "=0.17.0-alpha.1", path = "../ruma-api" } ruma-common = { version = "0.2.0", path = "../ruma-common" } ruma-events = { version = "=0.22.0-alpha.1", path = "../ruma-events" } diff --git a/ruma-client-api/src/r0/message/send_message_event.rs b/ruma-client-api/src/r0/message/send_message_event.rs index 8aef8fbe..14b1a595 100644 --- a/ruma-client-api/src/r0/message/send_message_event.rs +++ b/ruma-client-api/src/r0/message/send_message_event.rs @@ -1,48 +1,203 @@ //! [PUT /_matrix/client/r0/rooms/{roomId}/send/{eventType}/{txnId}](https://matrix.org/docs/spec/client_server/r0.6.1#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid) -use ruma_api::ruma_api; -use ruma_events::EventType; +use std::convert::TryFrom; + +use ruma_api::{ + error::{ + FromHttpRequestError, FromHttpResponseError, IntoHttpError, RequestDeserializationError, + ResponseDeserializationError, ServerError, + }, + EndpointError, Metadata, Outgoing, +}; +use ruma_events::{AnyMessageEventContent, EventContent as _}; use ruma_identifiers::{EventId, RoomId}; +use serde::{Deserialize, Serialize}; use serde_json::value::RawValue as RawJsonValue; -ruma_api! { - metadata: { - description: "Send a message event to a room.", - method: PUT, - name: "create_message_event", - path: "/_matrix/client/r0/rooms/:room_id/send/:event_type/:txn_id", - rate_limited: false, - requires_authentication: true, - } +/// Data for a request to the `send_message_event` API endpoint. +/// +/// Send a message event to a room. +#[derive(Clone, Debug, Outgoing)] +#[non_exhaustive] +#[incoming_no_deserialize] +pub struct Request<'a> { + /// The room to send the event to. + pub room_id: &'a RoomId, - request: { - /// The room to send the event to. - #[ruma_api(path)] - pub room_id: &'a RoomId, + /// The transaction ID for this event. + /// + /// Clients should generate an ID unique across requests with the + /// same access token; it will be used by the server to ensure + /// idempotency of requests. + pub txn_id: &'a str, - /// The type of event to send. - #[ruma_api(path)] - pub event_type: EventType, - - /// The transaction ID for this event. - /// - /// Clients should generate an ID unique across requests with the - /// same access token; it will be used by the server to ensure - /// idempotency of requests. - #[ruma_api(path)] - pub txn_id: &'a str, - - /// The event's content. The type for this field will be updated in a - /// future release, until then you can create a value using - /// `serde_json::value::to_raw_value`. - #[ruma_api(body)] - pub data: Box, - } - - response: { - /// A unique identifier for the event. - pub event_id: EventId, - } - - error: crate::Error + /// The event content to send. + pub content: &'a AnyMessageEventContent, +} + +impl<'a> Request<'a> { + /// Creates a new `Request` with the given room id, transaction id and event content. + pub fn new(room_id: &'a RoomId, txn_id: &'a str, content: &'a AnyMessageEventContent) -> Self { + Self { room_id, txn_id, content } + } +} + +/// Data in the response from the `send_message_event` API endpoint. +#[derive(Clone, Debug, Outgoing)] +#[non_exhaustive] +#[incoming_no_deserialize] +pub struct Response { + /// A unique identifier for the event. + pub event_id: EventId, +} + +impl Response { + /// Creates a new `Response` with the given event id. + pub fn new(event_id: EventId) -> Self { + Self { event_id } + } +} + +const METADATA: Metadata = Metadata { + description: "Send a message event to a room.", + method: http::Method::PUT, + name: "send_message_event", + path: "/_matrix/client/r0/rooms/:room_id/send/:event_type/:txn_id", + rate_limited: false, + requires_authentication: true, +}; + +impl TryFrom>> for IncomingRequest { + type Error = FromHttpRequestError; + + fn try_from(request: http::Request>) -> Result { + let path_segments: Vec<&str> = request.uri().path()[1..].split('/').collect(); + + let room_id = { + let decoded = + match percent_encoding::percent_decode(path_segments[4].as_bytes()).decode_utf8() { + Ok(val) => val, + Err(err) => return Err(RequestDeserializationError::new(err, request).into()), + }; + + match RoomId::try_from(&*decoded) { + Ok(val) => val, + Err(err) => return Err(RequestDeserializationError::new(err, request).into()), + } + }; + + let txn_id = + match percent_encoding::percent_decode(path_segments[7].as_bytes()).decode_utf8() { + Ok(val) => val.into_owned(), + Err(err) => return Err(RequestDeserializationError::new(err, request).into()), + }; + + let content = { + let request_body: Box = + match serde_json::from_slice(request.body().as_slice()) { + Ok(val) => val, + Err(err) => return Err(RequestDeserializationError::new(err, request).into()), + }; + + let event_type = { + match percent_encoding::percent_decode(path_segments[6].as_bytes()).decode_utf8() { + Ok(val) => val, + Err(err) => return Err(RequestDeserializationError::new(err, request).into()), + } + }; + + match AnyMessageEventContent::from_parts(&event_type, request_body) { + Ok(content) => content, + Err(err) => return Err(RequestDeserializationError::new(err, request).into()), + } + }; + + Ok(Self { room_id, txn_id, content }) + } +} + +/// Data in the response body. +#[derive(Debug, Deserialize, Serialize)] +struct ResponseBody { + /// A unique identifier for the event. + event_id: EventId, +} + +impl TryFrom for http::Response> { + type Error = IntoHttpError; + + fn try_from(response: Response) -> Result { + let response = http::Response::builder() + .header(http::header::CONTENT_TYPE, "application/json") + .body(serde_json::to_vec(&ResponseBody { event_id: response.event_id })?) + .unwrap(); + + Ok(response) + } +} + +impl TryFrom>> for Response { + type Error = FromHttpResponseError; + + fn try_from(response: http::Response>) -> Result { + if response.status().as_u16() < 400 { + let response_body: ResponseBody = + match serde_json::from_slice(response.body().as_slice()) { + Ok(val) => val, + Err(err) => return Err(ResponseDeserializationError::new(err, response).into()), + }; + + Ok(Self { event_id: response_body.event_id }) + } else { + match ::try_from_response(response) { + Ok(err) => Err(ServerError::Known(err).into()), + Err(response_err) => Err(ServerError::Unknown(response_err).into()), + } + } + } +} + +impl<'a> ruma_api::OutgoingRequest for Request<'a> { + type EndpointError = crate::Error; + type IncomingResponse = Response; + + /// Metadata for the `send_message_event` endpoint. + const METADATA: Metadata = METADATA; + + fn try_into_http_request( + self, + base_url: &str, + access_token: Option<&str>, + ) -> Result>, IntoHttpError> { + use http::header::{HeaderValue, AUTHORIZATION}; + use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; + + let http_request = http::Request::builder() + .method(http::Method::PUT) + .uri(format!( + "{}/_matrix/client/r0/rooms/{}/send/{}/{}", + base_url.strip_suffix("/").unwrap_or(base_url), + utf8_percent_encode(self.room_id.as_str(), NON_ALPHANUMERIC), + utf8_percent_encode(self.content.event_type(), NON_ALPHANUMERIC), + utf8_percent_encode(&self.txn_id, NON_ALPHANUMERIC), + )) + .header( + AUTHORIZATION, + HeaderValue::from_str(&format!( + "Bearer {}", + access_token.ok_or(IntoHttpError::NeedsAuthentication)? + ))?, + ) + .body(serde_json::to_vec(&self.content)?)?; + + Ok(http_request) + } +} + +impl ruma_api::IncomingRequest for IncomingRequest { + type EndpointError = crate::Error; + type OutgoingResponse = Response; + + /// Metadata for the `send_message_event` endpoint. + const METADATA: Metadata = METADATA; } diff --git a/ruma-client/examples/hello_world.rs b/ruma-client/examples/hello_world.rs index 1ced9517..fa49bc0d 100644 --- a/ruma-client/examples/hello_world.rs +++ b/ruma-client/examples/hello_world.rs @@ -5,34 +5,30 @@ use ruma::{ api::client::r0::{alias::get_alias, membership::join_room_by_id, message::send_message_event}, events::{ room::message::{MessageEventContent, TextMessageEventContent}, - EventType, + AnyMessageEventContent, }, RoomAliasId, }; use ruma_client::{self, Client}; -use serde_json::value::to_raw_value as to_raw_json_value; async fn hello_world(homeserver_url: Uri, room_alias: &RoomAliasId) -> anyhow::Result<()> { let client = Client::new(homeserver_url, None); - client.register_guest().await?; - let response = client.request(get_alias::Request::new(room_alias)).await?; - - let room_id = response.room_id; + let room_id = client.request(get_alias::Request::new(room_alias)).await?.room_id; client.request(join_room_by_id::Request::new(&room_id)).await?; - client - .request(send_message_event::Request { - room_id: &room_id, - event_type: EventType::RoomMessage, - txn_id: "1", - data: to_raw_json_value(&MessageEventContent::Text(TextMessageEventContent { - body: "Hello World!".to_owned(), - formatted: None, - relates_to: None, - }))?, - }) + .request(send_message_event::Request::new( + &room_id, + "1", + &AnyMessageEventContent::RoomMessage(MessageEventContent::Text( + TextMessageEventContent { + body: "Hello World!".to_owned(), + formatted: None, + relates_to: None, + }, + )), + )) .await?; Ok(())