2个版本
使用旧的Rust 2015
0.0.1 | 2017年6月18日 |
---|---|
0.0.0 | 2017年6月8日 |
#27 in #response-body
在 tapioca 中使用
42KB
1K SLoC
Tapioca
Typed APIOllie Coshed into an Acronym)
tapioca 是一个用于 rust 的HTTP客户端,旨在帮助编译器帮助 你 以类型更安全的方式访问REST+JSON API。
它使用 OpenAPI联盟的方案规范 来推断路径和查询参数、请求和响应体等的类型,然后使用 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 还旨在防止你因为无效的 序列 请求(例如在特定资源上执行 GET
后跟 DELETE
)而伤害自己:这是通过仅从响应和静态值构造资源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
中,但任何其他由tapicoa使用的crate(不是用于宏的)将 不需要 这种处理,也不会污染您项目的命名空间。
访问客户端
infer_api
构建的模块包含每个API上可用路径的模块名称,每个模块包含一个适用于该资源的每个有效HTTP方法的功能。例如,要执行 GET /foobars
,函数标识符是
example::foobars::get
为了调用此函数,我们可能需要提供一些用于 认证、查询参数、请求体 等。的参数——这些参数的类型位于与函数同名的模块中,例如
example::foobars::get::QueryParams
认证
在我们发出请求之前,我们需要引入认证——目前,即使是null,也必须为每个请求指定认证。
OAS模式中的认证要求在两个级别上指定:全局服务器和特定操作——《tt class="src-rs">GET /foobars 可以有不同的要求,而其他操作可能只是继承自服务器要求。因此,我们有两种可接受的认证方案 enum
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
bool
String
Option<_>
Vec<_>
- 更进一步的
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
~314K SLoC