2个版本
使用旧的Rust 2015
0.0.1 | 2017年6月18日 |
---|---|
0.0.0 | 2017年6月8日 |
#65 在 #rest-client
14KB
347 行
Tapioca
Typed API s(Oliver Coshed into an Acronym)
tapioca 是一个针对 rust 的HTTP客户端,旨在帮助编译器帮助 你 以类型更安全的方式访问REST+JSON API。
它使用 OpenAPI Initiative的架构规范 来推断路径和查询参数、请求和响应正文等类型,然后使用 serde 进行反序列化和序列化。
infer_api!(service, "https://service.api/schema.yml")
use service::path;
fn main() {
let auth = service::ServerAuth::new();
match path::get(&auth) {
Ok(response) => match response.body() {
path::OkBody::Status200(body) => println!("Thing is: {}", body.thing),
path::OkBody::UnspecifiedCode(body) => {
// We're forced to handle every status code in the schema;
// including the possibility that the server replies off-script.
println!("I don't know what thing is!")
},
},
Err(response) => match response.body() {
path::ErrBody::Status403(body) => println!("That's not my thing"),
path::ErrBody::UnspecifiedCode(_)
| path::ErrBody::MalformedJson(_)
| path::ErrBody::NetworkFailure() => println!("Something went wrong"),
},
}
}
因此,我们可以通过状态码来匹配响应,并将JSON响应作为 rust 类型访问。
tapioca 还旨在防止你因无效的 序列 请求而自伤,例如在特定资源上在 DELETE
后执行 GET
:这是通过仅从响应和静态值中构建资源ID来实现的。 DELETE
函数会导致资源ID参数被 移动(而其他方法只 借用),防止其进一步使用。
入门
要开始在项目中使用 tapioca,第一步是找到你希望使用的API的OAS架构。假设它位于 https://example.org/schema.yml
。然后,像往常一样将其最新版本添加到您的 Cargo.toml
中,并使用宏导入 tapioca
#[macro_use]
extern crate tapioca;
并调用 infer_api
宏来构建API的客户端
infer_api!(example, "https://example.org/schema.yml");
宏在编译时展开,就地构建类型化客户端;(几乎)它生成的所有代码都将位于名为 example
的模块下,或者我们在第一个参数中指定的任何内容。唯一的例外是两个crate(必须在根级别加载),您需要在您的crate内部导出以使用它们的宏 - 至少目前,Rust的宏系统正在经历很多变化,这可能会得到改善。这些是 serde_derive
和 tapicoa_codegen
;因此,它们也需要在您的 Cargo.toml
中,但tapioca使用的其他crate(不是用于宏的)不需要这种处理,也不会污染您的项目命名空间。
访问客户端
由 infer_api
构建的模块包含了API上每个路径的模块,每个模块都包含针对该资源的每个有效HTTP方法的函数。例如,对 GET /foobars
的函数标识符是
example::foobars::get
为了调用此函数,我们可能需要提供一些用于 认证、查询参数、请求体 等——这些类型的定义位于与函数同名的模块中,例如
example::foobars::get::QueryParams
认证
在发送请求之前,我们需要介绍认证——目前,每个请求都必须指定认证,即使它是null。
在OAS模式中,认证需求在两个级别上进行指定:全局服务器级和操作级——GET /foobars
可以有不同的需求,而其他操作可能只是继承自服务器需求。因此,我们有两种可接受的认证方案的枚举类型
example::ServerAuth
example::foobars::get::OpAuth
其使用取决于操作 examples::foobars::get
是否覆盖了全局认证需求——但类型检查器会告诉我们是否出错。
如果根本不需要认证,我们可以直接使用
example::ServerAuth::new();
如果是HTTP基本认证,那么(取决于是否是服务器或操作需求)
example::ServerAuth::Basic(username: String, password:String);
example::foobars::get::OpAuth::Basic(username: String, password:String);
如果是自定义头
example::ServerAuth::ApiKey(api_key: String);
example::foobars::get::OpAuth::ApiKey(api_key: String);
但请注意,变体标识符,例如 Basic
或 ApiKey
,取决于OAS模式中使用的名称。这是因为可能有多个相同类型的定义。
发送请求
既然我们已经看到了如何构造认证参数,我们实际上可以 GET
一些 foobars
了!
let auth = examples::ServerAuth::new();
let response = examples::foobars::get(&auth);
response
实际上是一个 Result<Response, Response>
:如果响应状态码是错误,我们得到一个 Err(response)
,否则它是一个 Ok(response)
。这意味着我们可以使用 response.is_ok
、response.is_err
和模式匹配
match examples::foobars::get(&auth) {
Ok(response) => foobar_handler(response),
Err(response) => err_handler(response),
}
我们可以在每个这些处理程序中使用进一步的模式匹配,以对不同的状态码做出不同的响应
fn foobar_handler(response: Response) {
match response.body {
OkBody::Status200(body) => {
for foobar in body.the_foobars {
println!("Foobar {} is named {}", foobar.id, foobar.name);
}
},
OkBody::UnspecifiedCode(body)
| OkBody::MalformedJson(body) => something_else(),
}
}
其中我们始终有 UnspecifiedCode
(不在模式中的代码)和 MalformedJson
(无效的JSON或未匹配模式)以及为模式中指定的每个可能性提供一个 StatusXXX
。 err_handler
的外观类似,有 ErrBody::Status403
等。
请求体
假设这个 example::foobars
集合还支持 POST
新的 foobar
,我们可以像这样提供请求体来创建一个
let body = example::foobars::post::RequestBody {
name: "Foobarry".into(),
age: 12,
email: None,
};
请求体的结构和字段类型完全由模式定义,可能包括
i32
、i64
布尔值
字符串
可选<_>
向量<_>
- 进一步的
struct
结构
查询参数
查询参数的提供方式与 请求体 类似
let query = example::foobars::get::QueryParams {
age: 34,
};
路径参数
路径参数略有不同。由于需要区分 example::foobars::get
与该集合中单个资源的 GET
操作,路径参数的名称被编码在路径模块名称中,例如
example::foobars__id_::get
如果API连续指定两个资源标识符,这将表示为 foobars__id1___id2_
。这看起来很糟糕,并且可能在未来的版本中更改。
路径参数可以从响应中构建,例如在创建新资源时服务器生成其ID,或从静态引用中
static provisioned_id = "fea3c8e91baa1";
fn main() {
let auth = example::ServerAuth::new();
let resource = examples::foobars__id_::Resource_id::from_static(provisioned_id);
example::foobars__id_::get(&resource, &auth);
}
依赖关系
~10–19MB
~311K SLoC