glitch_in_the_matrix/
request.rs

1//! Type for making a generic request to the Matrix API.
2
3use std::borrow::Cow;
4use std::collections::HashMap;
5use serde::Serialize;
6use serde::de::DeserializeOwned;
7use http::{Request, Response, Method};
8use futures::future::Either;
9use crate::errors::{MatrixError, MatrixResult};
10use types::replies::BadRequestReply;
11use serde_json;
12use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
13use futures::{self, Future, Poll, Async, try_ready};
14use std::marker::PhantomData;
15
16/// Describes the type of a Matrix API.
17pub trait ApiType {
18    /// Get the base path which all requests to this API should contain.
19    ///
20    /// For example, `ClientApi`, the struct for the client-server API, sets
21    /// this method to return `/_matrix/client/r0`
22    fn get_path<'a>(&'a self) -> Cow<'a, str>;
23}
24/// Types of Matrix APIs.
25pub mod apis {
26    /// APIs at version r0.
27    pub mod r0 {
28        use crate::request::ApiType;
29        use std::borrow::Cow;
30        /// `/_matrix/client/r0`
31        pub struct ClientApi;
32        impl ApiType for ClientApi {
33            fn get_path(&self) -> Cow<'static, str> {
34                "/_matrix/client/r0".into()
35            }
36        }
37        /// `/_matrix/media/r0`
38        pub struct MediaApi;
39        impl ApiType for MediaApi {
40            fn get_path(&self) -> Cow<'static, str> {
41                "/_matrix/media/r0".into()
42            }
43        }
44    }
45}
46/// Future representing a response to a Matrix API call that isn't ready yet.
47///
48/// Returned by the `typed_api_response` method on a `MatrixRequestable`.
49pub struct TypedApiResponse<T, U, V> {
50    response: Option<U>,
51    body: Option<V>,
52    parts: Option<::http::response::Parts>,
53    _ph: PhantomData<T>,
54    discard: bool
55}
56impl<T, U, RB, V> Future for TypedApiResponse<T, U, V>
57    where T: DeserializeOwned + 'static,
58          RB: AsRef<[u8]>,
59          V: Future<Item = RB, Error = MatrixError>,
60          U: Future<Item = Response<V>, Error = MatrixError> {
61
62    type Item = T;
63    type Error = MatrixError;
64
65    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
66        use std::any::TypeId;
67        use std::ptr;
68
69        if self.response.is_some() {
70            let resp = try_ready!(self.response.as_mut().unwrap().poll());
71            self.response = None;
72            let (parts, body) = resp.into_parts();
73            self.parts = Some(parts);
74            self.body = Some(body);
75        }
76        let body = try_ready!(self.body.as_mut().unwrap().poll());
77        let parts = self.parts.take().unwrap();
78        if !parts.status.is_success() {
79            if let Ok(e) = ::serde_json::from_slice::<BadRequestReply>(body.as_ref()) {
80                return Err(MatrixError::BadRequest(e));
81            }
82            else {
83                return Err(MatrixError::HttpCode(parts.status));
84            }
85        }
86        let data = if TypeId::of::<T>() == TypeId::of::<()>() && self.discard {
87            // If the response type is (), and we have the discard flag set,
88            // magic up a () from thin air and use that instead of whatever
89            // the JSON response is.
90            unsafe {
91                ptr::read(&() as *const () as *const T)
92            }
93        }
94        else {
95            ::serde_json::from_slice::<T>(body.as_ref())?
96        };
97        Ok(Async::Ready(data))
98    }
99}
100/// Represents an object that can make requests to the Matrix Client-Server API.
101pub trait MatrixRequestable {
102    /// The type of the transaction ID returned by `get_txnid()`.
103    type Txnid: ::std::fmt::Display;
104    /// The type of the HTTP response body.
105    type ResponseBody: AsRef<[u8]>;
106    /// The type of the future returned as a HTTP response body.
107    ///
108    /// This is polled in order to get the response body.
109    type ResponseBodyFuture: Future<Item = Self::ResponseBody, Error = MatrixError> + 'static;
110    /// The type of the future returned by `send_request()`.
111    ///
112    /// Should return a HTTP `Response` with the body being `Self::ResponseBodyFuture`.
113    type SendRequestFuture: Future<Item = Response<Self::ResponseBodyFuture>, Error = MatrixError> + 'static;
114    /// Gets the client's URL.
115    fn get_url(&self) -> Cow<str>;
116    /// Checks whether the client is an Application Service (AS).
117    fn is_as(&self) -> bool { false }
118    /// Gets the client's access token.
119    fn get_access_token(&self) -> Cow<str>;
120    /// Gets the client's user ID.
121    fn get_user_id(&self) -> Cow<str>;
122    /// Gets a new transaction ID.
123    ///
124    /// Implementors should generate a unique ID, as this will be used by the server to ensure
125    /// idempotency of requests.
126    fn get_txnid(&mut self) -> Self::Txnid;
127    /// Send an arbitrary HTTP request to the Matrix homeserver.
128    fn send_request(&mut self, req: Request<Vec<u8>>) -> Self::SendRequestFuture;
129
130    /// Send an arbitrary HTTP request to the Matrix homeserver, and deserialize the JSON response
131    /// to a value of type T.
132    ///
133    /// If T is `()`, and `discard` is true, discards the response and returns `()`, no matter what
134    /// the server responds with.
135    fn typed_api_call<T>(&mut self, req: Request<Vec<u8>>, discard: bool) -> TypedApiResponse<T, Self::SendRequestFuture, Self::ResponseBodyFuture> where T: DeserializeOwned + 'static {
136        TypedApiResponse {
137            response: Some(self.send_request(req)),
138            body: None,
139            parts: None,
140            discard,
141            _ph: PhantomData
142        }
143    }
144}
145
146use self::apis::r0::*;
147/// A arbitrary request to an endpoint in the Matrix API.
148///
149/// To actually determine what URL is used for the request, two things are
150/// consulted: the request type, and the request endpoint. The request type
151/// specifies what Matrix API is being used (for example, the client-server API
152/// revision 0, under `/_matrix/client/r0`), while the endpoint determines what
153/// method is being called on that API.
154///
155/// This type has Super `Cow` Powers.
156pub struct MatrixRequest<'a, T, U = ClientApi> {
157    /// Request method (exported in the `http` module)
158    pub meth: Method,
159    /// API endpoint (e.g. `/sync`)
160    pub endpoint: Cow<'a, str>,
161    /// Query-string parameters.
162    pub params: HashMap<Cow<'a, str>, Cow<'a, str>>,
163    /// Request body (some type implementing `Serialize`).
164    ///
165    /// If this is empty (serialises to `{}`), it will not be sent. Therefore,
166    /// requests with no body should use `()` here.
167    pub body: T,
168    /// Request type.
169    pub typ: U
170}
171impl<'a, T, U> MatrixRequest<'a, T, U> where T: Serialize, U: ApiType {
172    /// Make a new `MatrixRequest`, specifying all possible options.
173    pub fn new<S: Into<Cow<'a, str>>>(meth: Method, endpoint: S, body: T, typ: U) -> Self {
174        Self {
175            meth,
176            endpoint: endpoint.into(),
177            params: HashMap::new(),
178            body,
179            typ
180        }
181    }
182}
183impl<'a> MatrixRequest<'a, ()> {
184    /// Makes a `MatrixRequest` with the following defaults:
185    ///
186    /// - `meth` and `endpoint` specified
187    /// - `params` set to an empty hashmap
188    /// - `body` set to ()
189    /// - `typ` set to `apis::r0::ClientApi`
190    pub fn new_basic<S: Into<Cow<'a, str>>>(meth: Method, endpoint: S) -> Self {
191        Self {
192            meth,
193            endpoint: endpoint.into(),
194            params: HashMap::new(),
195            body: (),
196            typ: ClientApi
197        }
198    }
199}
200impl<'a, 'b, 'c> MatrixRequest<'a, HashMap<Cow<'b, str>, Cow<'c, str>>> {
201    /// Makes a `MatrixRequest` with the following defaults:
202    ///
203    /// - `meth` and `endpoint` specified
204    /// - `body` converted from an iterator over `(T, U)` where T & U implement `Into<Cow<str>>`
205    /// - `params` set to an empty hashmap
206    /// - `typ` set to `apis::r0::ClientApi`
207    pub fn new_with_body<S, T, U, V>(meth: Method, endpoint: S, body: V) -> Self
208        where S: Into<Cow<'a, str>>,
209              T: Into<Cow<'b, str>>,
210              U: Into<Cow<'c, str>>,
211              V: IntoIterator<Item=(T, U)> {
212        let body = body.into_iter().map(|(t, u)| (t.into(), u.into()))
213            .collect();
214        Self {
215            meth,
216            endpoint: endpoint.into(),
217            params: HashMap::new(),
218            body,
219            typ: ClientApi
220        }
221    }
222}
223impl<'a, T> MatrixRequest<'a, T> where T: Serialize {
224    /// Like `new_with_body`, but takes a serializable object for `body`.
225    pub fn new_with_body_ser<S>(meth: Method, endpoint: S, body: T) -> Self
226        where S: Into<Cow<'a, str>> {
227        Self {
228            meth,
229            endpoint: endpoint.into(),
230            params: HashMap::new(),
231            body,
232            typ: ClientApi
233        }
234    }
235}
236impl<'a, T, U> MatrixRequest<'a, T, U> where T: Serialize, U: ApiType {
237    fn body(&self) -> MatrixResult<Vec<u8>> {
238        let body = serde_json::to_string(&self.body)?;
239        Ok(if body == "{}" {
240            vec![]
241        }
242        else {
243            body.into_bytes()
244        })
245    }
246    /// Make this `MatrixRequest` into a HTTP request.
247    pub fn make_request<C>(&self, client: &C) -> MatrixResult<Request<Vec<u8>>> where C: MatrixRequestable {
248        let body = self.body()?;
249        let mut params = format!("access_token={}", client.get_access_token());
250        if client.is_as() {
251            params += &format!("&user_id={}",
252                              utf8_percent_encode(&client.get_user_id(), DEFAULT_ENCODE_SET));
253        }
254        for (k, v) in self.params.iter() {
255            params += &format!("&{}={}",
256                              utf8_percent_encode(k.as_ref(), DEFAULT_ENCODE_SET),
257                              utf8_percent_encode(v.as_ref(), DEFAULT_ENCODE_SET));
258        }
259        let url = format!("{}{}{}?{}",
260                          client.get_url(),
261                          self.typ.get_path(),
262                          self.endpoint,
263                          params);
264        let req = Request::builder()
265            .method(self.meth.clone())
266            .uri(url)
267            .body(body)?;
268        Ok(req)
269    }
270    /// Sends this request to a Matrix homeserver, expecting a deserializable
271    /// `R` return type.
272    ///
273    /// A helpful mix of `make_hyper()` and `MatrixClient::send_request()`.
274    pub fn send<C, R>(&self, mxc: &mut C) -> impl Future<Item = R, Error = MatrixError> + 'static where R: DeserializeOwned + 'static, C: MatrixRequestable {
275        let req = match self.make_request(mxc) {
276            Ok(r) => r,
277            Err(e) => return Either::B(futures::future::err(e.into()))
278        };
279        Either::A(mxc.typed_api_call(req, false))
280    }
281    /// Like `send()`, but uses `MatrixClient::send_discarding_request()`.
282    pub fn discarding_send<'x, 'y, C>(&'x self, mxc: &'y mut C) -> impl Future<Item = (), Error = MatrixError> + 'static where C: MatrixRequestable {
283        let req = match self.make_request(mxc) {
284            Ok(r) => r,
285            Err(e) => return Either::B(futures::future::err(e.into()))
286        };
287        Either::A(mxc.typed_api_call(req, true))
288    }
289}