1use reqwest::IntoUrl;
16use reqwest::StatusCode;
17use reqwest::Url;
18
19#[derive(Debug, thiserror::Error)]
20#[error("{0}")]
21pub enum Error {
22 #[from(std::io::Error)]
23 IO(std::io::Error),
24 #[from(reqwest::Error)]
25 Http(reqwest::Error),
26
27 Other(String),
28}
29
30pub struct ClientBuilder {
31 endpoint: String,
32}
33
34impl ClientBuilder {
35 pub fn new(endpoint: String) -> Self {
36 Self { endpoint }
37 }
38
39 pub fn build(self) -> Result<Client, Error> {
40 let builder = reqwest::ClientBuilder::new().no_proxy();
41 Client::new(self.endpoint, builder)
42 }
43}
44
45pub struct Client {
46 client: reqwest::Client,
47 base_url: Url,
48}
49
50impl Client {
51 pub async fn get(&self, key: &str) -> Result<Option<Vec<u8>>, Error> {
52 do_get(self, key).await
53 }
54
55 pub async fn put(&self, key: &str, value: &[u8]) -> Result<(), Error> {
56 do_put(self, key, value).await
57 }
58
59 pub async fn delete(&self, key: &str) -> Result<(), Error> {
60 do_delete(self, key).await
61 }
62
63 fn new(base_url: impl IntoUrl, builder: reqwest::ClientBuilder) -> Result<Self, Error> {
64 let client = builder.build().map_err(Error::Http)?;
65 let base_url = base_url.into_url().map_err(Error::Http)?;
66 Ok(Client { client, base_url })
67 }
68}
69
70async fn do_get(client: &Client, key: &str) -> Result<Option<Vec<u8>>, Error> {
71 let url = client
72 .base_url
73 .join(key)
74 .map_err(|e| Error::Other(e.to_string()))?;
75 let resp = client.client.get(url).send().await.map_err(Error::Http)?;
76
77 match resp.status() {
78 StatusCode::NOT_FOUND | StatusCode::TOO_MANY_REQUESTS => Ok(None),
79 StatusCode::OK => {
80 let body = resp.bytes().await.map_err(Error::Http)?;
81 Ok(Some(body.to_vec()))
82 }
83 _ => Err(Error::Other(resp.status().to_string())),
84 }
85}
86
87async fn do_put(client: &Client, key: &str, value: &[u8]) -> Result<(), Error> {
88 let url = client
89 .base_url
90 .join(key)
91 .map_err(|e| Error::Other(e.to_string()))?;
92 let resp = client
93 .client
94 .put(url)
95 .body(value.to_vec())
96 .send()
97 .await
98 .map_err(Error::Http)?;
99
100 match resp.status() {
101 StatusCode::OK
102 | StatusCode::CREATED
103 | StatusCode::NO_CONTENT
104 | StatusCode::TOO_MANY_REQUESTS => Ok(()),
105 _ => Err(Error::Other(resp.status().to_string())),
106 }
107}
108
109async fn do_delete(client: &Client, key: &str) -> Result<(), Error> {
110 let url = client
111 .base_url
112 .join(key)
113 .map_err(|e| Error::Other(e.to_string()))?;
114 let resp = client
115 .client
116 .delete(url)
117 .send()
118 .await
119 .map_err(Error::Http)?;
120
121 match resp.status() {
122 StatusCode::OK | StatusCode::NO_CONTENT | StatusCode::TOO_MANY_REQUESTS => Ok(()),
123 _ => Err(Error::Other(resp.status().to_string())),
124 }
125}