[][src]Crate seamless

An opinionated library to easily plug RPC style JSON APIs into your existing HTTP framework.

Here's what using it might look like:

use seamless::{ Api, ApiBody, ApiError, Json };
use http::{ Request, Response };

/* Step 1: Define some types that can be provided or handed back */

/// Provide two numbers to get back the division of them.
#[ApiBody]
struct DivisionInput {
    a: usize,
    b: usize
}

/// The division of two numbers `a` and `b`.
#[ApiBody]
#[derive(PartialEq)]
struct DivisionOutput {
    a: usize,
    b: usize,
    /// The division of the first and second number
    result: usize
}

/// We can use `seamless::ApiError` to easily allow an existing
/// enum or struct to be converted into an `ApiError`. We use `thiserror`
/// here to generate Display impls, which are what the internal and external
/// messages will show unless otherwise given.
#[derive(ApiError, Debug, thiserror::Error, PartialEq)]
enum DivisionError {
    #[error("Division by zero")]
    #[api_error(external, code=400)]
    DivideByZero
}

/* Step 2: Define route handlers */

let mut api = Api::new();

api.add("maths.divide")
   .description("Divide two numbers by each other")
   .handler(|body: Json<DivisionInput>| async move {
       let a = body.json.a;
       let b = body.json.b;
       a.checked_div(b)
           .ok_or(DivisionError::DivideByZero)
           .map(|result| DivisionOutput { a, b, result })
   });

/*
 * Step 3: Handle incoming http requests. As long as you can get an http::Request
 * out of your framework of choice, and handle an http::Response, you can plug this
 * api in.
 */

let req = Request::post("/maths.divide")
   .body(serde_json::to_vec(&DivisionInput { a: 20, b: 10 }).unwrap())
   .unwrap();
assert_eq!(
    api.handle(req).await.unwrap().into_body(),
    serde_json::to_vec(&DivisionOutput{ a: 20, b: 10, result: 2 }).unwrap()
);

let req = Request::post("/maths.divide")
   .body(serde_json::to_vec(&DivisionInput { a: 10, b: 0 }).unwrap())
   .unwrap();
assert_eq!(
    api.handle(req).await.unwrap_err().unwrap_api_error(),
    ApiError {
        code: 400,
        internal_message: "Division by zero".to_owned(),
        external_message: "Division by zero".to_owned(),
        value: None
    }
);

Re-exports

pub use router::Api;
pub use router::Context;
pub use router::RouteError;
pub use router::RouteInfo;
pub use router::Json;
pub use router::Binary;
pub use body::ApiBody;
pub use body::ApiBodyType;
pub use body::Type;
pub use error::ApiError;

Modules

body

A collection of types and such describing the JSON body that is handed back or provided in requests to the API.

error

A standard error type that describes what an erroneous API response should look like.

router

A router implementation that can handle requests in a type safe way, while also allowing information about the routes, route descriptions and expected input and output types to be automatically generated from it.

Structs

Method

The Request Method (VERB)

Attribute Macros

ApiBody

Use this macro to generate serde Serialize/Deserialize impls in addition to an ApiBody impl that can hand back information about the shape of the type.

async_trait

Derive Macros

ApiError