diff --git a/Cargo.toml b/Cargo.toml index ba9c6223..0f177ddc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,8 +11,13 @@ repository = "https://github.com/ruma/ruma-api" version = "0.3.0" [dependencies] -serde = "0.8.21" + +[dependencies.hyper] +git = "https://github.com/hyperium/hyper" +rev = "fed04dfb58e19b408322d4e5ca7474871e848a35" [dev-dependencies] -ruma-identifiers = "0.6.0" -serde_derive = "0.8.21" +ruma-identifiers = "0.11" +serde = "1.0" +serde_derive = "1.0" +serde_json = "1.0" diff --git a/src/lib.rs b/src/lib.rs index 8fd48458..0fe5181c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,133 +2,139 @@ //! in the various [Matrix](https://matrix.org) API specifications. //! These types can be shared by client and server code for all Matrix APIs. //! -//! When implementing a new Matrix API, each endpoint have a type that implements `Endpoint`, plus -//! any necessary associated types. +//! When implementing a new Matrix API, each endpoint has a type that implements `Endpoint`, plus +//! the necessary associated types. //! An implementation of `Endpoint` contains all the information about the HTTP method, the path and //! input parameters for requests, and the structure of a successful response. //! Such types can then be used by client code to make requests, and by server code to fulfill //! those requests. -//! -//! # Example -//! -//! ```rust,no_run -//! # #![feature(proc_macro)] -//! # -//! # extern crate ruma_api; -//! # extern crate ruma_identifiers; -//! # #[macro_use] -//! # extern crate serde_derive; -//! # -//! # fn main() { -//! /// PUT /_matrix/client/r0/directory/room/:room_alias -//! pub mod create { -//! use ruma_api; -//! use ruma_identifiers::{RoomAliasId, RoomId}; -//! -//! /// This API endpoint's body parameters. -//! #[derive(Clone, Debug, Deserialize, Serialize)] -//! pub struct BodyParams { -//! pub room_id: RoomId, -//! } -//! -//! /// This API endpoint's path parameters. -//! #[derive(Clone, Debug)] -//! pub struct PathParams { -//! pub room_alias: RoomAliasId, -//! } -//! -//! /// Details about this API endpoint. -//! pub struct Endpoint; -//! -//! impl ruma_api::Endpoint for Endpoint { -//! type BodyParams = BodyParams; -//! type PathParams = PathParams; -//! type QueryParams = (); -//! type Response = (); -//! -//! fn method() -> ruma_api::Method { -//! ruma_api::Method::Put -//! } -//! -//! fn request_path(params: Self::PathParams) -> String { -//! format!("/_matrix/client/r0/directory/room/{}", params.room_alias) -//! } -//! -//! fn router_path() -> &'static str { -//! "/_matrix/client/r0/directory/room/:room_alias" -//! } -//! -//! fn name() -> &'static str { -//! "room_directory" -//! } -//! -//! fn description() -> &'static str { -//! "Matrix implementation of room directory." -//! } -//! -//! fn requires_authentication() -> bool { -//! true -//! } -//! -//! fn rate_limited() -> bool { -//! false -//! } -//! } -//! } -//! # } +#![deny(missing_debug_implementations)] #![deny(missing_docs)] +#![feature(associated_consts, try_from)] -extern crate serde; +extern crate hyper; +#[cfg(test)] extern crate ruma_identifiers; +#[cfg(test)] extern crate serde; +#[cfg(test)] #[macro_use] extern crate serde_derive; +#[cfg(test)] extern crate serde_json; -use serde::{Deserialize, Serialize}; +use std::convert::{TryFrom, TryInto}; -/// HTTP request methods used in Matrix APIs. -#[derive(Clone, Copy, Debug)] -pub enum Method { - /// DELETE - Delete, - /// GET - Get, - /// POST - Post, - /// PUT - Put, -} +use hyper::{Method, Request, Response}; -/// An API endpoint. +/// A Matrix API endpoint. pub trait Endpoint { - /// Request parameters supplied via the body of the HTTP request. - type BodyParams: Deserialize + Serialize; + /// Data needed to make a request to the endpoint. + type Request: TryInto; + /// Data returned from the endpoint. + type Response: TryFrom; - /// Request parameters supplied via the URL's path. - type PathParams; - - /// Parameters supplied via the URL's query string. - type QueryParams: Deserialize + Serialize; - - /// The body of the response. - type Response: Deserialize + Serialize; - - /// Returns the HTTP method used by this endpoint. - fn method() -> Method; - - /// Generates the path component of the URL for this endpoint using the supplied parameters. - fn request_path(params: Self::PathParams) -> String; - - /// Generates a generic path component of the URL for this endpoint, suitable for `Router` from - /// the router crate. - fn router_path() -> &'static str; - - /// A unique identifier for this endpoint, suitable for `Router` from the router crate. - fn name() -> &'static str; - - /// A human-readable description of the endpoint. - fn description() -> &'static str; - - /// Whether or not this endpoint requires an authenticated user. - fn requires_authentication() -> bool; - - /// Whether or not this endpoint is rate limited. - fn rate_limited() -> bool; + /// Metadata about the endpoint. + const METADATA: Metadata; +} + +/// Metadata about an API endpoint. +#[derive(Clone, Debug)] +pub struct Metadata { + /// A human-readable description of the endpoint. + pub description: &'static str, + /// The HTTP method used by this endpoint. + pub method: Method, + /// A unique identifier for this endpoint. + pub name: &'static str, + /// The path of this endpoint's URL, with variable names where path parameters should be filled + /// in during a request. + pub path: &'static str, + /// Whether or not this endpoint is rate limited by the server. + pub rate_limited: bool, + /// Whether or not the server requires an authenticated user for this endpoint. + pub requires_authentication: bool, +} + +#[cfg(test)] +mod tests { + /// PUT /_matrix/client/r0/directory/room/:room_alias + pub mod create { + use std::convert::TryFrom; + + use hyper::{Method, Request as HyperRequest, Response as HyperResponse, StatusCode}; + use ruma_identifiers::{RoomAliasId, RoomId}; + use serde_json; + + use super::super::{Endpoint as ApiEndpoint, Metadata}; + + #[derive(Debug)] + pub struct Endpoint; + + #[derive(Debug)] + pub struct Error; + + impl ApiEndpoint for Endpoint { + type Request = Request; + type Response = Response; + + const METADATA: Metadata = Metadata { + description: "Add an alias to a room.", + method: Method::Put, + name: "create_alias", + path: "/_matrix/client/r0/directory/room/:room_alias", + rate_limited: false, + requires_authentication: true, + }; + + } + + /// A request to create a new room alias. + #[derive(Debug)] + pub struct Request { + pub room_id: RoomId, // body + pub room_alias: RoomAliasId, // path + } + + #[derive(Debug, Serialize)] + struct RequestBody { + room_id: RoomId, + } + + impl TryFrom for HyperRequest { + type Error = Error; + + fn try_from(request: Request) -> Result { + let metadata = Endpoint::METADATA; + + let path = metadata.path + .to_string() + .replace(":room_alias", &request.room_alias.to_string()); + + let mut hyper_request = HyperRequest::new( + metadata.method, + path.parse().map_err(|_| Error)?, + ); + + let request_body = RequestBody { + room_id: request.room_id, + }; + + hyper_request.set_body(serde_json::to_vec(&request_body).map_err(|_| Error)?); + + Ok(hyper_request) + } + } + + /// The response to a request to create a new room alias. + pub struct Response; + + impl TryFrom for Response { + type Error = Error; + + fn try_from(hyper_response: HyperResponse) -> Result { + if hyper_response.status() == StatusCode::Ok { + Ok(Response) + } else { + Err(Error) + } + } + } + } }