2 个不稳定版本
0.3.0 | 2023 年 11 月 17 日 |
---|---|
0.1.0 | 2023 年 10 月 9 日 |
#25 在 #json-api
在 cloudconvert 中使用
53KB
845 行
HTTP API 客户端 (hapic)
一个用于快速创建易于使用的 HTTP API 客户端库的 Rust 包,特别是在 HTTP JSON API 方面有很多工具。
这仍在进行中。
示例
有关示例的说明,请参阅 包文档。
有关完整客户端,请参阅 cloudconvert
包。
测试
要进行测试,您需要运行 ./test-api-server(只需使用 cargo run
)。
然后,只需在 ./hapic 中使用 cargo test
即可。
lib.rs
:
一个用于快速创建易于使用的 HTTP API 客户端库的 Rust 包,特别是在 HTTP JSON API 方面有很多工具。
这仍在进行中。
示例:定义 JSON API 客户端
超级简单
我们将从一个简单的虚拟 API 开始,它有以下端点
>> POST /add
>> { "a": 2, "b": 3 }
<< { "c": 5 }
>> POST /sub
>> { "a": 6, "b": 3 }
<< { "c": 3 }
>> POST /factorial
>> { "a": 4 }
<< { "c": 24 }
我们可以这样定义这个 API 的客户端
use hapic::json_api;
use serde::{Deserialize, Serialize};
#[derive(Serialize)]
struct Add {
a: u32,
b: u32,
}
#[derive(Serialize)]
struct Sub {
a: u32,
b: u32,
}
#[derive(Serialize)]
struct Factorial {
a: u8,
}
#[derive(Deserialize, PartialEq, Eq, Debug)]
struct Output {
c: u64,
}
json_api!(
struct TestClient<B, T: Transport<B>>;
trait TestApiCall;
simple {
"/add": Add => Output;
"/sub": Sub => Output;
"/factorial": Factorial => Output;
}
);
现在,调用类型(Add
、Sub
和 Factorial
)都实现了 TestApiCall
。
我们可以使用以下方式调用 API(到 https://127.0.0.1:8080
)
#
#
#
#
#
#
let client = TestClient::new("https://127.0.0.1:8000".into());
let output = client.call(Add { a: 1, b: 2 }).await.unwrap();
assert_eq!(output, Output { c: 3 });
带有转换
现在假设我们有一个端点用于所有这些操作
>> POST /op
>> { "a": 2, "b": 3, "operation": "add" }
<< { "c": 5 }
>> POST /op
>> { "a": 6, "b": 3, "operation": "sub" }
<< { "c": 3 }
>> POST /op
>> { "a": 4, "operation": "factorial" }
<< { "c": 24 }
我们可以定义以下类型
#[derive(serde::Serialize)]
struct Operation {
operation: &'static str,
a: u32,
#[serde(skip_serializing_if = "Option::is_none")]
b: Option<u32>,
}
这不是很符合习惯!首先,用户可以输入任何字符串作为操作,但我们知道我们的 API 服务器只接受 add
、sub
或 factorial
。此外,如果 b
在阶乘操作中为 Some
,或者在 add
或 sub
中为 None
,我们将有一个无效的调用。
我们希望使其安全,因此我们可以定义两个类型,一个是 API 调用类型,另一个将作为 JSON 主体发送。
#
enum Operation {
Add((u32, u32)),
Sub((u32, u32)),
Factorial(u8),
}
#[derive(serde::Serialize)]
struct JsonOperation {
operation: &'static str,
a: u32,
#[serde(skip_serializing_if = "Option::is_none")]
b: Option<u32>,
}
impl From<Operation> for JsonOperation {
fn from(op: Operation) -> JsonOperation {
match op {
Operation::Add((a, b)) => JsonOperation {
operation: "add",
a,
b: Some(b),
},
Operation::Sub((a, b)) => JsonOperation {
operation: "sub",
a,
b: Some(b),
},
Operation::Factorial(x) => JsonOperation {
operation: "factorial",
a: x as u32,
b: None,
},
}
}
}
json_api!(
struct TestClient<B, T: Transport<B>>;
trait TestApiCall;
json {
"/op": Operation as JsonOperation => Output as Output;
}
);
当然,我们可以使用自定义序列化实现达到相同的效果,但在许多情况下(特别是对于反序列化输出)定义两个类型并实现它们之间的 TryInto
或 Into
将显著更快。
最后,我们进行另一项调整。我们的 Output
类型只是一个单一的数字,所以为什么不让结果返回一个数字呢。
#
#
#
impl From<Output> for u64 {
fn from(output: Output) -> u64 {
output.c
}
}
json_api!(
struct TestClient<B, T: Transport<B>>;
trait TestApiCall;
json {
"/op": Operation as JsonOperation => Output as u64;
}
);
现在我们可以非常整洁地调用API了
#
#
#
#
#
#
#
let client = TestClient::new(std::borrow::Cow::Borrowed("https://127.0.0.1:8000"));
let output = client.call(Operation::Add((1, 2))).await.unwrap();
assert_eq!(output, 3);
真实世界示例
关于真实世界的示例,请参见 cloudconvert-rs
。
使用宏
在上面的简单示例中,我们使用了 json_api!
宏来生成客户端和API调用。
json_api!
宏只是使用 client!
宏和 json_api_call!
宏的简写方式,这两个宏都有详细的文档。
直接实现特质
您还可以(在某些情况下您可能想要)直接实现特质。以下是一个从最抽象到最具体的总结:
SimpleApiCall
:一个不进行输入和输出类型转换的JSON API调用(自动实现JsonApiCall
)。JsonApiCall
:一个允许进行请求和响应转换的JSON API调用(自动实现ApiCall
)。ApiCall
:一个通用的API调用(自动实现RawApiCall
)。RawApiCall
:API调用的最低级别特质,您可能想要实现其中之一。
依赖
~6–18MB
~247K SLoC