1use reqwest::{
2 self,
3 header::{HeaderMap, HeaderValue, SET_COOKIE},
4 Response, StatusCode,
5};
6use serde::de::DeserializeOwned;
7use std::{collections::HashMap, error::Error};
8
9#[derive(Debug)]
10pub enum WappuError {
11 Network(reqwest::Error),
12 UnexpectedStatusCode(reqwest::StatusCode, String),
13 CapmonsterError(String),
14}
15
16impl std::fmt::Display for WappuError {
17 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
18 match *self {
19 WappuError::Network(ref err) => write!(f, "Network error: {}", err),
20 WappuError::UnexpectedStatusCode(ref code, ref text) => {
21 write!(f, "Unexpected status code: {}. Response text: {}", code, text)
22 }
23 WappuError::CapmonsterError(ref err) => write!(f, "Capmonster error: {}", err),
24 }
25 }
26}
27
28impl From<serde_json::Error> for WappuError {
29 fn from(err: serde_json::Error) -> WappuError {
30 WappuError::CapmonsterError(format!("JSON deserialization error: {}", err))
31 }
32}
33
34#[macro_export]
35macro_rules! headers {
36 ($($key:expr => $value:expr),* $(,)?) => {{
37 let mut map = reqwest::header::HeaderMap::new();
38 $(
39 map.insert($key, $value.parse().unwrap());
40 )*
41 map
42 }};
43}
44
45#[macro_export]
46macro_rules! query_params {
47 ($($key:expr => $value:expr),* $(,)?) => {{
48 let mut params = Vec::new();
49 $(
50 params.push((String::from($key), String::from($value)));
51 )*
52 params
53 }};
54}
55
56impl Error for WappuError {}
57
58impl From<reqwest::Error> for WappuError {
59 fn from(err: reqwest::Error) -> WappuError {
60 WappuError::Network(err)
61 }
62}
63
64pub struct WappuClient {
65 client: reqwest::Client,
66 query_params: Vec<(String, String)>,
67}
68
69impl WappuClient {
70 pub fn new() -> Self {
71 WappuClient {
72 client: reqwest::Client::new(),
73 query_params: Vec::new(),
74 }
75 }
76
77 pub fn query_params(mut self, params: Vec<(String, String)>) -> Self {
78 self.query_params = params;
79 self
80 }
81
82 pub async fn get(
83 &self,
84 url: &str,
85 headers: Option<HeaderMap>,
86 ) -> Result<WappuResponse, WappuError> {
87 let request = self.client.get(url);
88
89 let request = if !self.query_params.is_empty() {
90 request.query(&self.query_params)
91 } else {
92 request
93 };
94
95 let response = self.send_request(request, headers).await?;
96 WappuResponse::from_response(response).await
97 }
98
99 pub async fn post(
100 &self,
101 url: &str,
102 body: &str,
103 headers: Option<HeaderMap>,
104 ) -> Result<WappuResponse, WappuError> {
105 let request = self.client.post(url).body(body.to_string());
106
107 let request = if !self.query_params.is_empty() {
108 request.query(&self.query_params)
109 } else {
110 request
111 };
112
113 let response = self.send_request(request, headers).await?;
114 WappuResponse::from_response(response).await
115 }
116
117 pub async fn put(
118 &self,
119 url: &str,
120 body: &str,
121 headers: Option<HeaderMap>,
122 ) -> Result<WappuResponse, WappuError> {
123 let request = self.client.put(url).body(body.to_string());
124
125 let request = if !self.query_params.is_empty() {
126 request.query(&self.query_params)
127 } else {
128 request
129 };
130
131 let response = self.send_request(request, headers).await?;
132 WappuResponse::from_response(response).await
133 }
134
135 pub async fn delete(
136 &self,
137 url: &str,
138 headers: Option<HeaderMap>,
139 ) -> Result<WappuResponse, WappuError> {
140 let request = self.client.delete(url);
141
142 let request = if !self.query_params.is_empty() {
143 request.query(&self.query_params)
144 } else {
145 request
146 };
147
148 let response = self.send_request(request, headers).await?;
149 WappuResponse::from_response(response).await
150 }
151
152 pub async fn head(
153 &self,
154 url: &str,
155 headers: Option<HeaderMap>,
156 ) -> Result<WappuResponse, WappuError> {
157 let request = self.client.head(url);
158
159 let request = if !self.query_params.is_empty() {
160 request.query(&self.query_params)
161 } else {
162 request
163 };
164
165 let response = self.send_request(request, headers).await?;
166 WappuResponse::from_response(response).await
167 }
168
169 pub async fn patch(
170 &self,
171 url: &str,
172 body: &str,
173 headers: Option<HeaderMap>,
174 ) -> Result<WappuResponse, WappuError> {
175 let request = self.client.patch(url).body(body.to_string());
176
177 let request = if !self.query_params.is_empty() {
178 request.query(&self.query_params)
179 } else {
180 request
181 };
182
183 let response = self.send_request(request, headers).await?;
184 WappuResponse::from_response(response).await
185 }
186
187 async fn send_request(
188 &self,
189 request: reqwest::RequestBuilder,
190 headers: Option<HeaderMap>,
191 ) -> Result<Response, WappuError> {
192 let mut request = request;
193 if let Some(h) = headers {
194 request = request.headers(h);
195 }
196 let response = request.send().await?;
197 if !response.status().is_success() {
198 let status_code = response.status();
199 let response_text = response.text().await.map_err(WappuError::from)?;
200 return Err(WappuError::UnexpectedStatusCode(status_code, response_text));
201 }
202 Ok(response)
203 }
204}
205
206pub struct WappuResponse {
207 text: String,
208 headers: HeaderMap,
209 status_code: StatusCode,
210 cookies: HashMap<String, String>, }
212
213impl WappuResponse {
214 async fn from_response(response: Response) -> Result<Self, WappuError> {
216 let status_code = response.status();
217 let headers = response.headers().clone(); let cookies = headers
221 .get_all(SET_COOKIE)
222 .iter()
223 .filter_map(|header_value| parse_cookie(header_value))
224 .collect();
225
226 let body_text = response.text().await.map_err(WappuError::from)?;
227
228 Ok(WappuResponse {
229 text: body_text,
230 headers,
231 status_code,
232 cookies,
233 })
234 }
235
236 pub fn text(&self) -> &str {
238 &self.text
239 }
240
241 pub fn headers(&self) -> &HeaderMap {
243 &self.headers
244 }
245
246 pub fn status_code(&self) -> StatusCode {
248 self.status_code
249 }
250
251 pub fn cookies(&self) -> &HashMap<String, String> {
253 &self.cookies
254 }
255
256 pub async fn json<T: DeserializeOwned>(&self) -> Result<T, WappuError> {
257 serde_json::from_str(&self.text).map_err(WappuError::from)
258 }
259}
260
261fn parse_cookie(header_value: &HeaderValue) -> Option<(String, String)> {
263 header_value.to_str().ok().and_then(|cookie_str| {
264 let parts: Vec<&str> = cookie_str.splitn(2, '=').collect();
265 if parts.len() == 2 {
266 Some((
267 parts[0].trim().to_string(),
268 parts[1].split(';').next()?.trim().to_string(),
269 ))
270 } else {
271 None
272 }
273 })
274}