Restless

Restless is a (prototype) crate that allows you to define your REST API in Rust using the type system. Once you have defined it like this, Restless comes with support for various HTTP clients that you can use to make your API requests.

API clients it supports:

  • Reqwest
  • Gloo (for WASM web applications)
  • Axum (mock API requests to test services)

With Restless, every API request is fully captured by a struct type. You implement a specific trait for it, depending on the type of request. For example, to implement a GET request, you implement the GetRequest type.

Example

Imagine you have an API that lets your users search. The query string is /search?q=<text>. When you issue a search, the response is a JSON document that looks like this:

[
    {
        id: 2381912,
        title: "10 tips they don't want you to know about"
    }
]

To capture this API, you first write some Rust struct definitions to capture your request and the response you expect. Depending on how you use them,

#![allow(unused)]
fn main() {
#[derive(serde::Serialize)]
struct SearchRequest {
    #[serde(rename = "q")]
    query: String,
}

#[derive(serde::Deserialize)]
struct SearchResponseItem {
    id: u64,
    title: String,
}
}

Next, you implement the GetRequest with all the information that Restless needs to issue your request and interpret the response.

#![allow(unused)]
fn main() {
use restless::{*, data::Json, query::Qs};
use std::borrow::Cow;

impl GetRequest for SearchRequest {
    type Response = Json<Vec<SearchResponseItem>>;
    type Query = Qs<Self>;

    // query to use (?q=query)
    fn query(&self) -> Self::Query {
        self.clone()
    }

    // path to make request to (/search)
    fn path(&self) -> Cow<'_, str> {
        "search"
    }
}
}

With this done, you can now issue your request. Depending on the HTTP library you are using, this might work differently.