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, 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
240impl std::fmt::Debug for Request {
242 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
243 f.debug_struct("Request")
244 .field("method", &self.method)
245 .field("url", &self.url.to_string())
246 .field("headers", &self.headers)
247 .field(
248 "body",
249 &self.body.as_ref().map(|body| String::from_utf8_lossy(body)),
250 )
251 .finish()
252 }
253}
254
255#[derive(Clone, uniffi::Record)]
257pub struct Response {
258 pub request_method: Method,
260 pub url: Url,
262 pub status: u16,
264 pub headers: Headers,
266 pub body: Vec<u8>,
268}
269
270impl Response {
271 pub fn json<'a, T>(&'a self) -> Result<T, serde_json::Error>
273 where
274 T: serde::Deserialize<'a>,
275 {
276 serde_json::from_slice(&self.body)
277 }
278
279 pub fn text(&self) -> std::borrow::Cow<'_, str> {
282 String::from_utf8_lossy(&self.body)
283 }
284
285 #[inline]
287 pub fn is_success(&self) -> bool {
288 status_codes::is_success_code(self.status)
289 }
290
291 #[inline]
293 pub fn is_server_error(&self) -> bool {
294 status_codes::is_server_error_code(self.status)
295 }
296
297 #[inline]
299 pub fn is_client_error(&self) -> bool {
300 status_codes::is_client_error_code(self.status)
301 }
302
303 #[inline]
306 pub fn require_success(self) -> Result<Self, UnexpectedStatus> {
307 if self.is_success() {
308 Ok(self)
309 } else {
310 Err(UnexpectedStatus {
311 method: self.request_method,
312 url: self.url,
315 status: self.status,
316 })
317 }
318 }
319}
320
321impl std::fmt::Debug for Response {
323 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
324 f.debug_struct("Response")
325 .field("request_method", &self.request_method)
326 .field("url", &self.url.to_string())
327 .field("status", &self.status)
328 .field("headers", &self.headers)
329 .field("body", &String::from_utf8_lossy(&self.body))
330 .finish()
331 }
332}
333
334pub mod status_codes {
336
337 #[inline]
339 pub fn is_success_code(c: u16) -> bool {
340 (200..300).contains(&c)
341 }
342
343 #[inline]
345 pub fn is_client_error_code(c: u16) -> bool {
346 (400..500).contains(&c)
347 }
348
349 #[inline]
351 pub fn is_server_error_code(c: u16) -> bool {
352 (500..600).contains(&c)
353 }
354
355 macro_rules! define_status_codes {
356 ($(($val:expr, $NAME:ident)),* $(,)?) => {
357 $(pub const $NAME: u16 = $val;)*
358 };
359 }
360 define_status_codes![
362 (100, CONTINUE),
363 (101, SWITCHING_PROTOCOLS),
364 (200, OK),
366 (201, CREATED),
367 (202, ACCEPTED),
368 (203, NONAUTHORITATIVE_INFORMATION),
369 (204, NO_CONTENT),
370 (205, RESET_CONTENT),
371 (206, PARTIAL_CONTENT),
372 (300, MULTIPLE_CHOICES),
374 (301, MOVED_PERMANENTLY),
375 (302, FOUND),
376 (303, SEE_OTHER),
377 (304, NOT_MODIFIED),
378 (305, USE_PROXY),
379 (307, TEMPORARY_REDIRECT),
381 (400, BAD_REQUEST),
383 (401, UNAUTHORIZED),
384 (402, PAYMENT_REQUIRED),
385 (403, FORBIDDEN),
386 (404, NOT_FOUND),
387 (405, METHOD_NOT_ALLOWED),
388 (406, NOT_ACCEPTABLE),
389 (407, PROXY_AUTHENTICATION_REQUIRED),
390 (408, REQUEST_TIMEOUT),
391 (409, CONFLICT),
392 (410, GONE),
393 (411, LENGTH_REQUIRED),
394 (412, PRECONDITION_FAILED),
395 (413, REQUEST_ENTITY_TOO_LARGE),
396 (414, REQUEST_URI_TOO_LONG),
397 (415, UNSUPPORTED_MEDIA_TYPE),
398 (416, REQUESTED_RANGE_NOT_SATISFIABLE),
399 (417, EXPECTATION_FAILED),
400 (429, TOO_MANY_REQUESTS),
401 (500, INTERNAL_SERVER_ERROR),
403 (501, NOT_IMPLEMENTED),
404 (502, BAD_GATEWAY),
405 (503, SERVICE_UNAVAILABLE),
406 (504, GATEWAY_TIMEOUT),
407 (505, HTTP_VERSION_NOT_SUPPORTED),
408 ];
409}
410
411pub fn parse_url(url: &str) -> Result<Url, ViaductError> {
412 Ok(Url::parse(url)?)
413}
414
415pub type ViaductUrl = Url;
417
418uniffi::custom_type!(ViaductUrl, String, {
419 remote,
420 try_lift: |val| Ok(ViaductUrl::parse(&val)?),
421 lower: |obj| obj.into(),
422});
423
424uniffi::custom_type!(Headers, std::collections::HashMap<String, String>, {
425 remote,
426 try_lift: |map| {
427 Ok(map.into_iter()
428 .map(|(name, value)| Header::new(name, value))
429 .collect::<Result<Vec<Header>>>()?
430 .into()
431 )
432 },
433 lower: |headers| headers.into(),
434});
435
436uniffi::setup_scaffolding!("viaduct");
437
438#[cfg(feature = "ohttp")]
460#[uniffi::export]
461pub async fn send_ohttp_request(request: Request, channel: String) -> Result<Response> {
462 let settings = crate::ClientSettings::default();
463 crate::ohttp::process_ohttp_request(request, &channel, settings).await
464}