1#![allow(unknown_lints)]
6#![warn(rust_2018_idioms)]
7
8use url::Url;
9#[macro_use]
10mod headers;
11
12mod backend;
13mod client;
14pub mod error;
15mod new_backend;
16pub mod settings;
17pub use error::*;
18pub use error_support::{debug, error, info, trace, warn};
20
21pub use backend::{note_backend, set_backend, Backend as OldBackend};
22pub use client::{Client, ClientSettings};
23pub use headers::{consts as header_names, Header, HeaderName, Headers, InvalidHeaderName};
24pub use new_backend::{init_backend, Backend};
25pub use settings::{allow_android_emulator_loopback, GLOBAL_SETTINGS};
26
27#[allow(clippy::derive_partial_eq_without_eq)]
28pub(crate) mod msg_types {
29 include!("mozilla.appservices.httpconfig.protobuf.rs");
30}
31
32#[derive(Clone, Debug, Copy, PartialEq, PartialOrd, Eq, Ord, Hash, uniffi::Enum)]
36#[repr(u8)]
37pub enum Method {
38 Get,
39 Head,
40 Post,
41 Put,
42 Delete,
43 Connect,
44 Options,
45 Trace,
46 Patch,
47}
48
49impl Method {
50 pub fn as_str(self) -> &'static str {
51 match self {
52 Method::Get => "GET",
53 Method::Head => "HEAD",
54 Method::Post => "POST",
55 Method::Put => "PUT",
56 Method::Delete => "DELETE",
57 Method::Connect => "CONNECT",
58 Method::Options => "OPTIONS",
59 Method::Trace => "TRACE",
60 Method::Patch => "PATCH",
61 }
62 }
63}
64
65impl std::fmt::Display for Method {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 f.write_str(self.as_str())
68 }
69}
70
71#[must_use = "`Request`'s \"builder\" functions take by move, not by `&mut self`"]
72#[derive(Clone, Debug, uniffi::Record)]
73pub struct Request {
74 pub method: Method,
75 pub url: Url,
76 pub headers: Headers,
77 pub body: Option<Vec<u8>>,
78}
79
80impl Request {
81 pub fn new(method: Method, url: Url) -> Self {
84 Self {
85 method,
86 url,
87 headers: Headers::new(),
88 body: None,
89 }
90 }
91
92 pub fn send(self) -> Result<Response, ViaductError> {
93 crate::backend::send(self)
94 }
95
96 pub fn get(url: Url) -> Self {
98 Self::new(Method::Get, url)
99 }
100
101 pub fn patch(url: Url) -> Self {
103 Self::new(Method::Patch, url)
104 }
105
106 pub fn post(url: Url) -> Self {
108 Self::new(Method::Post, url)
109 }
110
111 pub fn put(url: Url) -> Self {
113 Self::new(Method::Put, url)
114 }
115
116 pub fn delete(url: Url) -> Self {
118 Self::new(Method::Delete, url)
119 }
120
121 pub fn query(mut self, pairs: &[(&str, &str)]) -> Self {
137 let mut append_to = self.url.query_pairs_mut();
138 for (k, v) in pairs {
139 append_to.append_pair(k, v);
140 }
141 drop(append_to);
142 self
143 }
144
145 pub fn set_query<'a, Q: Into<Option<&'a str>>>(mut self, query: Q) -> Self {
164 self.url.set_query(query.into());
165 self
166 }
167
168 pub fn headers<I>(mut self, to_add: I) -> Self
171 where
172 I: IntoIterator<Item = Header>,
173 {
174 self.headers.extend(to_add);
175 self
176 }
177
178 pub fn header<Name, Val>(mut self, name: Name, val: Val) -> Result<Self, crate::ViaductError>
197 where
198 Name: Into<HeaderName> + PartialEq<HeaderName>,
199 Val: Into<String> + AsRef<str>,
200 {
201 self.headers.insert(name, val)?;
202 Ok(self)
203 }
204
205 pub fn body(mut self, body: impl Into<Vec<u8>>) -> Self {
207 self.body = Some(body.into());
208 self
209 }
210
211 pub fn json<T: ?Sized + serde::Serialize>(mut self, val: &T) -> Self {
225 self.body =
226 Some(serde_json::to_vec(val).expect("Rust component bug: serde_json::to_vec failure"));
227 self.headers
228 .insert_if_missing(header_names::CONTENT_TYPE, "application/json")
229 .unwrap(); self
231 }
232}
233
234#[derive(Clone, Debug, uniffi::Record)]
236pub struct Response {
237 pub request_method: Method,
239 pub url: Url,
241 pub status: u16,
243 pub headers: Headers,
245 pub body: Vec<u8>,
247}
248
249impl Response {
250 pub fn json<'a, T>(&'a self) -> Result<T, serde_json::Error>
252 where
253 T: serde::Deserialize<'a>,
254 {
255 serde_json::from_slice(&self.body)
256 }
257
258 pub fn text(&self) -> std::borrow::Cow<'_, str> {
261 String::from_utf8_lossy(&self.body)
262 }
263
264 #[inline]
266 pub fn is_success(&self) -> bool {
267 status_codes::is_success_code(self.status)
268 }
269
270 #[inline]
272 pub fn is_server_error(&self) -> bool {
273 status_codes::is_server_error_code(self.status)
274 }
275
276 #[inline]
278 pub fn is_client_error(&self) -> bool {
279 status_codes::is_client_error_code(self.status)
280 }
281
282 #[inline]
285 pub fn require_success(self) -> Result<Self, UnexpectedStatus> {
286 if self.is_success() {
287 Ok(self)
288 } else {
289 Err(UnexpectedStatus {
290 method: self.request_method,
291 url: self.url,
294 status: self.status,
295 })
296 }
297 }
298}
299
300pub mod status_codes {
302
303 #[inline]
305 pub fn is_success_code(c: u16) -> bool {
306 (200..300).contains(&c)
307 }
308
309 #[inline]
311 pub fn is_client_error_code(c: u16) -> bool {
312 (400..500).contains(&c)
313 }
314
315 #[inline]
317 pub fn is_server_error_code(c: u16) -> bool {
318 (500..600).contains(&c)
319 }
320
321 macro_rules! define_status_codes {
322 ($(($val:expr, $NAME:ident)),* $(,)?) => {
323 $(pub const $NAME: u16 = $val;)*
324 };
325 }
326 define_status_codes![
328 (100, CONTINUE),
329 (101, SWITCHING_PROTOCOLS),
330 (200, OK),
332 (201, CREATED),
333 (202, ACCEPTED),
334 (203, NONAUTHORITATIVE_INFORMATION),
335 (204, NO_CONTENT),
336 (205, RESET_CONTENT),
337 (206, PARTIAL_CONTENT),
338 (300, MULTIPLE_CHOICES),
340 (301, MOVED_PERMANENTLY),
341 (302, FOUND),
342 (303, SEE_OTHER),
343 (304, NOT_MODIFIED),
344 (305, USE_PROXY),
345 (307, TEMPORARY_REDIRECT),
347 (400, BAD_REQUEST),
349 (401, UNAUTHORIZED),
350 (402, PAYMENT_REQUIRED),
351 (403, FORBIDDEN),
352 (404, NOT_FOUND),
353 (405, METHOD_NOT_ALLOWED),
354 (406, NOT_ACCEPTABLE),
355 (407, PROXY_AUTHENTICATION_REQUIRED),
356 (408, REQUEST_TIMEOUT),
357 (409, CONFLICT),
358 (410, GONE),
359 (411, LENGTH_REQUIRED),
360 (412, PRECONDITION_FAILED),
361 (413, REQUEST_ENTITY_TOO_LARGE),
362 (414, REQUEST_URI_TOO_LONG),
363 (415, UNSUPPORTED_MEDIA_TYPE),
364 (416, REQUESTED_RANGE_NOT_SATISFIABLE),
365 (417, EXPECTATION_FAILED),
366 (429, TOO_MANY_REQUESTS),
367 (500, INTERNAL_SERVER_ERROR),
369 (501, NOT_IMPLEMENTED),
370 (502, BAD_GATEWAY),
371 (503, SERVICE_UNAVAILABLE),
372 (504, GATEWAY_TIMEOUT),
373 (505, HTTP_VERSION_NOT_SUPPORTED),
374 ];
375}
376
377pub fn parse_url(url: &str) -> Result<Url, ViaductError> {
378 Ok(Url::parse(url)?)
379}
380
381pub type ViaductUrl = Url;
383
384uniffi::custom_type!(ViaductUrl, String, {
385 remote,
386 try_lift: |val| Ok(ViaductUrl::parse(&val)?),
387 lower: |obj| obj.into(),
388});
389
390uniffi::custom_type!(Headers, std::collections::HashMap<String, String>, {
391 remote,
392 try_lift: |map| {
393 Ok(map.into_iter()
394 .map(|(name, value)| Header::new(name, value))
395 .collect::<Result<Vec<Header>>>()?
396 .into()
397 )
398 },
399 lower: |headers| headers.into(),
400});
401
402uniffi::setup_scaffolding!("viaduct");