1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
//! Fetch API.
//!
//! Seed Fetch API is very similar to the browser [native one][fetch-mdn].
//!
//! There is one entry point: [`fetch`][fetch] function.
//! It can accept both string urls as well as [`Request`][request].
//!
//! To get a [`Response`][response] you need to `.await` fetch:
//! ```rust
//! let response = fetch("/foo").await?;
//! ```
//!
//! Then you can check [`Status`][status] and extract body in various formats:
//! ```rust
//! let response = fetch("/foo").await?.check_status()?;
//! let body: FooStruct = response.json().await?;
//! ```
//!
//! Use [`Request`][request] methods to set init options:
//! ```rust
//! fetch(Request::new(url).method(Method::Post)).await
//! ```
//!
//!
//! [fetch]: ./fn.fetch.html
//! [request]: ./struct.Request.html
//! [response]: ./struct.Response.html
//! [status]: ./struct.Status.html
//! [fetch-mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API

use crate::util::window;
use std::convert::TryInto;
use wasm_bindgen_futures::JsFuture;

pub mod header;
mod method;
mod request;
mod response;
mod status;

pub use header::{Header, Headers};
pub use method::*;
pub use request::*;
pub use response::*;
pub use status::*;

/// Convenient type alias.
pub type Result<T> = std::result::Result<T, FetchError>;

/// The main Fetch API function.
/// It fires a HTTP request.
///
/// ## Examples
///
/// Simple `GET` request:
/// ```rust
/// let response = fetch("https://seed-rs.org").await?;
/// let body = response.text().await?;
/// ```
///
/// `POST` request with `JSON` body:
/// ```rust
/// let form = Form{email: "foo@example.com"};
/// let response = fetch(Request::new("/api").method(Method::Post).json(form)).await?;
/// let data: SubmitResponse = response.json().await?;
/// ```
///
/// ## Errors
///
/// `fetch` will return `Err` only on network errors. This means that
/// even if you get `Ok` from this function, you still need to check
/// `Response` status for HTTP errors.
pub async fn fetch<'a>(request: impl Into<Request<'a>>) -> Result<Response> {
    let request = request.into();
    let promise = window().fetch_with_request(&request.try_into()?);

    let raw_response = JsFuture::from(promise)
        .await
        .map(Into::into)
        .map_err(FetchError::NetworkError)?;

    Ok(Response { raw_response })
}

#[allow(clippy::module_name_repetitions)]
#[derive(Debug)]
pub enum FetchError {
    SerdeError(serde_json::Error),
    DomException(web_sys::DomException),
    PromiseError(wasm_bindgen::JsValue),
    NetworkError(wasm_bindgen::JsValue),
    /// Request construction failed.
    RequestError(wasm_bindgen::JsValue),
    StatusError(Status),
}

#[cfg(test)]
mod tests {
    use wasm_bindgen_test::*;
    wasm_bindgen_test_configure!(run_in_browser);
    use super::*;
    use crate::browser::Url;

    #[wasm_bindgen_test]
    fn test_fetch_args() {
        let _ = fetch("https://seed-rs.org");
        let _ = fetch(String::from("https://seed-rs.org"));
        let _ = fetch(Url::new().set_path(&["/", "foo"]));
        let _ = fetch(Request::new("https://seed-rs.org"));
    }
}