1#[cfg(feature = "json")]
2use serde::Serialize;
3
4#[cfg(feature = "multipart")]
5use crate::multipart::MultipartBuilder;
6
7#[derive(Clone, Debug, Default)]
11pub struct Headers {
12 pub headers: Vec<(String, String)>,
14}
15
16impl Headers {
17 pub fn new(headers: &[(&str, &str)]) -> Self {
28 Self {
29 headers: headers
30 .iter()
31 .map(|e| (e.0.to_owned(), e.1.to_owned()))
32 .collect(),
33 }
34 }
35
36 pub fn insert(&mut self, key: impl ToString, value: impl ToString) {
41 self.headers.push((key.to_string(), value.to_string()));
42 }
43
44 pub fn get(&self, key: &str) -> Option<&str> {
48 let key = key.to_string().to_lowercase();
49 self.headers
50 .iter()
51 .find(|(k, _)| k.to_lowercase() == key)
52 .map(|(_, v)| v.as_str())
53 }
54
55 pub fn get_all(&self, key: &str) -> impl Iterator<Item = &str> {
59 let key = key.to_string().to_lowercase();
60 self.headers
61 .iter()
62 .filter(move |(k, _)| k.to_lowercase() == key)
63 .map(|(_, v)| v.as_str())
64 }
65
66 pub fn sort(&mut self) {
72 self.headers.sort_by(|a, b| a.0.cmp(&b.0));
73 }
74}
75
76impl IntoIterator for Headers {
77 type Item = (String, String);
78 type IntoIter = std::vec::IntoIter<Self::Item>;
79
80 fn into_iter(self) -> Self::IntoIter {
81 self.headers.into_iter()
82 }
83}
84
85impl<'h> IntoIterator for &'h Headers {
86 type Item = &'h (String, String);
87 type IntoIter = std::slice::Iter<'h, (String, String)>;
88
89 fn into_iter(self) -> Self::IntoIter {
90 self.headers.iter()
91 }
92}
93
94#[cfg(target_arch = "wasm32")]
99#[derive(Default, Clone, Copy, Debug)]
100pub enum Mode {
101 SameOrigin = 0,
103
104 NoCors = 1,
107
108 #[default]
111 Cors = 2,
112
113 Navigate = 3,
115}
116
117#[cfg(target_arch = "wasm32")]
118impl From<Mode> for web_sys::RequestMode {
119 fn from(mode: Mode) -> Self {
120 match mode {
121 Mode::SameOrigin => web_sys::RequestMode::SameOrigin,
122 Mode::NoCors => web_sys::RequestMode::NoCors,
123 Mode::Cors => web_sys::RequestMode::Cors,
124 Mode::Navigate => web_sys::RequestMode::Navigate,
125 }
126 }
127}
128
129#[derive(Clone, Debug)]
131pub struct Request {
132 pub method: String,
134
135 pub url: String,
137
138 pub body: Vec<u8>,
140
141 pub headers: Headers,
143
144 #[cfg(target_arch = "wasm32")]
146 pub mode: Mode,
147}
148
149impl Request {
150 #[allow(clippy::needless_pass_by_value)]
152 pub fn get(url: impl ToString) -> Self {
153 Self {
154 method: "GET".to_owned(),
155 url: url.to_string(),
156 body: vec![],
157 headers: Headers::new(&[("Accept", "*/*")]),
158 #[cfg(target_arch = "wasm32")]
159 mode: Mode::default(),
160 }
161 }
162
163 #[allow(clippy::needless_pass_by_value)]
165 pub fn head(url: impl ToString) -> Self {
166 Self {
167 method: "HEAD".to_owned(),
168 url: url.to_string(),
169 body: vec![],
170 headers: Headers::new(&[("Accept", "*/*")]),
171 #[cfg(target_arch = "wasm32")]
172 mode: Mode::default(),
173 }
174 }
175
176 #[allow(clippy::needless_pass_by_value)]
178 pub fn post(url: impl ToString, body: Vec<u8>) -> Self {
179 Self {
180 method: "POST".to_owned(),
181 url: url.to_string(),
182 body,
183 headers: Headers::new(&[
184 ("Accept", "*/*"),
185 ("Content-Type", "text/plain; charset=utf-8"),
186 ]),
187 #[cfg(target_arch = "wasm32")]
188 mode: Mode::default(),
189 }
190 }
191
192 #[cfg(feature = "multipart")]
215 pub fn multipart(url: impl ToString, builder: MultipartBuilder) -> Self {
216 let (content_type, data) = builder.finish();
217 Self {
218 method: "POST".to_string(),
219 url: url.to_string(),
220 body: data,
221 headers: Headers::new(&[("Accept", "*/*"), ("Content-Type", content_type.as_str())]),
222 #[cfg(target_arch = "wasm32")]
223 mode: Mode::default(),
224 }
225 }
226
227 #[cfg(feature = "json")]
228 #[allow(clippy::needless_pass_by_value)]
230 pub fn json<T>(url: impl ToString, body: &T) -> serde_json::error::Result<Self>
231 where
232 T: ?Sized + Serialize,
233 {
234 Ok(Self {
235 method: "POST".to_owned(),
236 url: url.to_string(),
237 body: serde_json::to_string(body)?.into_bytes(),
238 headers: Headers::new(&[("Accept", "*/*"), ("Content-Type", "application/json")]),
239 #[cfg(target_arch = "wasm32")]
240 mode: Mode::default(),
241 })
242 }
243}
244
245#[derive(Clone)]
247pub struct Response {
248 pub url: String,
250
251 pub ok: bool,
253
254 pub status: u16,
256
257 pub status_text: String,
259
260 pub headers: Headers,
262
263 pub bytes: Vec<u8>,
265}
266
267impl Response {
268 pub fn text(&self) -> Option<&str> {
269 std::str::from_utf8(&self.bytes).ok()
270 }
271
272 #[cfg(feature = "json")]
273 pub fn json<T: serde::de::DeserializeOwned>(&self) -> serde_json::Result<T> {
275 serde_json::from_slice(self.bytes.as_slice())
276 }
277
278 pub fn content_type(&self) -> Option<&str> {
280 self.headers.get("content-type")
281 }
282}
283
284impl std::fmt::Debug for Response {
285 fn fmt(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286 let Self {
287 url,
288 ok,
289 status,
290 status_text,
291 headers,
292 bytes,
293 } = self;
294
295 fmt.debug_struct("Response")
296 .field("url", url)
297 .field("ok", ok)
298 .field("status", status)
299 .field("status_text", status_text)
300 .field("headers", headers)
301 .field("bytes", &format!("{} bytes", bytes.len()))
302 .finish_non_exhaustive()
303 }
304}
305
306#[derive(Clone, Debug)]
308pub struct PartialResponse {
309 pub url: String,
311
312 pub ok: bool,
314
315 pub status: u16,
317
318 pub status_text: String,
320
321 pub headers: Headers,
323}
324
325impl PartialResponse {
326 pub fn complete(self, bytes: Vec<u8>) -> Response {
327 let Self {
328 url,
329 ok,
330 status,
331 status_text,
332 headers,
333 } = self;
334 Response {
335 url,
336 ok,
337 status,
338 status_text,
339 headers,
340 bytes,
341 }
342 }
343}
344
345pub type Error = String;
350
351pub type Result<T> = std::result::Result<T, Error>;