diff --git a/Cargo.lock b/Cargo.lock index c86fd7e..33d3307 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -572,6 +572,7 @@ dependencies = [ "serde", "serde-wasm-bindgen", "serde_bytes", + "thiserror", "url", "wasm-bindgen", "wasm-bindgen-futures", diff --git a/fetcher/Cargo.toml b/fetcher/Cargo.toml index a4c3344..819d94a 100644 --- a/fetcher/Cargo.toml +++ b/fetcher/Cargo.toml @@ -8,16 +8,17 @@ repository.workspace = true edition = "2021" [dependencies] -serde = { version = "1.0", features = ["derive"] } -serde_bytes = "0.11" +thiserror = "2.0" http = "1.2" url = "2.5" [target.'cfg(target_arch = "wasm32")'.dependencies] -js-sys = "0.3" -wasm-bindgen = "0.2" +serde = { version = "1.0", features = ["derive"] } wasm-bindgen-futures = "0.4" serde-wasm-bindgen = "0.6" +serde_bytes = "0.11" +wasm-bindgen = "0.2" +js-sys = "0.3" [target.'cfg(not(target_arch = "wasm32"))'.dependencies] reqwest = "0.12" diff --git a/fetcher/src/index.ts b/fetcher/src/index.ts index d7990b3..94ecf8c 100644 --- a/fetcher/src/index.ts +++ b/fetcher/src/index.ts @@ -83,3 +83,10 @@ export const defaultFetcher: Fetcher = async (req) => { bytes }; }; + +export class FetcherError extends Error { + constructor(message: string) { + super(message); + this.name = "FetcherError"; + } +} diff --git a/fetcher/src/lib.rs b/fetcher/src/lib.rs index 2bffc49..1bfa2fc 100644 --- a/fetcher/src/lib.rs +++ b/fetcher/src/lib.rs @@ -1,15 +1,47 @@ +#[cfg(target_arch = "wasm32")] use serde::{Deserialize, Serialize, Serializer}; -use std::str::FromStr; +use thiserror::Error; pub use http::{HeaderMap, HeaderName, Method}; pub use url::Url; +#[derive(Error, Debug)] +pub enum Error { + #[cfg(not(target_arch = "wasm32"))] + #[error("{0}")] + FetcherError(#[from] reqwest::Error), + #[cfg(target_arch = "wasm32")] + #[error("{0}")] + FetcherError(String), +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen::prelude::wasm_bindgen] +extern "C" { + #[wasm_bindgen(js_namespace = liUtilsFetcher)] + type FetcherError; + + #[wasm_bindgen(constructor, js_namespace = liUtilsFetcher)] + fn new(message: &str) -> FetcherError; +} + +#[cfg(target_arch = "wasm32")] +impl From for wasm_bindgen::JsValue { + fn from(error: Error) -> Self { + let error_msg = error.to_string(); + + match error { + Error::FetcherError() => FetcherError::new(&error_msg).into(), + } + } +} + #[cfg(not(target_arch = "wasm32"))] -pub async fn fetch(request: Request) -> Response { +pub async fn fetch(request: Request) -> Result { use reqwest::{redirect::Policy, Client}; let client = if !request.follow { - Client::builder().redirect(Policy::none()).build().unwrap() + Client::builder().redirect(Policy::none()).build()? } else { Client::new() }; @@ -19,42 +51,64 @@ pub async fn fetch(request: Request) -> Response { .headers(request.headers) .body(request.body.unwrap_or_default()) .send() - .await - .unwrap(); + .await?; let status = response.status().as_u16(); + let headers = response.headers().clone(); - let headers = response - .headers() - .iter() - .map(|(key, value)| { - let key = key.as_str().to_string(); - let value = value.to_str().unwrap_or_default().to_string(); - - (key, value) - }) - .collect(); - - let bytes = response.bytes().await.unwrap(); + let bytes = response.bytes().await?; let bytes = bytes.to_vec(); - Response { + Ok(Response { status, headers, bytes, - } + }) } #[cfg(target_arch = "wasm32")] -pub async fn fetch(request: Request, fetcher: &js_sys::Function) -> Response { +pub async fn fetch(request: Request, fetcher: &js_sys::Function) -> Result { use js_sys::Promise; + use std::str::FromStr; use wasm_bindgen::JsValue; use wasm_bindgen_futures::JsFuture; - let request = serde_wasm_bindgen::to_value(&request).unwrap(); - let response = Promise::from(fetcher.call1(&JsValue::NULL, &request).unwrap()); - let response = JsFuture::from(response).await.unwrap(); - serde_wasm_bindgen::from_value::(response).unwrap() + let request = + serde_wasm_bindgen::to_value(&request).map_err(|err| Error::FetcherError(err.to_string()))?; + + let response = Promise::from(fetcher.call1(&JsValue::NULL, &request).map_err(|err| { + Error::FetcherError( + err + .as_string() + .unwrap_or("error calling the fetcher".into()), + ) + })?); + + let response = JsFuture::from(response).await.map_err(|err| { + Error::FetcherError( + err + .as_string() + .unwrap_or("error during the fetcher promise".into()), + ) + })?; + + let response = serde_wasm_bindgen::from_value::(response) + .map_err(|err| Error::FetcherError(err.to_string()))?; + + let mut headers = HeaderMap::new(); + + for (key, value) in response.headers { + let key = HeaderName::from_str(key.as_str()).unwrap(); + let value = value.parse().unwrap(); + + headers.insert(key, value); + } + + Ok(Response { + status: response.status, + headers, + bytes: response.bytes, + }) } pub struct Request { @@ -66,6 +120,7 @@ pub struct Request { pub follow: bool, } +#[cfg(target_arch = "wasm32")] impl Serialize for Request { fn serialize(&self, serializer: S) -> Result where @@ -73,7 +128,8 @@ impl Serialize for Request { { use serde::ser::SerializeStruct; - let mut state = serializer.serialize_struct("Request", 4)?; + let field_count = 4 + self.body.is_some() as usize; + let mut state = serializer.serialize_struct("Request", field_count)?; state.serialize_field("url", &self.url.as_str())?; state.serialize_field("method", &self.method.as_str())?; @@ -100,30 +156,23 @@ impl Serialize for Request { } } +#[cfg(target_arch = "wasm32")] #[derive(Deserialize)] -pub struct Response { +struct ResponseWasm { pub status: u16, - /// Headers are represented as a list of key-value pairs - /// where the key is the header name (always lowercase) and the value is the header value. - headers: Vec<(String, String)>, + pub headers: Vec<(String, String)>, #[serde(with = "serde_bytes")] pub bytes: Vec, } +pub struct Response { + pub status: u16, + pub headers: HeaderMap, + pub bytes: Vec, +} + impl Response { pub fn text(&self) -> String { String::from_utf8_lossy(&self.bytes).to_string() } - pub fn headers(&self) -> HeaderMap { - let mut headers = HeaderMap::new(); - - for (key, value) in &self.headers { - let key = HeaderName::from_str(key.as_str()).unwrap(); - let value = value.parse().unwrap(); - - headers.insert(key, value); - } - - headers - } }