arrpc_contract/
http.rs

1use anyhow::{bail, Context};
2use arrpc_core::{ClientContract, MakeClient, Request, Result, ServiceContract, UniversalClient};
3use async_trait::async_trait;
4use http::{Method, Response, StatusCode};
5use reqwest::Client;
6use serde::{de::DeserializeOwned, Serialize};
7
8const AUTH_KEY: &str = "auth-key";
9
10pub struct HttpContract {
11    pub auth_token: String,
12}
13
14pub struct HttpRequest(http::Request<Vec<u8>>);
15
16impl From<http::Request<Vec<u8>>> for HttpRequest {
17    fn from(value: http::Request<Vec<u8>>) -> Self {
18        HttpRequest(value)
19    }
20}
21
22impl Request for HttpRequest {
23    type Response = http::Response<Vec<u8>>;
24
25    fn proc<P: DeserializeOwned>(&self) -> Result<P> {
26        serde_json::from_slice(self.0.body()).context("deserializing request value")
27    }
28
29    fn respond<V: Serialize>(self, value: V) -> Result<Self::Response> {
30        let response = serde_json::to_vec(&value).context("serialize proc result")?;
31
32        Response::builder()
33            .status(StatusCode::OK)
34            .body(response)
35            .context("build response")
36    }
37}
38
39#[async_trait]
40impl ServiceContract for HttpContract {
41    type R = HttpRequest;
42    async fn eval(&self, req: &Self::R) -> Result<()> {
43        if req.0.method() != Method::POST {
44            bail!("incorrect method used");
45        }
46        let header_val = req
47            .0
48            .headers()
49            .get(AUTH_KEY)
50            .map(|header| header.as_bytes());
51
52        match header_val == Some(&self.auth_token.bytes().collect::<Vec<_>>()) {
53            true => Ok(()),
54            false => bail!("auth token is invalid"),
55        }
56    }
57}
58
59impl MakeClient for HttpContract {
60    type Args = ClientArgs;
61    type Client = HttpClientContract;
62
63    fn make_client<A>(args: A) -> UniversalClient<Self::Client>
64    where
65        Self::Args: From<A>,
66    {
67        let ClientArgs { url, auth_token } = args.into();
68        let client = reqwest::Client::new();
69        let client = HttpClientContract {
70            url,
71            client,
72            auth_token,
73        };
74        UniversalClient(client)
75    }
76}
77
78pub struct ClientArgs {
79    url: String,
80    auth_token: String,
81}
82
83impl<Url: ToString, Token: ToString> From<(Url, Token)> for ClientArgs {
84    fn from((url, auth_token): (Url, Token)) -> Self {
85        Self {
86            url: url.to_string(),
87            auth_token: auth_token.to_string(),
88        }
89    }
90}
91
92pub struct HttpClientContract {
93    url: String,
94    client: Client,
95    auth_token: String,
96}
97
98#[async_trait]
99impl ClientContract for HttpClientContract {
100    async fn send<R, V>(&self, req: R) -> Result<V>
101    where
102        R: Serialize + Send + Sync,
103        V: DeserializeOwned + Send + Sync,
104    {
105        self.client
106            .post(self.url.as_str())
107            .header(AUTH_KEY, &self.auth_token)
108            .json(&req)
109            .send()
110            .await
111            .context("request to service")?
112            .json()
113            .await
114            .context("deserializing service response")
115    }
116}