Skip to content

Commit

Permalink
feat(fetcher): add FetcherError and optimize headers handling and…
Browse files Browse the repository at this point in the history
… remove useless `serde` when not needed
  • Loading branch information
Vexcited committed Jan 3, 2025
1 parent 676ab44 commit ceb38c7
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 45 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 5 additions & 4 deletions fetcher/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
7 changes: 7 additions & 0 deletions fetcher/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,10 @@ export const defaultFetcher: Fetcher = async (req) => {
bytes
};
};

export class FetcherError extends Error {
constructor(message: string) {
super(message);
this.name = "FetcherError";
}
}
131 changes: 90 additions & 41 deletions fetcher/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<Error> 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<Response, Error> {
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()
};
Expand All @@ -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<Response, Error> {
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>(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::<ResponseWasm>(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 {
Expand All @@ -66,14 +120,16 @@ pub struct Request {
pub follow: bool,
}

#[cfg(target_arch = "wasm32")]
impl Serialize for Request {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
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())?;

Expand All @@ -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<u8>,
}

pub struct Response {
pub status: u16,
pub headers: HeaderMap,
pub bytes: Vec<u8>,
}

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
}
}

0 comments on commit ceb38c7

Please sign in to comment.