http.rs 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  1. // Copyright 2019-2021 Tauri Programme within The Commons Conservancy
  2. // SPDX-License-Identifier: Apache-2.0
  3. // SPDX-License-Identifier: MIT
  4. //! Types and functions related to HTTP request.
  5. use http::Method;
  6. pub use http::StatusCode;
  7. use serde::{Deserialize, Deserializer, Serialize};
  8. use serde_json::Value;
  9. use serde_repr::{Deserialize_repr, Serialize_repr};
  10. use url::Url;
  11. use std::{collections::HashMap, path::PathBuf, time::Duration};
  12. #[cfg(feature = "reqwest-client")]
  13. pub use reqwest::header;
  14. #[cfg(not(feature = "reqwest-client"))]
  15. pub use attohttpc::header;
  16. use header::{HeaderName, HeaderValue};
  17. #[derive(Deserialize)]
  18. #[serde(untagged)]
  19. enum SerdeDuration {
  20. Seconds(u64),
  21. Duration(Duration),
  22. }
  23. fn deserialize_duration<'de, D: Deserializer<'de>>(
  24. deserializer: D,
  25. ) -> Result<Option<Duration>, D::Error> {
  26. if let Some(duration) = Option::<SerdeDuration>::deserialize(deserializer)? {
  27. Ok(Some(match duration {
  28. SerdeDuration::Seconds(s) => Duration::from_secs(s),
  29. SerdeDuration::Duration(d) => d,
  30. }))
  31. } else {
  32. Ok(None)
  33. }
  34. }
  35. /// The builder of [`Client`].
  36. #[derive(Debug, Clone, Default, Deserialize)]
  37. #[serde(rename_all = "camelCase")]
  38. pub struct ClientBuilder {
  39. /// Max number of redirections to follow.
  40. pub max_redirections: Option<usize>,
  41. /// Connect timeout for the request.
  42. #[serde(deserialize_with = "deserialize_duration", default)]
  43. pub connect_timeout: Option<Duration>,
  44. }
  45. impl ClientBuilder {
  46. /// Creates a new client builder with the default options.
  47. pub fn new() -> Self {
  48. Default::default()
  49. }
  50. /// Sets the maximum number of redirections.
  51. #[must_use]
  52. pub fn max_redirections(mut self, max_redirections: usize) -> Self {
  53. self.max_redirections = Some(max_redirections);
  54. self
  55. }
  56. /// Sets the connection timeout.
  57. #[must_use]
  58. pub fn connect_timeout(mut self, connect_timeout: Duration) -> Self {
  59. self.connect_timeout.replace(connect_timeout);
  60. self
  61. }
  62. /// Builds the Client.
  63. #[cfg(not(feature = "reqwest-client"))]
  64. pub fn build(self) -> crate::api::Result<Client> {
  65. Ok(Client(self))
  66. }
  67. /// Builds the Client.
  68. #[cfg(feature = "reqwest-client")]
  69. pub fn build(self) -> crate::api::Result<Client> {
  70. let mut client_builder = reqwest::Client::builder();
  71. if let Some(max_redirections) = self.max_redirections {
  72. client_builder = client_builder.redirect(if max_redirections == 0 {
  73. reqwest::redirect::Policy::none()
  74. } else {
  75. reqwest::redirect::Policy::limited(max_redirections)
  76. });
  77. }
  78. if let Some(connect_timeout) = self.connect_timeout {
  79. client_builder = client_builder.connect_timeout(connect_timeout);
  80. }
  81. let client = client_builder.build()?;
  82. Ok(Client(client))
  83. }
  84. }
  85. /// The HTTP client based on [`reqwest`].
  86. #[cfg(feature = "reqwest-client")]
  87. #[derive(Debug, Clone)]
  88. pub struct Client(reqwest::Client);
  89. /// The HTTP client.
  90. #[cfg(not(feature = "reqwest-client"))]
  91. #[derive(Debug, Clone)]
  92. pub struct Client(ClientBuilder);
  93. #[cfg(not(feature = "reqwest-client"))]
  94. impl Client {
  95. /// Executes an HTTP request.
  96. ///
  97. /// # Examples
  98. ///
  99. /// ```rust,no_run
  100. /// use tauri::api::http::{ClientBuilder, HttpRequestBuilder, ResponseType};
  101. /// async fn run_request() {
  102. /// let client = ClientBuilder::new().build().unwrap();
  103. /// let response = client.send(
  104. /// HttpRequestBuilder::new("GET", "https://www.rust-lang.org")
  105. /// .unwrap()
  106. /// .response_type(ResponseType::Binary)
  107. /// ).await;
  108. /// if let Ok(response) = response {
  109. /// let bytes = response.bytes();
  110. /// }
  111. /// }
  112. /// ```
  113. pub async fn send(&self, request: HttpRequestBuilder) -> crate::api::Result<Response> {
  114. let method = Method::from_bytes(request.method.to_uppercase().as_bytes())?;
  115. let mut request_builder = attohttpc::RequestBuilder::try_new(method, &request.url)?;
  116. if let Some(query) = request.query {
  117. request_builder = request_builder.params(&query);
  118. }
  119. if let Some(headers) = &request.headers {
  120. for (name, value) in headers.0.iter() {
  121. request_builder = request_builder.header(name, value);
  122. }
  123. }
  124. if let Some(max_redirections) = self.0.max_redirections {
  125. if max_redirections == 0 {
  126. request_builder = request_builder.follow_redirects(false);
  127. } else {
  128. request_builder = request_builder.max_redirections(max_redirections as u32);
  129. }
  130. }
  131. if let Some(timeout) = request.timeout {
  132. request_builder = request_builder.timeout(timeout);
  133. #[cfg(windows)]
  134. {
  135. // on Windows the global timeout is not respected, see https://github.com/sbstp/attohttpc/issues/118
  136. request_builder = request_builder.read_timeout(timeout);
  137. }
  138. }
  139. let response = if let Some(body) = request.body {
  140. match body {
  141. Body::Bytes(data) => request_builder.body(attohttpc::body::Bytes(data)).send()?,
  142. Body::Text(text) => request_builder.body(attohttpc::body::Bytes(text)).send()?,
  143. Body::Json(json) => request_builder.json(&json)?.send()?,
  144. Body::Form(form_body) => {
  145. #[allow(unused_variables)]
  146. fn send_form(
  147. request_builder: attohttpc::RequestBuilder,
  148. headers: &Option<HeaderMap>,
  149. form_body: FormBody,
  150. ) -> crate::api::Result<attohttpc::Response> {
  151. #[cfg(feature = "http-multipart")]
  152. if matches!(
  153. headers
  154. .as_ref()
  155. .and_then(|h| h.0.get("content-type"))
  156. .map(|v| v.as_bytes()),
  157. Some(b"multipart/form-data")
  158. ) {
  159. let mut multipart = attohttpc::MultipartBuilder::new();
  160. let mut byte_cache: HashMap<String, Vec<u8>> = Default::default();
  161. for (name, part) in &form_body.0 {
  162. if let FormPart::File { file, .. } = part {
  163. byte_cache.insert(name.to_string(), file.clone().try_into()?);
  164. }
  165. }
  166. for (name, part) in &form_body.0 {
  167. multipart = match part {
  168. FormPart::File {
  169. file,
  170. mime,
  171. file_name,
  172. } => {
  173. // safe to unwrap: always set by previous loop
  174. let mut file =
  175. attohttpc::MultipartFile::new(name, byte_cache.get(name).unwrap());
  176. if let Some(mime) = mime {
  177. file = file.with_type(mime)?;
  178. }
  179. if let Some(file_name) = file_name {
  180. file = file.with_filename(file_name);
  181. }
  182. multipart.with_file(file)
  183. }
  184. FormPart::Text(value) => multipart.with_text(name, value),
  185. };
  186. }
  187. return request_builder
  188. .body(multipart.build()?)
  189. .send()
  190. .map_err(Into::into);
  191. }
  192. let mut form = Vec::new();
  193. for (name, part) in form_body.0 {
  194. match part {
  195. FormPart::File { file, .. } => {
  196. let bytes: Vec<u8> = file.try_into()?;
  197. form.push((name, serde_json::to_string(&bytes)?))
  198. }
  199. FormPart::Text(value) => form.push((name, value)),
  200. }
  201. }
  202. request_builder.form(&form)?.send().map_err(Into::into)
  203. }
  204. send_form(request_builder, &request.headers, form_body)?
  205. }
  206. }
  207. } else {
  208. request_builder.send()?
  209. };
  210. Ok(Response(
  211. request.response_type.unwrap_or(ResponseType::Json),
  212. response,
  213. request.url,
  214. ))
  215. }
  216. }
  217. #[cfg(feature = "reqwest-client")]
  218. impl Client {
  219. /// Executes an HTTP request
  220. ///
  221. /// # Examples
  222. pub async fn send(&self, mut request: HttpRequestBuilder) -> crate::api::Result<Response> {
  223. let method = Method::from_bytes(request.method.to_uppercase().as_bytes())?;
  224. let mut request_builder = self.0.request(method, request.url.as_str());
  225. if let Some(query) = request.query {
  226. request_builder = request_builder.query(&query);
  227. }
  228. if let Some(timeout) = request.timeout {
  229. request_builder = request_builder.timeout(timeout);
  230. }
  231. if let Some(body) = request.body {
  232. request_builder = match body {
  233. Body::Bytes(data) => request_builder.body(bytes::Bytes::from(data)),
  234. Body::Text(text) => request_builder.body(bytes::Bytes::from(text)),
  235. Body::Json(json) => request_builder.json(&json),
  236. Body::Form(form_body) => {
  237. #[allow(unused_variables)]
  238. fn send_form(
  239. request_builder: reqwest::RequestBuilder,
  240. headers: &mut Option<HeaderMap>,
  241. form_body: FormBody,
  242. ) -> crate::api::Result<reqwest::RequestBuilder> {
  243. #[cfg(feature = "http-multipart")]
  244. if matches!(
  245. headers
  246. .as_ref()
  247. .and_then(|h| h.0.get("content-type"))
  248. .map(|v| v.as_bytes()),
  249. Some(b"multipart/form-data")
  250. ) {
  251. // the Content-Type header will be set by reqwest in the `.multipart` call
  252. headers.as_mut().map(|h| h.0.remove("content-type"));
  253. let mut multipart = reqwest::multipart::Form::new();
  254. for (name, part) in form_body.0 {
  255. let part = match part {
  256. FormPart::File {
  257. file,
  258. mime,
  259. file_name,
  260. } => {
  261. let bytes: Vec<u8> = file.try_into()?;
  262. let mut part = reqwest::multipart::Part::bytes(bytes);
  263. if let Some(mime) = mime {
  264. part = part.mime_str(&mime)?;
  265. }
  266. if let Some(file_name) = file_name {
  267. part = part.file_name(file_name);
  268. }
  269. part
  270. }
  271. FormPart::Text(value) => reqwest::multipart::Part::text(value),
  272. };
  273. multipart = multipart.part(name, part);
  274. }
  275. return Ok(request_builder.multipart(multipart));
  276. }
  277. let mut form = Vec::new();
  278. for (name, part) in form_body.0 {
  279. match part {
  280. FormPart::File { file, .. } => {
  281. let bytes: Vec<u8> = file.try_into()?;
  282. form.push((name, serde_json::to_string(&bytes)?))
  283. }
  284. FormPart::Text(value) => form.push((name, value)),
  285. }
  286. }
  287. Ok(request_builder.form(&form))
  288. }
  289. send_form(request_builder, &mut request.headers, form_body)?
  290. }
  291. };
  292. }
  293. if let Some(headers) = request.headers {
  294. request_builder = request_builder.headers(headers.0);
  295. }
  296. let http_request = request_builder.build()?;
  297. let response = self.0.execute(http_request).await?;
  298. Ok(Response(
  299. request.response_type.unwrap_or(ResponseType::Json),
  300. response,
  301. ))
  302. }
  303. }
  304. #[derive(Serialize_repr, Deserialize_repr, Clone, Debug)]
  305. #[repr(u16)]
  306. #[non_exhaustive]
  307. /// The HTTP response type.
  308. pub enum ResponseType {
  309. /// Read the response as JSON
  310. Json = 1,
  311. /// Read the response as text
  312. Text,
  313. /// Read the response as binary
  314. Binary,
  315. }
  316. /// A file path or contents.
  317. #[derive(Debug, Clone, Deserialize)]
  318. #[serde(untagged)]
  319. #[non_exhaustive]
  320. pub enum FilePart {
  321. /// File path.
  322. Path(PathBuf),
  323. /// File contents.
  324. Contents(Vec<u8>),
  325. }
  326. impl TryFrom<FilePart> for Vec<u8> {
  327. type Error = crate::api::Error;
  328. fn try_from(file: FilePart) -> crate::api::Result<Self> {
  329. let bytes = match file {
  330. FilePart::Path(path) => std::fs::read(&path)?,
  331. FilePart::Contents(bytes) => bytes,
  332. };
  333. Ok(bytes)
  334. }
  335. }
  336. /// [`FormBody`] data types.
  337. #[derive(Debug, Deserialize)]
  338. #[serde(untagged)]
  339. #[non_exhaustive]
  340. pub enum FormPart {
  341. /// A string value.
  342. Text(String),
  343. /// A file value.
  344. #[serde(rename_all = "camelCase")]
  345. File {
  346. /// File path or content.
  347. file: FilePart,
  348. /// Mime type of this part.
  349. /// Only used when the `Content-Type` header is set to `multipart/form-data`.
  350. mime: Option<String>,
  351. /// File name.
  352. /// Only used when the `Content-Type` header is set to `multipart/form-data`.
  353. file_name: Option<String>,
  354. },
  355. }
  356. /// Form body definition.
  357. #[derive(Debug, Deserialize)]
  358. pub struct FormBody(pub(crate) HashMap<String, FormPart>);
  359. impl FormBody {
  360. /// Creates a new form body.
  361. pub fn new(data: HashMap<String, FormPart>) -> Self {
  362. Self(data)
  363. }
  364. }
  365. /// A body for the request.
  366. #[derive(Debug, Deserialize)]
  367. #[serde(tag = "type", content = "payload")]
  368. #[non_exhaustive]
  369. pub enum Body {
  370. /// A form body.
  371. Form(FormBody),
  372. /// A JSON body.
  373. Json(Value),
  374. /// A text string body.
  375. Text(String),
  376. /// A byte array body.
  377. Bytes(Vec<u8>),
  378. }
  379. /// A set of HTTP headers.
  380. #[derive(Debug, Default)]
  381. pub struct HeaderMap(header::HeaderMap);
  382. impl<'de> Deserialize<'de> for HeaderMap {
  383. fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
  384. where
  385. D: Deserializer<'de>,
  386. {
  387. let map = HashMap::<String, String>::deserialize(deserializer)?;
  388. let mut headers = header::HeaderMap::default();
  389. for (key, value) in map {
  390. if let (Ok(key), Ok(value)) = (
  391. header::HeaderName::from_bytes(key.as_bytes()),
  392. header::HeaderValue::from_str(&value),
  393. ) {
  394. headers.insert(key, value);
  395. } else {
  396. return Err(serde::de::Error::custom(format!(
  397. "invalid header `{}` `{}`",
  398. key, value
  399. )));
  400. }
  401. }
  402. Ok(Self(headers))
  403. }
  404. }
  405. /// The builder for a HTTP request.
  406. ///
  407. /// # Examples
  408. /// ```rust,no_run
  409. /// use tauri::api::http::{HttpRequestBuilder, ResponseType, ClientBuilder};
  410. /// async fn run() {
  411. /// let client = ClientBuilder::new()
  412. /// .max_redirections(3)
  413. /// .build()
  414. /// .unwrap();
  415. /// let request = HttpRequestBuilder::new("GET", "http://example.com").unwrap()
  416. /// .response_type(ResponseType::Text);
  417. /// if let Ok(response) = client.send(request).await {
  418. /// println!("got response");
  419. /// } else {
  420. /// println!("Something Happened!");
  421. /// }
  422. /// }
  423. /// ```
  424. #[derive(Debug, Deserialize)]
  425. #[serde(rename_all = "camelCase")]
  426. pub struct HttpRequestBuilder {
  427. /// The request method (GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, CONNECT or TRACE)
  428. pub method: String,
  429. /// The request URL
  430. pub url: Url,
  431. /// The request query params
  432. pub query: Option<HashMap<String, String>>,
  433. /// The request headers
  434. pub headers: Option<HeaderMap>,
  435. /// The request body
  436. pub body: Option<Body>,
  437. /// Timeout for the whole request
  438. #[serde(deserialize_with = "deserialize_duration", default)]
  439. pub timeout: Option<Duration>,
  440. /// The response type (defaults to Json)
  441. pub response_type: Option<ResponseType>,
  442. }
  443. impl HttpRequestBuilder {
  444. /// Initializes a new instance of the HttpRequestrequest_builder.
  445. pub fn new(method: impl Into<String>, url: impl AsRef<str>) -> crate::api::Result<Self> {
  446. Ok(Self {
  447. method: method.into(),
  448. url: Url::parse(url.as_ref())?,
  449. query: None,
  450. headers: None,
  451. body: None,
  452. timeout: None,
  453. response_type: None,
  454. })
  455. }
  456. /// Sets the request parameters.
  457. #[must_use]
  458. pub fn query(mut self, query: HashMap<String, String>) -> Self {
  459. self.query = Some(query);
  460. self
  461. }
  462. /// Adds a header.
  463. pub fn header<K, V>(mut self, key: K, value: V) -> crate::api::Result<Self>
  464. where
  465. HeaderName: TryFrom<K>,
  466. <HeaderName as TryFrom<K>>::Error: Into<http::Error>,
  467. HeaderValue: TryFrom<V>,
  468. <HeaderValue as TryFrom<V>>::Error: Into<http::Error>,
  469. {
  470. let key: Result<HeaderName, http::Error> = key.try_into().map_err(Into::into);
  471. let value: Result<HeaderValue, http::Error> = value.try_into().map_err(Into::into);
  472. self
  473. .headers
  474. .get_or_insert_with(Default::default)
  475. .0
  476. .insert(key?, value?);
  477. Ok(self)
  478. }
  479. /// Sets the request headers.
  480. #[must_use]
  481. pub fn headers(mut self, headers: header::HeaderMap) -> Self {
  482. self.headers.replace(HeaderMap(headers));
  483. self
  484. }
  485. /// Sets the request body.
  486. #[must_use]
  487. pub fn body(mut self, body: Body) -> Self {
  488. self.body = Some(body);
  489. self
  490. }
  491. /// Sets the general request timeout.
  492. #[must_use]
  493. pub fn timeout(mut self, timeout: Duration) -> Self {
  494. self.timeout.replace(timeout);
  495. self
  496. }
  497. /// Sets the type of the response. Interferes with the way we read the response.
  498. #[must_use]
  499. pub fn response_type(mut self, response_type: ResponseType) -> Self {
  500. self.response_type = Some(response_type);
  501. self
  502. }
  503. }
  504. /// The HTTP response.
  505. #[cfg(feature = "reqwest-client")]
  506. #[derive(Debug)]
  507. pub struct Response(ResponseType, reqwest::Response);
  508. /// The HTTP response.
  509. #[cfg(not(feature = "reqwest-client"))]
  510. #[derive(Debug)]
  511. pub struct Response(ResponseType, attohttpc::Response, Url);
  512. impl Response {
  513. /// Get the [`StatusCode`] of this Response.
  514. pub fn status(&self) -> StatusCode {
  515. self.1.status()
  516. }
  517. /// Get the headers of this Response.
  518. pub fn headers(&self) -> &header::HeaderMap {
  519. self.1.headers()
  520. }
  521. /// Reads the response as raw bytes.
  522. pub async fn bytes(self) -> crate::api::Result<RawResponse> {
  523. let status = self.status().as_u16();
  524. #[cfg(feature = "reqwest-client")]
  525. let data = self.1.bytes().await?.to_vec();
  526. #[cfg(not(feature = "reqwest-client"))]
  527. let data = self.1.bytes()?;
  528. Ok(RawResponse { status, data })
  529. }
  530. #[cfg(not(feature = "reqwest-client"))]
  531. #[allow(dead_code)]
  532. pub(crate) fn reader(self) -> attohttpc::ResponseReader {
  533. let (_, _, reader) = self.1.split();
  534. reader
  535. }
  536. /// Convert the response into a Stream of [`bytes::Bytes`] from the body.
  537. ///
  538. /// # Examples
  539. ///
  540. /// ```no_run
  541. /// use futures::StreamExt;
  542. ///
  543. /// # async fn run() -> Result<(), Box<dyn std::error::Error>> {
  544. /// let client = tauri::api::http::ClientBuilder::new().build()?;
  545. /// let mut stream = client.send(tauri::api::http::HttpRequestBuilder::new("GET", "http://httpbin.org/ip")?)
  546. /// .await?
  547. /// .bytes_stream();
  548. ///
  549. /// while let Some(item) = stream.next().await {
  550. /// println!("Chunk: {:?}", item?);
  551. /// }
  552. /// # Ok(())
  553. /// # }
  554. /// ```
  555. #[cfg(feature = "reqwest-client")]
  556. #[allow(dead_code)]
  557. pub(crate) fn bytes_stream(
  558. self,
  559. ) -> impl futures::Stream<Item = crate::api::Result<bytes::Bytes>> {
  560. use futures::StreamExt;
  561. self.1.bytes_stream().map(|res| res.map_err(Into::into))
  562. }
  563. /// Reads the response.
  564. ///
  565. /// Note that the body is serialized to a [`Value`].
  566. pub async fn read(self) -> crate::api::Result<ResponseData> {
  567. #[cfg(feature = "reqwest-client")]
  568. let url = self.1.url().clone();
  569. #[cfg(not(feature = "reqwest-client"))]
  570. let url = self.2;
  571. let mut headers = HashMap::new();
  572. let mut raw_headers = HashMap::new();
  573. for (name, value) in self.1.headers() {
  574. headers.insert(
  575. name.as_str().to_string(),
  576. String::from_utf8(value.as_bytes().to_vec())?,
  577. );
  578. raw_headers.insert(
  579. name.as_str().to_string(),
  580. self
  581. .1
  582. .headers()
  583. .get_all(name)
  584. .into_iter()
  585. .map(|v| String::from_utf8(v.as_bytes().to_vec()).map_err(Into::into))
  586. .collect::<crate::api::Result<Vec<String>>>()?,
  587. );
  588. }
  589. let status = self.1.status().as_u16();
  590. #[cfg(feature = "reqwest-client")]
  591. let data = match self.0 {
  592. ResponseType::Json => self.1.json().await?,
  593. ResponseType::Text => Value::String(self.1.text().await?),
  594. ResponseType::Binary => serde_json::to_value(&self.1.bytes().await?)?,
  595. };
  596. #[cfg(not(feature = "reqwest-client"))]
  597. let data = match self.0 {
  598. ResponseType::Json => self.1.json()?,
  599. ResponseType::Text => Value::String(self.1.text()?),
  600. ResponseType::Binary => serde_json::to_value(&self.1.bytes()?)?,
  601. };
  602. Ok(ResponseData {
  603. url,
  604. status,
  605. headers,
  606. raw_headers,
  607. data,
  608. })
  609. }
  610. }
  611. /// A response with raw bytes.
  612. #[non_exhaustive]
  613. #[derive(Debug)]
  614. pub struct RawResponse {
  615. /// Response status code.
  616. pub status: u16,
  617. /// Response bytes.
  618. pub data: Vec<u8>,
  619. }
  620. /// The response data.
  621. #[derive(Debug, Serialize)]
  622. #[serde(rename_all = "camelCase")]
  623. #[non_exhaustive]
  624. pub struct ResponseData {
  625. /// Response URL. Useful if it followed redirects.
  626. pub url: Url,
  627. /// Response status code.
  628. pub status: u16,
  629. /// Response headers.
  630. pub headers: HashMap<String, String>,
  631. /// Response raw headers.
  632. pub raw_headers: HashMap<String, Vec<String>>,
  633. /// Response data.
  634. pub data: Value,
  635. }
  636. #[cfg(test)]
  637. mod test {
  638. use super::ClientBuilder;
  639. use quickcheck::{Arbitrary, Gen};
  640. impl Arbitrary for ClientBuilder {
  641. fn arbitrary(g: &mut Gen) -> Self {
  642. Self {
  643. max_redirections: Option::arbitrary(g),
  644. connect_timeout: Option::arbitrary(g),
  645. }
  646. }
  647. }
  648. }