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