platz_sdk/client/
request.rs1use super::base::PlatzClient;
2use super::error::PlatzClientError;
3use async_trait::async_trait;
4use reqwest::{ClientBuilder, RequestBuilder};
5use serde::{de::DeserializeOwned, Deserialize, Serialize};
6use std::collections::HashMap;
7use tracing::instrument;
8
9#[derive(Clone)]
10pub struct PlatzRequest<'a> {
11 client: &'a PlatzClient,
12 method: reqwest::Method,
13 path: String,
14 query: HashMap<String, String>,
15}
16
17#[derive(Deserialize)]
18pub struct Paginated<T> {
19 pub page: i64,
20 pub per_page: i64,
21 pub items: Vec<T>,
22 pub num_total: i64,
23}
24
25lazy_static::lazy_static! {
26 static ref HTTP_USER_AGENT: String = format!(
27 "{}/{}/{}",
28 env!("CARGO_PKG_NAME"),
29 env!("CARGO_PKG_VERSION"),
30 option_env!("CARGO_BIN_NAME").unwrap_or("lib")
31 );
32}
33
34impl<'a> PlatzRequest<'a> {
35 pub fn new<S>(client: &'a PlatzClient, method: reqwest::Method, path: S) -> Self
36 where
37 S: AsRef<str>,
38 {
39 Self {
40 client,
41 method,
42 path: path.as_ref().to_owned(),
43 query: Default::default(),
44 }
45 }
46
47 pub fn query<K, V>(mut self, key: K, value: V) -> Self
48 where
49 K: AsRef<str>,
50 V: AsRef<str>,
51 {
52 self.query
53 .insert(key.as_ref().to_owned(), value.as_ref().to_owned());
54 self
55 }
56
57 pub fn add_to_query<I, K, V>(mut self, iter: I) -> Self
58 where
59 I: IntoIterator<Item = (K, V)>,
60 K: AsRef<str>,
61 V: AsRef<str>,
62 {
63 for (key, value) in iter.into_iter() {
64 self.query
65 .insert(key.as_ref().to_owned(), value.as_ref().to_owned());
66 }
67 self
68 }
69
70 pub async fn request_builder(&self) -> Result<RequestBuilder, PlatzClientError> {
71 let (header_key, header_value) = self.client.authorization().await?;
72 Ok(ClientBuilder::new()
73 .user_agent(HTTP_USER_AGENT.clone())
74 .gzip(true)
75 .brotli(true)
76 .deflate(true)
77 .build()?
78 .request(
79 self.method.clone(),
80 self.client.build_url(&self.path).await?,
81 )
82 .header(header_key, header_value)
83 .query(&self.query))
84 }
85
86 pub async fn send_with_no_response(self) -> Result<(), PlatzClientError> {
87 self.request_builder()
88 .await?
89 .send()
90 .await?
91 .error_for_status_with_body()
92 .await?;
93 Ok(())
94 }
95
96 pub async fn send<T>(self) -> Result<T, PlatzClientError>
97 where
98 T: DeserializeOwned + Send,
99 {
100 Ok(self
101 .request_builder()
102 .await?
103 .send()
104 .await?
105 .error_for_status_with_body()
106 .await?
107 .json()
108 .await?)
109 }
110
111 #[instrument(skip_all, fields(path=self.path))]
112 pub async fn send_with_body<T, R>(self, body: T) -> Result<R, PlatzClientError>
113 where
114 T: Serialize,
115 R: DeserializeOwned + Send,
116 {
117 Ok(self
118 .request_builder()
119 .await?
120 .json(&body)
121 .send()
122 .await?
123 .error_for_status_with_body()
124 .await?
125 .json()
126 .await?)
127 }
128
129 pub async fn single_page<T>(
130 &self,
131 page_index: i64,
132 page_size: Option<i64>,
133 ) -> Result<Paginated<T>, PlatzClientError>
134 where
135 T: DeserializeOwned + Send,
136 Paginated<T>: DeserializeOwned + Send,
137 {
138 let mut paging_info = vec![("page", page_index.to_string())];
139 if let Some(size) = page_size {
140 paging_info.push(("page_size", size.to_string()))
141 }
142 let page = self
143 .request_builder()
144 .await?
145 .query(&paging_info)
146 .send()
147 .await?
148 .error_for_status_with_body()
149 .await?
150 .json()
151 .await?;
152 Ok(page)
153 }
154
155 #[instrument(skip_all, fields(path=self.path))]
156 pub async fn paginated<T>(self) -> Result<Vec<T>, PlatzClientError>
157 where
158 T: DeserializeOwned + Send,
159 Paginated<T>: DeserializeOwned + Send,
160 {
161 let page_size: Option<i64> = None;
162 let mut cur_page = self.single_page(1, page_size).await?;
163 let mut items = cur_page.items;
164
165 while cur_page.page * cur_page.per_page < cur_page.num_total {
166 let next_page = cur_page.page + 1;
167 cur_page = self.single_page(next_page, page_size).await?;
168 items.extend(cur_page.items.into_iter());
169 }
170
171 Ok(items)
172 }
173
174 #[instrument(skip_all, fields(path=self.path))]
175 pub async fn paginated_expect_one<T>(self) -> Result<T, PlatzClientError>
176 where
177 T: DeserializeOwned + Send,
178 Paginated<T>: DeserializeOwned + Send,
179 {
180 let items = self.paginated().await?;
181 match items.len() {
182 0 => Err(PlatzClientError::ExpectedOneGotNone),
183 1 => Ok(items.into_iter().next().unwrap()),
184 n => Err(PlatzClientError::ExpectedOneGotMany(n)),
185 }
186 }
187}
188#[async_trait]
189trait ResponseExt {
190 async fn error_for_status_with_body(self) -> Result<reqwest::Response, PlatzClientError>;
191}
192
193#[async_trait]
194impl ResponseExt for reqwest::Response {
195 async fn error_for_status_with_body(self) -> Result<reqwest::Response, PlatzClientError> {
196 let status = self.status();
197 if status.is_success() {
198 Ok(self)
199 } else {
200 let body = self
201 .text()
202 .await
203 .ok()
204 .map(|s| format!(": {s:?}"))
205 .unwrap_or_default();
206 let err_msg = format!("{status}{body}");
207 Err(PlatzClientError::HttpError(err_msg))
208 }
209 }
210}