1pub mod crates;
2
3use bytes::Bytes;
4use http::StatusCode;
5use std::borrow::Cow;
6use std::collections::HashMap;
7use std::fmt;
8use url::Url;
9
10#[derive(fmt::Debug, Default)]
11pub struct QueryParams {
12 inner: HashMap<Cow<'static, str>, Cow<'static, str>>,
13}
14
15impl QueryParams {
16 pub fn append_to_url(&self, url: &mut Url) {
17 url.query_pairs_mut().extend_pairs(self.iter_tuples());
18 }
19
20 fn iter_tuples(&self) -> impl Iterator<Item = (&str, &str)> {
21 self.inner.iter().map(|(k, v)| (k.as_ref(), v.as_ref()))
22 }
23}
24
25#[derive(fmt::Debug, thiserror::Error)]
26#[error(transparent)]
27#[non_exhaustive]
28pub struct BodyError {
29 pub error: serde_json::Error,
30}
31
32#[derive(fmt::Debug, thiserror::Error)]
33#[non_exhaustive]
34pub enum JsonResult {
35 #[error("{0}")]
36 Json(serde_json::Value),
37 #[error(transparent)]
38 Error(serde_json::Error),
39}
40
41impl From<Result<serde_json::Value, serde_json::Error>> for JsonResult {
42 fn from(value: Result<serde_json::Value, serde_json::Error>) -> Self {
43 match value {
44 Ok(v) => JsonResult::Json(v),
45 Err(e) => JsonResult::Error(e),
46 }
47 }
48}
49
50#[derive(fmt::Debug, thiserror::Error)]
51#[non_exhaustive]
52pub enum ApiError<C: std::error::Error + Send + Sync + 'static> {
53 #[error("Body error: {}", error)]
54 Body {
55 #[from]
56 error: BodyError,
57 },
58 #[error("Client error: {}", error)]
59 Client { error: C },
60 #[error("Unable to build HTTP request: {}", error)]
61 HttpRequest { error: http::Error },
62 #[error("HTTP request failed with status code '{}': {}", status_code, body)]
63 HttpResponse {
64 status_code: StatusCode,
65 body: JsonResult,
66 },
67 #[error("Unable to parse JSON response into type '{}': {}", r#type, error)]
68 ParseType {
69 error: serde_json::Error,
70 r#type: &'static str,
71 },
72 #[error("Unable to parse url '{}' (path '{}'): {}", url, path, error)]
73 Url {
74 error: url::ParseError,
75 url: Cow<'static, str>,
76 path: Cow<'static, str>,
77 },
78}
79
80impl<C: std::error::Error + Send + Sync + 'static> ApiError<C> {
81 pub fn parse_type_error<T>(error: serde_json::Error) -> Self {
82 ApiError::ParseType {
83 error,
84 r#type: std::any::type_name::<T>(),
85 }
86 }
87}
88
89pub trait Endpoint {
96 fn method(&self) -> http::Method;
97
98 fn endpoint(&self) -> Cow<'static, str>;
99
100 fn parameters(&self) -> QueryParams {
101 QueryParams::default()
102 }
103
104 fn body(&self) -> Result<Vec<u8>, BodyError> {
105 Ok(Vec::with_capacity(0))
106 }
107}
108
109pub trait Client {
116 type Error: std::error::Error + Send + Sync + 'static;
117
118 fn base_endpoint(&self, path: &str) -> Result<Url, ApiError<Self::Error>>;
119
120 fn send(
123 &self,
124 request_builder: http::request::Builder,
125 body: Vec<u8>,
126 ) -> Result<http::Response<Bytes>, ApiError<Self::Error>>;
127}
128
129pub trait Query<T, C: Client> {
136 fn query(&self, client: &C) -> Result<T, ApiError<C::Error>>;
137}
138
139impl<E> Endpoint for &E
140where
141 E: Endpoint,
142{
143 fn method(&self) -> http::Method {
144 (*self).method()
145 }
146
147 fn endpoint(&self) -> Cow<'static, str> {
148 (*self).endpoint()
149 }
150
151 fn parameters(&self) -> QueryParams {
152 (*self).parameters()
153 }
154
155 fn body(&self) -> Result<Vec<u8>, BodyError> {
156 (*self).body()
157 }
158}
159
160impl<E, T, C> Query<T, C> for E
161where
162 E: Endpoint,
163 T: serde::de::DeserializeOwned,
164 C: Client,
165{
166 fn query(&self, client: &C) -> Result<T, ApiError<C::Error>> {
167 let mut url = client.base_endpoint(self.endpoint().as_ref())?;
170 self.parameters().append_to_url(&mut url);
172
173 let body = self.body()?;
175 let request = http::Request::builder()
176 .method(self.method())
177 .uri(url.as_ref());
178
179 let response = client.send(request, body)?;
181
182 if !response.status().is_success() {
184 return Err(ApiError::HttpResponse {
186 status_code: response.status(),
187 body: serde_json::from_slice(response.body()).into(),
188 });
189 }
190
191 serde_json::from_slice::<T>(response.body()).map_err(ApiError::parse_type_error::<T>)
193 }
194}
195
196pub struct Json<E> {
197 endpoint: E,
198}
199
200impl<E> Json<E> {
201 pub fn new(endpoint: E) -> Self {
202 Self { endpoint }
203 }
204}
205
206impl<E, C> Query<serde_json::Value, C> for Json<E>
207where
208 E: Endpoint,
209 C: Client,
210{
211 fn query(&self, client: &C) -> Result<serde_json::Value, ApiError<C::Error>> {
212 self.endpoint.query(client)
213 }
214}