logo
ruma_api!() { /* proc-macro */ }
Expand description

Generates IncomingRequest and OutgoingRequest from a concise definition.

The macro expects the following structure as input:

ruma_api! {
    metadata: {
        description: &'static str,
        method: http::Method,
        name: &'static str,
        path: &'static str,
        rate_limited: bool,
        authentication: ruma_common::api::AuthScheme,
    }

    request: {
        // Struct fields for each piece of data required
        // to make a request to this API endpoint.
    }

    response: {
        // Struct fields for each piece of data expected
        // in the response from this API endpoint.
    }

    // The error returned when a response fails, defaults to `MatrixError`.
    error: path::to::Error
}

This will generate a Metadata value to be used for the associated constants of IncomingRequest and OutgoingRequest, single Request and Response structs, and the necessary trait implementations to convert the request into a http::Request and to create a response from a http::Response and vice versa.

The details of each of the three sections of the macros are documented below.

Metadata

  • description: A short description of what the endpoint does.
  • method: The HTTP method used for requests to the endpoint. It’s not necessary to import http::Method’s associated constants. Just write the value as if it was imported, e.g. GET.
  • name: A unique name for the endpoint. Generally this will be the same as the containing module.
  • path: The path component of the URL for the endpoint, e.g. “/foo/bar”. Components of the path that are parameterized can indicate a variable by using a Rust identifier prefixed with a colon, e.g. /foo/:some_parameter. A corresponding query string parameter will be expected in the request struct (see below for details).
  • rate_limited: Whether or not the endpoint enforces rate limiting on requests.
  • authentication: What authentication scheme the endpoint uses.

Request

The request block contains normal struct field definitions. Doc comments and attributes are allowed as normal. There are also a few special attributes available to control how the struct is converted into an http::Request:

  • #[ruma_api(header = HEADER_NAME)]: Fields with this attribute will be treated as HTTP headers on the request. The value must implement AsRef<str>. Generally this is a String. The attribute value shown above as HEADER_NAME must be a header name constant from http::header, e.g. CONTENT_TYPE.
  • #[ruma_api(path)]: Fields with this attribute will be inserted into the matching path component of the request URL.
  • #[ruma_api(query)]: Fields with this attribute will be inserting into the URL’s query string.
  • #[ruma_api(query_map)]: Instead of individual query fields, one query_map field, of any type that implements IntoIterator<Item = (String, String)> (e.g. HashMap<String, String>, can be used for cases where an endpoint supports arbitrary query parameters.

Any field that does not include one of these attributes will be part of the request’s JSON body.

Response

Like the request block, the response block consists of normal struct field definitions. Doc comments and attributes are allowed as normal. There is also a special attribute available to control how the struct is created from a http::Request:

  • #[ruma_api(header = HEADER_NAME)]: Fields with this attribute will be treated as HTTP headers on the response. The value must implement AsRef<str>. Generally this is a String. The attribute value shown above as HEADER_NAME must be a header name constant from http::header, e.g. CONTENT_TYPE.

Any field that does not include the above attribute will be expected in the response’s JSON body.

Newtype bodies

Both the request and response block also support “newtype bodies” by using the #[ruma_api(body)] attribute on a field. If present on a field, the entire request or response body will be treated as the value of the field. This allows you to treat the entire request or response body as a specific type, rather than a JSON object with named fields. Only one field in each struct can be marked with this attribute. It is an error to have a newtype body field and normal body fields within the same struct.

There is another kind of newtype body that is enabled with #[ruma_api(raw_body)]. It is used for endpoints in which the request or response body can be arbitrary bytes instead of a JSON objects. A field with #[ruma_api(raw_body)] needs to have the type Vec<u8>.

Examples

pub mod some_endpoint {
    use ruma_common::api::ruma_api;

    ruma_api! {
        metadata: {
            description: "Does something.",
            method: POST,
            name: "some_endpoint",
            stable_path: "/_matrix/some/endpoint/:baz",
            rate_limited: false,
            authentication: None,
            added: 1.1,
        }

        request: {
            pub foo: String,

            #[ruma_api(header = CONTENT_TYPE)]
            pub content_type: String,

            #[ruma_api(query)]
            pub bar: String,

            #[ruma_api(path)]
            pub baz: String,
        }

        response: {
            #[ruma_api(header = CONTENT_TYPE)]
            pub content_type: String,

            pub value: String,
        }
    }
}

pub mod newtype_body_endpoint {
    use ruma_common::api::ruma_api;
    use serde::{Deserialize, Serialize};

    #[derive(Clone, Debug, Deserialize, Serialize)]
    pub struct MyCustomType {
        pub foo: String,
    }

    ruma_api! {
        metadata: {
            description: "Does something.",
            method: PUT,
            name: "newtype_body_endpoint",
            stable_path: "/_matrix/some/newtype/body/endpoint",
            rate_limited: false,
            authentication: None,
            added: 1.1,
        }

        request: {
            #[ruma_api(raw_body)]
            pub file: &'a [u8],
        }

        response: {
            #[ruma_api(body)]
            pub my_custom_type: MyCustomType,
        }
    }
}

A procedural macro for easily generating API endpoints.

Note that for technical reasons, the ruma_api! macro is only documented in ruma-common, not here.