1#![allow(unknown_lints)]
6#![warn(rust_2018_idioms)]
7
8#[allow(unused_extern_crates)]
11extern crate rusqlite;
12
13use url::Url;
14#[macro_use]
15mod headers;
16
17mod backend;
18mod client;
19pub mod error;
20#[cfg(feature = "ohttp")]
21pub mod ohttp;
22#[cfg(feature = "ohttp")]
23mod ohttp_client;
24pub mod settings;
25pub use error::*;
26pub use error_support::{debug, error, info, trace, warn};
28
29pub use backend::{init_backend, Backend};
30pub use client::{Client, ClientSettings};
31pub use headers::{consts as header_names, Header, HeaderName, Headers, InvalidHeaderName};
32#[cfg(feature = "ohttp")]
33pub use ohttp::{clear_ohttp_channels, configure_ohttp_channel, list_ohttp_channels, OhttpConfig};
34pub use settings::{allow_android_emulator_loopback, GLOBAL_SETTINGS};
35
36#[derive(Clone, Debug, Copy, PartialEq, PartialOrd, Eq, Ord, Hash, uniffi::Enum)]
40#[repr(u8)]
41pub enum Method {
42 Get,
43 Head,
44 Post,
45 Put,
46 Delete,
47 Connect,
48 Options,
49 Trace,
50 Patch,
51}
52
53impl Method {
54 pub fn as_str(self) -> &'static str {
55 match self {
56 Method::Get => "GET",
57 Method::Head => "HEAD",
58 Method::Post => "POST",
59 Method::Put => "PUT",
60 Method::Delete => "DELETE",
61 Method::Connect => "CONNECT",
62 Method::Options => "OPTIONS",
63 Method::Trace => "TRACE",
64 Method::Patch => "PATCH",
65 }
66 }
67}
68
69impl std::fmt::Display for Method {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 f.write_str(self.as_str())
72 }
73}
74
75#[must_use = "`Request`'s \"builder\" functions take by move, not by `&mut self`"]
76#[derive(Clone, uniffi::Record)]
77pub struct Request {
78 pub method: Method,
79 pub url: ViaductUrl,
80 pub headers: Headers,
81 pub body: Option<Vec<u8>>,
82}
83
84impl Request {
85 pub fn new(method: Method, url: Url) -> Self {
88 Self {
89 method,
90 url,
91 headers: Headers::new(),
92 body: None,
93 }
94 }
95
96 pub fn send(self) -> Result<Response, ViaductError> {
101 Client::default().send_sync(self)
102 }
103
104 pub fn get(url: Url) -> Self {
106 Self::new(Method::Get, url)
107 }
108
109 pub fn patch(url: Url) -> Self {
111 Self::new(Method::Patch, url)
112 }
113
114 pub fn post(url: Url) -> Self {
116 Self::new(Method::Post, url)
117 }
118
119 pub fn put(url: Url) -> Self {
121 Self::new(Method::Put, url)
122 }
123
124 pub fn delete(url: Url) -> Self {
126 Self::new(Method::Delete, url)
127 }
128
129 pub fn query(mut self, pairs: &[(&str, &str)]) -> Self {
145 let mut append_to = self.url.query_pairs_mut();
146 for (k, v) in pairs {
147 append_to.append_pair(k, v);
148 }
149 drop(append_to);
150 self
151 }
152
153 pub fn set_query<'a, Q: Into<Option<&'a str>>>(mut self, query: Q) -> Self {
172 self.url.set_query(query.into());
173 self
174 }
175
176 pub fn headers<I>(mut self, to_add: I) -> Self
179 where
180 I: IntoIterator<Item = Header>,
181 {
182 self.headers.extend(to_add);
183 self
184 }
185
186 pub fn header<Name, Val>(mut self, name: Name, val: Val) -> Result<Self, crate::ViaductError>
205 where
206 Name: Into<HeaderName> + PartialEq<HeaderName>,
207 Val: Into<String> + AsRef<str>,
208 {
209 self.headers.insert(name, val)?;
210 Ok(self)
211 }
212
213 pub fn body(mut self, body: impl Into<Vec<u8>>) -> Self {
215 self.body = Some(body.into());
216 self
217 }
218
219 pub fn json<T: ?Sized + serde::Serialize>(mut self, val: &T) -> Self {
233 self.body =
234 Some(serde_json::to_vec(val).expect("Rust component bug: serde_json::to_vec failure"));
235 self.headers
236 .insert_if_missing(header_names::CONTENT_TYPE, "application/json")
237 .unwrap(); self
239 }
240}
241
242impl std::fmt::Debug for Request {
244 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245 f.debug_struct("Request")
246 .field("method", &self.method)
247 .field("url", &self.url.to_string())
248 .field("headers", &self.headers)
249 .field(
250 "body",
251 &self.body.as_ref().map(|body| String::from_utf8_lossy(body)),
252 )
253 .finish()
254 }
255}
256
257#[derive(Clone, uniffi::Record)]
259pub struct Response {
260 pub request_method: Method,
262 pub url: ViaductUrl,
264 pub status: u16,
266 pub headers: Headers,
268 pub body: Vec<u8>,
270}
271
272impl Response {
273 pub fn json<'a, T>(&'a self) -> Result<T, serde_json::Error>
275 where
276 T: serde::Deserialize<'a>,
277 {
278 serde_json::from_slice(&self.body)
279 }
280
281 pub fn text(&self) -> std::borrow::Cow<'_, str> {
284 String::from_utf8_lossy(&self.body)
285 }
286
287 #[inline]
289 pub fn is_success(&self) -> bool {
290 status_codes::is_success_code(self.status)
291 }
292
293 #[inline]
295 pub fn is_server_error(&self) -> bool {
296 status_codes::is_server_error_code(self.status)
297 }
298
299 #[inline]
301 pub fn is_client_error(&self) -> bool {
302 status_codes::is_client_error_code(self.status)
303 }
304
305 #[inline]
308 pub fn require_success(self) -> Result<Self, UnexpectedStatus> {
309 if self.is_success() {
310 Ok(self)
311 } else {
312 Err(UnexpectedStatus {
313 method: self.request_method,
314 url: self.url,
317 status: self.status,
318 })
319 }
320 }
321}
322
323impl std::fmt::Debug for Response {
325 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
326 f.debug_struct("Response")
327 .field("request_method", &self.request_method)
328 .field("url", &self.url.to_string())
329 .field("status", &self.status)
330 .field("headers", &self.headers)
331 .field("body", &String::from_utf8_lossy(&self.body))
332 .finish()
333 }
334}
335
336pub mod status_codes {
338
339 #[inline]
341 pub fn is_success_code(c: u16) -> bool {
342 (200..300).contains(&c)
343 }
344
345 #[inline]
347 pub fn is_client_error_code(c: u16) -> bool {
348 (400..500).contains(&c)
349 }
350
351 #[inline]
353 pub fn is_server_error_code(c: u16) -> bool {
354 (500..600).contains(&c)
355 }
356
357 macro_rules! define_status_codes {
358 ($(($val:expr, $NAME:ident)),* $(,)?) => {
359 $(pub const $NAME: u16 = $val;)*
360 };
361 }
362 define_status_codes![
364 (100, CONTINUE),
365 (101, SWITCHING_PROTOCOLS),
366 (200, OK),
368 (201, CREATED),
369 (202, ACCEPTED),
370 (203, NONAUTHORITATIVE_INFORMATION),
371 (204, NO_CONTENT),
372 (205, RESET_CONTENT),
373 (206, PARTIAL_CONTENT),
374 (300, MULTIPLE_CHOICES),
376 (301, MOVED_PERMANENTLY),
377 (302, FOUND),
378 (303, SEE_OTHER),
379 (304, NOT_MODIFIED),
380 (305, USE_PROXY),
381 (307, TEMPORARY_REDIRECT),
383 (400, BAD_REQUEST),
385 (401, UNAUTHORIZED),
386 (402, PAYMENT_REQUIRED),
387 (403, FORBIDDEN),
388 (404, NOT_FOUND),
389 (405, METHOD_NOT_ALLOWED),
390 (406, NOT_ACCEPTABLE),
391 (407, PROXY_AUTHENTICATION_REQUIRED),
392 (408, REQUEST_TIMEOUT),
393 (409, CONFLICT),
394 (410, GONE),
395 (411, LENGTH_REQUIRED),
396 (412, PRECONDITION_FAILED),
397 (413, REQUEST_ENTITY_TOO_LARGE),
398 (414, REQUEST_URI_TOO_LONG),
399 (415, UNSUPPORTED_MEDIA_TYPE),
400 (416, REQUESTED_RANGE_NOT_SATISFIABLE),
401 (417, EXPECTATION_FAILED),
402 (429, TOO_MANY_REQUESTS),
403 (500, INTERNAL_SERVER_ERROR),
405 (501, NOT_IMPLEMENTED),
406 (502, BAD_GATEWAY),
407 (503, SERVICE_UNAVAILABLE),
408 (504, GATEWAY_TIMEOUT),
409 (505, HTTP_VERSION_NOT_SUPPORTED),
410 ];
411}
412
413pub fn parse_url(url: &str) -> Result<Url, ViaductError> {
414 Ok(Url::parse(url)?)
415}
416
417pub type ViaductUrl = Url;
419
420uniffi::custom_type!(ViaductUrl, String, {
421 remote,
422 try_lift: |val| Ok(ViaductUrl::parse(&val)?),
423 lower: |obj| obj.into(),
424});
425
426uniffi::custom_type!(Headers, std::collections::HashMap<String, String>, {
427 remote,
428 try_lift: |map| {
429 Ok(map.into_iter()
430 .map(|(name, value)| Header::new(name, value))
431 .collect::<Result<Vec<Header>>>()?
432 .into()
433 )
434 },
435 lower: |headers| headers.into(),
436});
437
438uniffi::setup_scaffolding!("viaduct");
439
440#[cfg(feature = "ohttp")]
462#[uniffi::export]
463pub async fn send_ohttp_request(request: Request, channel: String) -> Result<Response> {
464 let settings = crate::ClientSettings::default();
465 crate::ohttp::process_ohttp_request(request, &channel, settings).await
466}