zenlayercloud_sdk/
client.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
use log::debug;
use serde::{de::DeserializeOwned, Deserialize, Serialize};

use crate::{
    credentials::AccessKeyCredential,
    signer::{Signer, Zc2HS256Signer},
    Error, API_DOMAIN, SDK_VERSION,
};

#[derive(Debug)]
pub struct Request {
    rb: reqwest::RequestBuilder,
}

impl Request {
    pub fn build(self) -> reqwest::Result<reqwest::Request> {
        self.rb.build()
    }
}

/// [Responses](https://docs.console.zenlayer.com/api-reference/api-introduction/instruction/response)
#[derive(Debug, Deserialize)]
pub struct Response<T> {
    #[serde(rename = "requestId")]
    pub request_id: String,
    /// Success response. Content are API specific.
    pub response: Option<T>,
    /// Failure response.
    pub code: Option<String>,
    pub message: Option<String>,
}

pub struct Client {
    hc: reqwest::Client,
    signer: Zc2HS256Signer,
}

impl Client {
    pub fn new(credential: AccessKeyCredential) -> Self {
        let hc = reqwest::Client::new();
        let signer = Zc2HS256Signer::new(credential);
        Client { hc, signer }
    }

    pub fn create_request<T>(
        &self,
        service: &str,
        api_version: &str,
        action: &str,
        payload: &T,
    ) -> Request
    where
        T: Serialize + ?Sized,
    {
        let host = API_DOMAIN;
        let url = format!("https://{}/api/v2/{}", host, service);

        let timestamp = format!("{}", chrono::Local::now().timestamp());
        let rb = self
            .hc
            .post(&url)
            .header("Host", host)
            .header("x-zc-version", api_version)
            .header("x-zc-action", action)
            .header("x-zc-timestamp", timestamp)
            .header("x-zc-sdk-version", SDK_VERSION)
            .header("x-zc-sdk-lang", "rust")
            .json(payload); // set Content-Type: application/json
        Request { rb }
    }

    pub async fn call_api<R>(&self, req: Request) -> Result<Response<R>, Error>
    where
        R: DeserializeOwned,
    {
        let mut http_req = req.build()?;
        self.signer.sign_request(&mut http_req)?;
        let http_res = self.hc.execute(http_req).await?;
        debug!("http_res={:?}", http_res);
        let status = http_res.status();
        let res: Response<R> = http_res.json().await?;
        if status.is_success() {
            Ok(res)
        } else {
            Err(Error::APIError {
                request_id: res.request_id,
                http_status: status.as_u16(),
                code: res.code.unwrap_or_default(),
                message: res.message.unwrap_or_default(),
            })
        }
    }
}