Seamless

API Docs

This library aims to provide an easy-to-use and extensible approach to declaring API routes, and will automatically keep track of various information surrounding requests and responses so that detailed route information (including type information) can be generated based on the current state of the API.

This library is fully async, but totally independent from any particular async implementation (you don't even need to toggle any feature flags).

Using Seamless, we can write our API in pure Rust, and from that generate a TypeScript based API client (or just the types, or just documentation) based on the current state of the API. This allows us to achieve type safe communication between the API and TypeScript-like clients without relying on external specifications like OpenAPI.

The typical steps for using this library are:

Have a look at the examples in the examples directory to get a proper feel for how this library can be used, or keep reading!

A Basic Example

Below is a basic self contained example of using this library.

```rust use seamless::{ http::{ Request }, api::{ Api, ApiBody, ApiError }, handler::{ body::FromJson, request::Bytes, response::ToJson } };

// The API relies on types that have been annotated with ApiBody (request and response // types) or ApiError (for any errors we might give back). These annotations do some // reflection to allow us to get information about the shape of the type and doc comments // added to it, as well as ensuring that they can be Serialized/Deserialized.

[ApiBody]

struct DivisionInput { a: usize, b: usize }

[ApiBody]

[derive(PartialEq)]

struct DivisionOutput { a: usize, b: usize, result: usize }

// Any errors that we return must implement Into<ApiError>, Display and Debug. We can derive // ApiError to automate this for us. Here we use thiserror to derive the Display impl // for us. See the documentation on the ApiError macro for more info.

[derive(ApiError, Debug, thiserror::Error, PartialEq)]

enum MathsError { #[error("Division by zero")] #[api_error(external, code=400)] DivideByZero }

let mut api = Api::new();

// We add routes to our new API like so. The handler functions would often be defined // separately and called from this handler. Handler functions can be async or sync, and can // return any valid handler::HandlerResponse. api.add("/echo") .description("Echoes back a JSON string") .handler(|body: FromJson| ToJson(body.0)); api.add("/reverse") .description("Reverse an array of numbers") .handler(|body: FromJson>| ToJson(body.0.intoiter().rev().collect::>()) ); api.add("/maths.divide") .description("Divide two numbers by each other") .handler(|body: FromJson| async move { let a = body.0.a; let b = body.0.b; a.checkeddiv(b) .ok_or(MathsError::DivideByZero) .map(|result| ToJson(DivisionOutput { a, b, result })) });

// Once we've added routes to the api, we use it by sending http::Requests to it. // Since we're expecting JSON to be provided, we need to remember to set the correct // content-type:

let req = Request::post("/maths.divide") .header("content-type", "application/json") .body(Bytes::fromvec(serdejson::tovec(&DivisionInput { a: 20, b: 10 }).unwrap())) .unwrap(); asserteq!( api.handle(req).await.unwrap().intobody(), serdejson::to_vec(&DivisionOutput{ a: 20, b: 10, result: 2 }).unwrap() ); ```

Check out the API Docs for lots more information, or take a look at the examples.