#json-api #http-client #http-api #json #http #api

hapic

HTTP API 客户端 (hapic):一个用于快速创建易于使用的 HTTP API 客户端库的 Rust 包,特别是在 HTTP JSON API 方面有很多工具。

2 个不稳定版本

0.3.0 2023 年 11 月 17 日
0.1.0 2023 年 10 月 9 日

#25#json-api


cloudconvert 中使用

MIT 许可证

53KB
845

HTTP API 客户端 (hapic)

Crates.io docs.rs MIT License

一个用于快速创建易于使用的 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;
    }
);

现在,调用类型(AddSubFactorial)都实现了 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 服务器只接受 addsubfactorial。此外,如果 b 在阶乘操作中为 Some,或者在 addsub 中为 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;
    }
);

当然,我们可以使用自定义序列化实现达到相同的效果,但在许多情况下(特别是对于反序列化输出)定义两个类型并实现它们之间的 TryIntoInto 将显著更快。

最后,我们进行另一项调整。我们的 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