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}