use crate::fetch::headers::JsHeaders;
use boa_engine::object::builtins::{JsPromise, JsUint8Array};
use boa_engine::value::{TryFromJs, TryIntoJs};
use boa_engine::{
Context, JsData, JsNativeError, JsResult, JsString, JsValue, boa_class, js_error, js_str,
js_string,
};
use boa_gc::{Finalize, Trace};
use http::StatusCode;
use std::rc::Rc;
#[derive(Debug, Copy, Clone)]
pub enum ResponseType {
Basic,
Cors,
Error,
Opaque,
OpaqueRedirect,
}
impl ResponseType {
#[must_use]
pub fn to_string(self) -> JsString {
match self {
ResponseType::Basic => js_string!("basic"),
ResponseType::Cors => js_string!("cors"),
ResponseType::Error => js_string!("error"),
ResponseType::Opaque => js_string!("opaque"),
ResponseType::OpaqueRedirect => js_string!("opaqueredirect"),
}
}
}
impl TryFromJs for ResponseType {
fn try_from_js(value: &JsValue, context: &mut Context) -> JsResult<Self> {
let value_str = value.to_string(context)?;
if value_str == js_str!("basic") {
Ok(ResponseType::Basic)
} else if value_str == js_str!("cors") {
Ok(ResponseType::Cors)
} else if value_str == js_str!("error") {
Ok(ResponseType::Error)
} else if value_str == js_str!("opaque") {
Ok(ResponseType::Opaque)
} else if value_str == js_str!("opaqueredirect") {
Ok(ResponseType::OpaqueRedirect)
} else {
Err(js_error!(TypeError: "Invalid response type value"))
}
}
}
impl TryIntoJs for ResponseType {
fn try_into_js(&self, _: &mut Context) -> JsResult<JsValue> {
Ok(self.to_string().into())
}
}
#[derive(Clone, Debug, Trace, Finalize, JsData)]
pub struct JsResponse {
url: JsString,
#[unsafe_ignore_trace]
r#type: ResponseType,
#[unsafe_ignore_trace]
status: Option<StatusCode>,
headers: JsHeaders,
#[unsafe_ignore_trace]
body: Rc<Vec<u8>>,
}
impl JsResponse {
#[must_use]
pub fn basic(url: JsString, inner: http::Response<Vec<u8>>) -> Self {
let (parts, body) = inner.into_parts();
let status = Some(parts.status);
let headers = JsHeaders::from_http(parts.headers);
let body = Rc::new(body);
Self {
url,
r#type: ResponseType::Basic,
status,
headers,
body,
}
}
#[must_use]
pub fn error() -> Self {
Self {
url: js_string!(""),
r#type: ResponseType::Error,
status: None,
headers: JsHeaders::default(),
body: Rc::new(Vec::new()),
}
}
#[must_use]
pub fn body(&self) -> Rc<Vec<u8>> {
self.body.clone()
}
}
#[derive(Debug, Clone, TryFromJs, TryIntoJs, Trace, Finalize, JsData)]
#[boa(rename_all = "camelCase")]
pub struct JsResponseOptions {
status: Option<u16>,
status_text: Option<JsString>,
headers: Option<JsHeaders>,
}
#[boa_class(rename = "Response")]
#[boa(rename_all = "camelCase")]
impl JsResponse {
#[boa(static)]
#[boa(rename = "error")]
fn error_() -> Self {
Self::error()
}
#[boa(constructor)]
fn constructor(_body: Option<JsValue>, _options: JsResponseOptions) -> Self {
Self::basic(js_string!(""), http::Response::new(Vec::new()))
}
#[boa(getter)]
fn status(&self) -> u16 {
self.status.map_or(0, |s| s.as_u16())
}
#[boa(getter)]
fn status_text(&self) -> JsString {
if let Some(status) = self.status {
JsString::from(status.canonical_reason().unwrap_or_else(|| status.as_str()))
} else {
JsString::default()
}
}
#[boa(getter)]
fn headers(&self) -> JsHeaders {
self.headers.clone()
}
#[boa(getter)]
#[boa(rename = "type")]
fn r#type(&self) -> JsString {
self.r#type.to_string()
}
#[boa(getter)]
fn url(&self) -> JsString {
self.url.clone()
}
fn bytes(&self, context: &mut Context) -> JsPromise {
let body = self.body.clone();
JsPromise::from_async_fn(
async move |context| {
JsUint8Array::from_iter(body.iter().copied(), &mut context.borrow_mut())
.map(Into::into)
},
context,
)
}
fn text(&self, context: &mut Context) -> JsPromise {
let body = self.body.clone();
JsPromise::from_async_fn(
async move |_| {
let body = String::from_utf8_lossy(body.as_ref());
Ok(JsString::from(body).into())
},
context,
)
}
fn json(&self, context: &mut Context) -> JsPromise {
let body = self.body.clone();
JsPromise::from_async_fn(
async move |context| {
let json_string = String::from_utf8_lossy(body.as_ref());
let json = serde_json::from_str::<serde_json::Value>(&json_string)
.map_err(|e| JsNativeError::syntax().with_message(e.to_string()))?;
JsValue::from_json(&json, &mut context.borrow_mut())
},
context,
)
}
}