#json-api #json #api #json-rpc #rpc #http-request #http

seamless

一个具有偏见的库,可以轻松创建RPC风格的JSON API

15个版本 (重大更改)

0.11.0 2023年1月21日
0.10.0 2022年1月1日
0.9.0 2021年12月23日
0.8.0 2021年6月3日
0.7.1 2021年2月21日

#333 in HTTP服务器

每月48次下载
用于 seamless_macros

MIT 许可证

77KB
1K SLoC

Seamless

API文档

这个库旨在提供一个易于使用且可扩展的方法来声明API路由,并会自动跟踪请求和响应周围的各项信息,从而可以根据API的当前状态生成详细的路由信息(包括类型信息)。

这个库是完全异步的,但与任何特定的异步实现无关(你甚至不需要切换任何功能标志)。

使用Seamless,我们可以用纯Rust编写我们的API,然后根据API的当前状态生成基于TypeScript的API客户端(或只是类型,或只是文档)。这允许我们在不依赖外部规范如OpenAPI的情况下,实现API和类似TypeScript客户端之间的类型安全通信。

使用此库的典型步骤是

  • 使用 [macro@ApiBody] 宏对这些路由的输入和输出类型进行注释。
  • 为任何你希望发出的错误推导 [macro@ApiError](或手动实现 Into<ApiError>)。
  • 使用此库声明每个API路由。API处理程序只需作为函数参数请求它们需要的内容,包括基于传入请求的任意状态或信息(由你决定)。
  • 一旦声明了API路由,就使用 [Api::info()] 获取有关API的足够信息以生成完全类型安全的客户端代码(信息已优化以生成TypeScript类型/代码)。
  • 将此API与类似 warprocket 的东西集成,以便你的 seamless API路由可以与你要服务的一切内容一起存在(参见示例以了解如何进行此操作)。

查看 examples 目录中的示例,以了解如何使用此库,或继续阅读!

基本示例

以下是一个使用本库的基本自包含示例。

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<String>| ToJson(body.0));
api.add("/reverse")
    .description("Reverse an array of numbers")
    .handler(|body: FromJson<Vec<usize>>|
        ToJson(body.0.into_iter().rev().collect::<Vec<usize>>())
    );
api.add("/maths.divide")
   .description("Divide two numbers by each other")
   .handler(|body: FromJson<DivisionInput>| async move {
       let a = body.0.a;
       let b = body.0.b;
       a.checked_div(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::Request`s 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::from_vec(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()
);

请查看API文档以获取更多信息,或查看示例。

依赖项

~4MB
~79K SLoC