1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
//! Type for making a generic request to the Matrix API.

use std::borrow::Cow;
use std::collections::HashMap;
use serde::Serialize;
use serde::de::DeserializeOwned;
use http::{Request, Response, Method};
use futures::future::Either;
use crate::errors::{MatrixError, MatrixResult};
use types::replies::BadRequestReply;
use serde_json;
use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET};
use futures::{self, Future, Poll, Async, try_ready};
use std::marker::PhantomData;

/// Describes the type of a Matrix API.
pub trait ApiType {
    /// Get the base path which all requests to this API should contain.
    ///
    /// For example, `ClientApi`, the struct for the client-server API, sets
    /// this method to return `/_matrix/client/r0`
    fn get_path<'a>(&'a self) -> Cow<'a, str>;
}
/// Types of Matrix APIs.
pub mod apis {
    /// APIs at version r0.
    pub mod r0 {
        use crate::request::ApiType;
        use std::borrow::Cow;
        /// `/_matrix/client/r0`
        pub struct ClientApi;
        impl ApiType for ClientApi {
            fn get_path(&self) -> Cow<'static, str> {
                "/_matrix/client/r0".into()
            }
        }
        /// `/_matrix/media/r0`
        pub struct MediaApi;
        impl ApiType for MediaApi {
            fn get_path(&self) -> Cow<'static, str> {
                "/_matrix/media/r0".into()
            }
        }
    }
}
/// Future representing a response to a Matrix API call that isn't ready yet.
///
/// Returned by the `typed_api_response` method on a `MatrixRequestable`.
pub struct TypedApiResponse<T, U, V> {
    response: Option<U>,
    body: Option<V>,
    parts: Option<::http::response::Parts>,
    _ph: PhantomData<T>,
    discard: bool
}
impl<T, U, RB, V> Future for TypedApiResponse<T, U, V>
    where T: DeserializeOwned + 'static,
          RB: AsRef<[u8]>,
          V: Future<Item = RB, Error = MatrixError>,
          U: Future<Item = Response<V>, Error = MatrixError> {

    type Item = T;
    type Error = MatrixError;

    fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
        use std::any::TypeId;
        use std::ptr;

        if self.response.is_some() {
            let resp = try_ready!(self.response.as_mut().unwrap().poll());
            self.response = None;
            let (parts, body) = resp.into_parts();
            self.parts = Some(parts);
            self.body = Some(body);
        }
        let body = try_ready!(self.body.as_mut().unwrap().poll());
        let parts = self.parts.take().unwrap();
        if !parts.status.is_success() {
            if let Ok(e) = ::serde_json::from_slice::<BadRequestReply>(body.as_ref()) {
                return Err(MatrixError::BadRequest(e));
            }
            else {
                return Err(MatrixError::HttpCode(parts.status));
            }
        }
        let data = if TypeId::of::<T>() == TypeId::of::<()>() && self.discard {
            // If the response type is (), and we have the discard flag set,
            // magic up a () from thin air and use that instead of whatever
            // the JSON response is.
            unsafe {
                ptr::read(&() as *const () as *const T)
            }
        }
        else {
            ::serde_json::from_slice::<T>(body.as_ref())?
        };
        Ok(Async::Ready(data))
    }
}
/// Represents an object that can make requests to the Matrix Client-Server API.
pub trait MatrixRequestable {
    /// The type of the transaction ID returned by `get_txnid()`.
    type Txnid: ::std::fmt::Display;
    /// The type of the HTTP response body.
    type ResponseBody: AsRef<[u8]>;
    /// The type of the future returned as a HTTP response body.
    ///
    /// This is polled in order to get the response body.
    type ResponseBodyFuture: Future<Item = Self::ResponseBody, Error = MatrixError> + 'static;
    /// The type of the future returned by `send_request()`.
    ///
    /// Should return a HTTP `Response` with the body being `Self::ResponseBodyFuture`.
    type SendRequestFuture: Future<Item = Response<Self::ResponseBodyFuture>, Error = MatrixError> + 'static;
    /// Gets the client's URL.
    fn get_url(&self) -> Cow<str>;
    /// Checks whether the client is an Application Service (AS).
    fn is_as(&self) -> bool { false }
    /// Gets the client's access token.
    fn get_access_token(&self) -> Cow<str>;
    /// Gets the client's user ID.
    fn get_user_id(&self) -> Cow<str>;
    /// Gets a new transaction ID.
    ///
    /// Implementors should generate a unique ID, as this will be used by the server to ensure
    /// idempotency of requests.
    fn get_txnid(&mut self) -> Self::Txnid;
    /// Send an arbitrary HTTP request to the Matrix homeserver.
    fn send_request(&mut self, req: Request<Vec<u8>>) -> Self::SendRequestFuture;

    /// Send an arbitrary HTTP request to the Matrix homeserver, and deserialize the JSON response
    /// to a value of type T.
    ///
    /// If T is `()`, and `discard` is true, discards the response and returns `()`, no matter what
    /// the server responds with.
    fn typed_api_call<T>(&mut self, req: Request<Vec<u8>>, discard: bool) -> TypedApiResponse<T, Self::SendRequestFuture, Self::ResponseBodyFuture> where T: DeserializeOwned + 'static {
        TypedApiResponse {
            response: Some(self.send_request(req)),
            body: None,
            parts: None,
            discard,
            _ph: PhantomData
        }
    }
}

use self::apis::r0::*;
/// A arbitrary request to an endpoint in the Matrix API.
///
/// To actually determine what URL is used for the request, two things are
/// consulted: the request type, and the request endpoint. The request type
/// specifies what Matrix API is being used (for example, the client-server API
/// revision 0, under `/_matrix/client/r0`), while the endpoint determines what
/// method is being called on that API.
///
/// This type has Super `Cow` Powers.
pub struct MatrixRequest<'a, T, U = ClientApi> {
    /// Request method (exported in the `http` module)
    pub meth: Method,
    /// API endpoint (e.g. `/sync`)
    pub endpoint: Cow<'a, str>,
    /// Query-string parameters.
    pub params: HashMap<Cow<'a, str>, Cow<'a, str>>,
    /// Request body (some type implementing `Serialize`).
    ///
    /// If this is empty (serialises to `{}`), it will not be sent. Therefore,
    /// requests with no body should use `()` here.
    pub body: T,
    /// Request type.
    pub typ: U
}
impl<'a, T, U> MatrixRequest<'a, T, U> where T: Serialize, U: ApiType {
    /// Make a new `MatrixRequest`, specifying all possible options.
    pub fn new<S: Into<Cow<'a, str>>>(meth: Method, endpoint: S, body: T, typ: U) -> Self {
        Self {
            meth,
            endpoint: endpoint.into(),
            params: HashMap::new(),
            body,
            typ
        }
    }
}
impl<'a> MatrixRequest<'a, ()> {
    /// Makes a `MatrixRequest` with the following defaults:
    ///
    /// - `meth` and `endpoint` specified
    /// - `params` set to an empty hashmap
    /// - `body` set to ()
    /// - `typ` set to `apis::r0::ClientApi`
    pub fn new_basic<S: Into<Cow<'a, str>>>(meth: Method, endpoint: S) -> Self {
        Self {
            meth,
            endpoint: endpoint.into(),
            params: HashMap::new(),
            body: (),
            typ: ClientApi
        }
    }
}
impl<'a, 'b, 'c> MatrixRequest<'a, HashMap<Cow<'b, str>, Cow<'c, str>>> {
    /// Makes a `MatrixRequest` with the following defaults:
    ///
    /// - `meth` and `endpoint` specified
    /// - `body` converted from an iterator over `(T, U)` where T & U implement `Into<Cow<str>>`
    /// - `params` set to an empty hashmap
    /// - `typ` set to `apis::r0::ClientApi`
    pub fn new_with_body<S, T, U, V>(meth: Method, endpoint: S, body: V) -> Self
        where S: Into<Cow<'a, str>>,
              T: Into<Cow<'b, str>>,
              U: Into<Cow<'c, str>>,
              V: IntoIterator<Item=(T, U)> {
        let body = body.into_iter().map(|(t, u)| (t.into(), u.into()))
            .collect();
        Self {
            meth,
            endpoint: endpoint.into(),
            params: HashMap::new(),
            body,
            typ: ClientApi
        }
    }
}
impl<'a, T> MatrixRequest<'a, T> where T: Serialize {
    /// Like `new_with_body`, but takes a serializable object for `body`.
    pub fn new_with_body_ser<S>(meth: Method, endpoint: S, body: T) -> Self
        where S: Into<Cow<'a, str>> {
        Self {
            meth,
            endpoint: endpoint.into(),
            params: HashMap::new(),
            body,
            typ: ClientApi
        }
    }
}
impl<'a, T, U> MatrixRequest<'a, T, U> where T: Serialize, U: ApiType {
    fn body(&self) -> MatrixResult<Vec<u8>> {
        let body = serde_json::to_string(&self.body)?;
        Ok(if body == "{}" {
            vec![]
        }
        else {
            body.into_bytes()
        })
    }
    /// Make this `MatrixRequest` into a HTTP request.
    pub fn make_request<C>(&self, client: &C) -> MatrixResult<Request<Vec<u8>>> where C: MatrixRequestable {
        let body = self.body()?;
        let mut params = format!("access_token={}", client.get_access_token());
        if client.is_as() {
            params += &format!("&user_id={}",
                              utf8_percent_encode(&client.get_user_id(), DEFAULT_ENCODE_SET));
        }
        for (k, v) in self.params.iter() {
            params += &format!("&{}={}",
                              utf8_percent_encode(k.as_ref(), DEFAULT_ENCODE_SET),
                              utf8_percent_encode(v.as_ref(), DEFAULT_ENCODE_SET));
        }
        let url = format!("{}{}{}?{}",
                          client.get_url(),
                          self.typ.get_path(),
                          self.endpoint,
                          params);
        let req = Request::builder()
            .method(self.meth.clone())
            .uri(url)
            .body(body)?;
        Ok(req)
    }
    /// Sends this request to a Matrix homeserver, expecting a deserializable
    /// `R` return type.
    ///
    /// A helpful mix of `make_hyper()` and `MatrixClient::send_request()`.
    pub fn send<C, R>(&self, mxc: &mut C) -> impl Future<Item = R, Error = MatrixError> + 'static where R: DeserializeOwned + 'static, C: MatrixRequestable {
        let req = match self.make_request(mxc) {
            Ok(r) => r,
            Err(e) => return Either::B(futures::future::err(e.into()))
        };
        Either::A(mxc.typed_api_call(req, false))
    }
    /// Like `send()`, but uses `MatrixClient::send_discarding_request()`.
    pub fn discarding_send<'x, 'y, C>(&'x self, mxc: &'y mut C) -> impl Future<Item = (), Error = MatrixError> + 'static where C: MatrixRequestable {
        let req = match self.make_request(mxc) {
            Ok(r) => r,
            Err(e) => return Either::B(futures::future::err(e.into()))
        };
        Either::A(mxc.typed_api_call(req, true))
    }
}