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