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