Update js functions to new rquickjs version (#2252)

This commit is contained in:
Mees Delzenne 2023-07-14 15:35:32 +02:00 committed by GitHub
parent 1e30eb4aa1
commit 4f4339848e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 1362 additions and 1498 deletions

389
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -75,7 +75,7 @@ fuzzy-matcher = "0.3.7"
geo = { version = "0.25.1", features = ["use-serde"] } geo = { version = "0.25.1", features = ["use-serde"] }
indexmap = { version = "1.9.3", features = ["serde"] } indexmap = { version = "1.9.3", features = ["serde"] }
indxdb = { version = "0.3.0", optional = true } indxdb = { version = "0.3.0", optional = true }
js = { version = "0.3.1" , package = "rquickjs", features = ["array-buffer", "bindgen", "classes", "futures", "loader", "macro", "parallel", "properties","rust-alloc"], optional = true } js = { version = "0.4.0-beta.0" , package = "rquickjs", features = ["array-buffer", "bindgen", "classes", "futures", "loader", "macro", "parallel", "properties","rust-alloc"], optional = true }
jsonwebtoken = "8.3.0" jsonwebtoken = "8.3.0"
lexicmp = "0.1.0" lexicmp = "0.1.0"
lru = "0.10.1" lru = "0.10.1"
@ -155,4 +155,4 @@ harness = false
[[bench]] [[bench]]
name = "index_btree" name = "index_btree"
harness = false harness = false

View file

@ -1,51 +1,48 @@
#[js::bind(object, public)] use js::class::Trace;
#[quickjs(bare)]
#[allow(non_snake_case)]
#[allow(unused_variables)]
#[allow(clippy::module_inception)]
pub mod duration {
use crate::sql::duration; use crate::sql::duration;
use crate::sql::value::Value;
use js::{class::Ref, function::Rest};
#[derive(Clone)] #[derive(Clone, Trace)]
#[quickjs(cloneable)] #[js::class]
pub struct Duration { pub struct Duration {
pub(crate) value: Option<duration::Duration>, #[qjs(skip_trace)]
pub(crate) value: Option<duration::Duration>,
}
#[js::methods]
impl Duration {
#[qjs(constructor)]
pub fn new(value: String) -> Self {
Self {
value: duration::Duration::try_from(value).ok(),
}
} }
impl Duration { #[qjs(get)]
#[quickjs(constructor)] pub fn value(&self) -> String {
pub fn new(value: String, args: Rest<Value>) -> Self { match &self.value {
Self { Some(v) => v.to_raw(),
value: duration::Duration::try_from(value).ok(), None => String::from("Invalid Duration"),
}
} }
#[quickjs(get)] }
pub fn value(&self) -> String { // Compare two Duration instances
match &self.value { pub fn is(a: &Duration, b: &Duration) -> bool {
Some(v) => v.to_raw(), a.value.is_some() && b.value.is_some() && a.value == b.value
None => String::from("Invalid Duration"), }
} /// Convert the object to a string
#[qjs(rename = "toString")]
pub fn js_to_string(&self) -> String {
match &self.value {
Some(v) => v.to_raw(),
None => String::from("Invalid Duration"),
} }
// Compare two Duration instances }
pub fn is(a: Ref<Duration>, b: Ref<Duration>, args: Rest<()>) -> bool { /// Convert the object to JSON
a.value.is_some() && b.value.is_some() && a.value == b.value #[qjs(rename = "toJSON")]
} pub fn to_json(&self) -> String {
/// Convert the object to a string match &self.value {
pub fn toString(&self, args: Rest<()>) -> String { Some(v) => v.to_raw(),
match &self.value { None => String::from("Invalid Duration"),
Some(v) => v.to_raw(),
None => String::from("Invalid Duration"),
}
}
/// Convert the object to JSON
pub fn toJSON(&self, args: Rest<()>) -> String {
match &self.value {
Some(v) => v.to_raw(),
None => String::from("Invalid Duration"),
}
} }
} }
} }

View file

@ -1,13 +1,13 @@
use js::{Ctx, Result}; use js::{Class, Ctx, Result};
pub mod duration; pub mod duration;
pub mod record; pub mod record;
pub mod uuid; pub mod uuid;
pub fn init(ctx: Ctx<'_>) -> Result<()> { pub fn init(ctx: &Ctx<'_>) -> Result<()> {
let globals = ctx.globals(); let globals = ctx.globals();
globals.init_def::<duration::Duration>()?; Class::<duration::Duration>::define(&globals)?;
globals.init_def::<record::Record>()?; Class::<record::Record>::define(&globals)?;
globals.init_def::<uuid::Uuid>()?; Class::<uuid::Uuid>::define(&globals)?;
Ok(()) Ok(())
} }

View file

@ -1,56 +1,52 @@
#[js::bind(object, public)] use crate::sql::thing;
#[quickjs(bare)] use crate::sql::value::Value;
#[allow(non_snake_case)] use js::class::Trace;
#[allow(unused_variables)]
#[allow(clippy::module_inception)]
pub mod record {
use crate::sql::thing; #[derive(Clone, Trace)]
use crate::sql::value::Value; #[js::class]
use js::{class::Ref, function::Rest}; pub struct Record {
#[qjs(skip_trace)]
pub(crate) value: thing::Thing,
}
#[derive(Clone)] #[js::methods]
#[quickjs(cloneable)] impl Record {
pub struct Record { #[qjs(constructor)]
pub(crate) value: thing::Thing, pub fn new(tb: String, id: Value) -> Self {
Self {
value: thing::Thing {
tb,
id: match id {
Value::Array(v) => v.into(),
Value::Object(v) => v.into(),
Value::Number(v) => v.into(),
v => v.as_string().into(),
},
},
}
} }
impl Record { #[qjs(get)]
#[quickjs(constructor)] pub fn tb(&self) -> String {
pub fn new(tb: String, id: Value, args: Rest<()>) -> Self { self.value.tb.clone()
Self { }
value: thing::Thing {
tb,
id: match id {
Value::Array(v) => v.into(),
Value::Object(v) => v.into(),
Value::Number(v) => v.into(),
v => v.as_string().into(),
},
},
}
}
#[quickjs(get)] #[qjs(get)]
pub fn tb(&self) -> String { pub fn id(&self) -> String {
self.value.tb.clone() self.value.id.to_raw()
} }
// Compare two Record instances
#[quickjs(get)] pub fn is(a: &Record, b: &Record) -> bool {
pub fn id(&self) -> String { a.value == b.value
self.value.id.to_raw() }
} /// Convert the object to a string
// Compare two Record instances #[qjs(rename = "toString")]
pub fn is<'js>(a: Ref<'js, Record>, b: Ref<'js, Record>, args: Rest<()>) -> bool { pub fn js_to_string(&self) -> String {
a.value == b.value self.value.to_raw()
} }
/// Convert the object to a string /// Convert the object to JSON
pub fn toString(&self, args: Rest<()>) -> String { #[qjs(rename = "toJSON")]
self.value.to_raw() pub fn to_json(&self) -> String {
} self.value.to_raw()
/// Convert the object to JSON
pub fn toJSON(&self, args: Rest<()>) -> String {
self.value.to_raw()
}
} }
} }

View file

@ -1,51 +1,46 @@
#[js::bind(object, public)] use crate::sql::uuid;
#[quickjs(bare)] use js::class::Trace;
#[allow(non_snake_case)]
#[allow(unused_variables)]
#[allow(clippy::module_inception)]
pub mod uuid {
use crate::sql::uuid; #[derive(Clone, Trace)]
use crate::sql::value::Value; #[js::class]
use js::{class::Ref, function::Rest}; pub struct Uuid {
#[qjs(skip_trace)]
pub(crate) value: Option<uuid::Uuid>,
}
#[derive(Clone)] #[js::methods]
#[quickjs(cloneable)] impl Uuid {
pub struct Uuid { #[qjs(constructor)]
pub(crate) value: Option<uuid::Uuid>, pub fn new(value: String) -> Self {
Self {
value: uuid::Uuid::try_from(value).ok(),
}
} }
#[qjs(get)]
impl Uuid { pub fn value(&self) -> String {
#[quickjs(constructor)] match &self.value {
pub fn new(value: String, args: Rest<Value>) -> Self { Some(v) => v.to_raw(),
Self { None => String::from("Invalid Uuid"),
value: uuid::Uuid::try_from(value).ok(),
}
} }
#[quickjs(get)] }
pub fn value(&self) -> String { // Compare two Uuid instances
match &self.value { pub fn is(a: &Uuid, b: &Uuid) -> bool {
Some(v) => v.to_raw(), a.value.is_some() && b.value.is_some() && a.value == b.value
None => String::from("Invalid Uuid"), }
} /// Convert the object to a string
#[qjs(rename = "toString")]
pub fn js_to_string(&self) -> String {
match &self.value {
Some(v) => v.to_raw(),
None => String::from("Invalid Uuid"),
} }
// Compare two Uuid instances }
pub fn is<'js>(a: Ref<'js, Uuid>, b: Ref<'js, Uuid>, _args: Rest<()>) -> bool { /// Convert the object to JSON
a.value.is_some() && b.value.is_some() && a.value == b.value #[qjs(rename = "toJSON")]
} pub fn to_json(&self) -> String {
/// Convert the object to a string match &self.value {
pub fn toString(&self, _args: Rest<()>) -> String { Some(v) => v.to_raw(),
match &self.value { None => String::from("Invalid Uuid"),
Some(v) => v.to_raw(),
None => String::from("Invalid Uuid"),
}
}
/// Convert the object to JSON
pub fn toJSON(&self, _args: Rest<()>) -> String {
match &self.value {
Some(v) => v.to_raw(),
None => String::from("Invalid Uuid"),
}
} }
} }
} }

View file

@ -1,4 +1,4 @@
use crate::fnc::script::fetch::{classes::BlobClass, stream::ReadableStream, RequestError}; use crate::fnc::script::fetch::{stream::ReadableStream, RequestError};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use futures::{future, Stream, TryStreamExt}; use futures::{future, Stream, TryStreamExt};
use js::{ArrayBuffer, Class, Ctx, Error, Exception, FromJs, Result, Type, TypedArray, Value}; use js::{ArrayBuffer, Class, Ctx, Error, Exception, FromJs, Result, Type, TypedArray, Value};
@ -7,6 +7,8 @@ use std::{
result::Result as StdResult, result::Result as StdResult,
}; };
use super::classes::Blob;
pub type StreamItem = StdResult<Bytes, RequestError>; pub type StreamItem = StdResult<Bytes, RequestError>;
#[derive(Clone)] #[derive(Clone)]
@ -127,7 +129,7 @@ impl Body {
} }
impl<'js> FromJs<'js> for Body { impl<'js> FromJs<'js> for Body {
fn from_js(ctx: Ctx<'js>, value: Value<'js>) -> Result<Self> { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
let object = match value.type_of() { let object = match value.type_of() {
Type::String => { Type::String => {
let string = value.as_string().unwrap().to_string()?; let string = value.as_string().unwrap().to_string()?;
@ -142,7 +144,7 @@ impl<'js> FromJs<'js> for Body {
}) })
} }
}; };
if let Ok(x) = Class::<BlobClass>::from_object(object.clone()) { if let Some(x) = Class::<Blob>::from_object(object.clone()) {
let borrow = x.borrow(); let borrow = x.borrow();
return Ok(Body::buffer(BodyKind::Blob(borrow.mime.clone()), borrow.data.clone())); return Ok(Body::buffer(BodyKind::Blob(borrow.mime.clone()), borrow.data.clone()));
} }
@ -194,7 +196,7 @@ impl<'js> FromJs<'js> for Body {
.ok_or_else(|| Exception::throw_type(ctx, "Buffer is already detached"))?; .ok_or_else(|| Exception::throw_type(ctx, "Buffer is already detached"))?;
return Ok(Body::buffer(BodyKind::Buffer, Bytes::copy_from_slice(bytes))); return Ok(Body::buffer(BodyKind::Buffer, Bytes::copy_from_slice(bytes)));
} }
if let Ok(x) = ArrayBuffer::from_object(object.clone()) { if let Some(x) = ArrayBuffer::from_object(object.clone()) {
let bytes = x let bytes = x
.as_bytes() .as_bytes()
.ok_or_else(|| Exception::throw_type(ctx, "Buffer is already detached"))?; .ok_or_else(|| Exception::throw_type(ctx, "Buffer is already detached"))?;

View file

@ -1,9 +1,11 @@
//! Blob class implementation //! Blob class implementation
use bytes::BytesMut; use bytes::{Bytes, BytesMut};
use js::{bind, prelude::Coerced, ArrayBuffer, Class, Ctx, Exception, FromJs, Result, Value}; use js::{
class::Trace,
pub use blob::Blob as BlobClass; prelude::{Coerced, Opt},
ArrayBuffer, Class, Ctx, Exception, FromJs, Object, Result, Value,
};
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum EndingType { pub enum EndingType {
@ -12,7 +14,7 @@ pub enum EndingType {
} }
fn append_blob_part<'js>( fn append_blob_part<'js>(
ctx: Ctx<'js>, ctx: &Ctx<'js>,
value: Value<'js>, value: Value<'js>,
ending: EndingType, ending: EndingType,
data: &mut BytesMut, data: &mut BytesMut,
@ -23,11 +25,11 @@ fn append_blob_part<'js>(
const LINE_ENDING: &[u8] = b"\n"; const LINE_ENDING: &[u8] = b"\n";
if let Some(object) = value.as_object() { if let Some(object) = value.as_object() {
if let Ok(x) = Class::<BlobClass>::from_object(object.clone()) { if let Some(x) = Class::<Blob>::from_object(object.clone()) {
data.extend_from_slice(&x.borrow().data); data.extend_from_slice(&x.borrow().data);
return Ok(()); return Ok(());
} }
if let Ok(x) = ArrayBuffer::from_object(object.clone()) { if let Some(x) = ArrayBuffer::from_object(object.clone()) {
data.extend_from_slice(x.as_bytes().ok_or_else(|| { data.extend_from_slice(x.as_bytes().ok_or_else(|| {
Exception::throw_type(ctx, "Tried to construct blob with detached buffer") Exception::throw_type(ctx, "Tried to construct blob with detached buffer")
})?); })?);
@ -74,144 +76,121 @@ fn normalize_type(mut ty: String) -> String {
} }
} }
#[bind(object, public)] #[derive(Clone, Trace)]
#[quickjs(bare)] #[js::class]
#[allow(non_snake_case)] pub struct Blob {
#[allow(unused_variables)] pub(crate) mime: String,
#[allow(clippy::module_inception)] // TODO: make bytes?
mod blob { #[qjs(skip_trace)]
use super::*; pub(crate) data: Bytes,
}
use bytes::{Bytes, BytesMut}; #[js::methods]
use js::{ impl Blob {
function::{Opt, Rest}, // ------------------------------
ArrayBuffer, Ctx, Exception, Object, Result, Value, // Constructor
}; // ------------------------------
#[derive(Clone)] #[qjs(constructor)]
#[quickjs(cloneable)] pub fn new<'js>(
pub struct Blob { ctx: Ctx<'js>,
pub(crate) mime: String, parts: Opt<Value<'js>>,
// TODO: make bytes? options: Opt<Object<'js>>,
pub(crate) data: Bytes, ) -> Result<Self> {
let mut r#type = String::new();
let mut endings = EndingType::Transparent;
if let Some(obj) = options.into_inner() {
if let Some(x) = obj.get::<_, Option<Coerced<String>>>("type")? {
r#type = normalize_type(x.to_string());
}
if let Some(Coerced(x)) = obj.get::<_, Option<Coerced<String>>>("endings")? {
if x == "native" {
endings = EndingType::Native;
} else if x != "transparent" {
return Err(Exception::throw_type(
&ctx,
",expected endings to be either 'transparent' or 'native'",
));
}
}
}
let data = if let Some(parts) = parts.into_inner() {
let array = parts
.into_array()
.ok_or_else(|| Exception::throw_type(&ctx, "Blob parts are not a sequence"))?;
let mut buffer = BytesMut::new();
for elem in array.iter::<Value>() {
let elem = elem?;
append_blob_part(&ctx, elem, endings, &mut buffer)?;
}
buffer.freeze()
} else {
Bytes::new()
};
Ok(Self {
mime: r#type,
data,
})
} }
impl Blob { // ------------------------------
// ------------------------------ // Instance properties
// Constructor // ------------------------------
// ------------------------------
#[quickjs(constructor)] #[qjs(get)]
pub fn new<'js>( pub fn size(&self) -> usize {
ctx: Ctx<'js>, self.data.len()
parts: Opt<Value<'js>>, }
options: Opt<Object<'js>>,
_rest: Rest<()>,
) -> Result<Self> {
let mut r#type = String::new();
let mut endings = EndingType::Transparent;
if let Some(obj) = options.into_inner() { #[qjs(get, rename = "type")]
if let Some(x) = obj.get::<_, Option<Coerced<String>>>("type")? { pub fn r#type(&self) -> String {
r#type = normalize_type(x.to_string()); self.mime.clone()
} }
if let Some(Coerced(x)) = obj.get::<_, Option<Coerced<String>>>("endings")? {
if x == "native" {
endings = EndingType::Native;
} else if x != "transparent" {
return Err(Exception::throw_type(
ctx,
",expected endings to be either 'transparent' or 'native'",
));
}
}
}
let data = if let Some(parts) = parts.into_inner() { pub fn slice(&self, start: Opt<isize>, end: Opt<isize>, content_type: Opt<String>) -> Blob {
let array = parts // see https://w3c.github.io/FileAPI/#slice-blob
.into_array() let start = start.into_inner().unwrap_or_default();
.ok_or_else(|| Exception::throw_type(ctx, "Blob parts are not a sequence"))?; let start = if start < 0 {
(self.data.len() as isize + start).max(0) as usize
let mut buffer = BytesMut::new(); } else {
start as usize
for elem in array.iter::<Value>() { };
let elem = elem?; let end = end.into_inner().unwrap_or_default();
append_blob_part(ctx, elem, endings, &mut buffer)?; let end = if end < 0 {
} (self.data.len() as isize + end).max(0) as usize
buffer.freeze() } else {
} else { end as usize
Bytes::new() };
}; let data = self.data.slice(start..end);
Ok(Self { let content_type = content_type.into_inner().map(normalize_type).unwrap_or_default();
mime: r#type, Blob {
data, mime: content_type,
}) data,
} }
}
// ------------------------------ pub async fn text(&self) -> Result<String> {
// Instance properties let text = String::from_utf8(self.data.to_vec())?;
// ------------------------------ Ok(text)
}
#[quickjs(get)] #[qjs(rename = "arrayBuffer")]
pub fn size(&self) -> usize { pub async fn array_buffer<'js>(&self, ctx: Ctx<'js>) -> Result<ArrayBuffer<'js>> {
self.data.len() ArrayBuffer::new(ctx, self.data.to_vec())
} }
#[quickjs(get)] // ------------------------------
#[quickjs(rename = "type")] // Instance methods
pub fn r#type(&self) -> String { // ------------------------------
self.mime.clone()
}
pub fn slice( // Convert the object to a string
&self, #[qjs(rename = "toString")]
start: Opt<isize>, pub fn js_to_string(&self) -> String {
end: Opt<isize>, String::from("[object Blob]")
content_type: Opt<String>,
_rest: Rest<()>,
) -> Blob {
// see https://w3c.github.io/FileAPI/#slice-blob
let start = start.into_inner().unwrap_or_default();
let start = if start < 0 {
(self.data.len() as isize + start).max(0) as usize
} else {
start as usize
};
let end = end.into_inner().unwrap_or_default();
let end = if end < 0 {
(self.data.len() as isize + end).max(0) as usize
} else {
end as usize
};
let data = self.data.slice(start..end);
let content_type = content_type.into_inner().map(normalize_type).unwrap_or_default();
Blob {
mime: content_type,
data,
}
}
pub async fn text(&self, _rest: Rest<()>) -> Result<String> {
let text = String::from_utf8(self.data.to_vec())?;
Ok(text)
}
pub async fn arrayBuffer<'js>(
&self,
ctx: Ctx<'js>,
_rest: Rest<()>,
) -> Result<ArrayBuffer<'js>> {
ArrayBuffer::new(ctx, self.data.to_vec())
}
// ------------------------------
// Instance methods
// ------------------------------
// Convert the object to a string
pub fn toString(&self, _rest: Rest<()>) -> String {
String::from("[object Blob]")
}
} }
} }
@ -242,17 +221,17 @@ mod test {
blob = new Blob(["\n\r\n \n\r"],{endings: "transparent"}); blob = new Blob(["\n\r\n \n\r"],{endings: "transparent"});
assert.eq(blob.size,6) assert.eq(blob.size,6)
assert.eq(await blob.text(),"\n\r\n \n\r"); assert.eq(await blob.text(),"\n\r\n \n\r");
blob = new Blob(["\n\r\n \n\r"],{endings: "native"}); blob = new Blob(["\n\r\n \n\r"],{endings: "native"});
// \n \r\n and the \n from \n\r are converted. // \n \r\n and the \n from \n\r are converted.
// the part of the string which isn't converted is the space and the \r // the part of the string which isn't converted is the space and the \r
assert.eq(await blob.text(),`${NATIVE_LINE_ENDING}${NATIVE_LINE_ENDING} ${NATIVE_LINE_ENDING}\r`); assert.eq(await blob.text(),`${NATIVE_LINE_ENDING}${NATIVE_LINE_ENDING} ${NATIVE_LINE_ENDING}\r`);
assert.eq(blob.size,NATIVE_LINE_ENDING.length*3 + 2) assert.eq(blob.size,NATIVE_LINE_ENDING.length*3 + 2)
assert.mustThrow(() => new Blob("text")); assert.mustThrow(() => new Blob("text"));
assert.mustThrow(() => new Blob(["text"], {endings: "invalid value"})); assert.mustThrow(() => new Blob(["text"], {endings: "invalid value"}));
})() })()
"#).catch(ctx).unwrap().await.catch(ctx).unwrap(); "#).catch(&ctx).unwrap().await.catch(&ctx).unwrap();
}) })
.await .await
} }

View file

@ -1,34 +1,36 @@
//! FormData class implementation //! FormData class implementation
use js::{ use js::{
bind, function::Opt, prelude::Coerced, Class, Ctx, Exception, FromJs, Persistent, Result, class::{Class, Trace},
String, Value, function::{Opt, Rest},
prelude::Coerced,
Ctx, Exception, FromJs, Result, String, Value,
}; };
use std::string::String as StdString; use reqwest::multipart::{Form, Part};
use std::{collections::HashMap, string::String as StdString};
use crate::fnc::script::fetch::classes::BlobClass; use crate::fnc::script::fetch::classes::Blob;
#[derive(Clone)] #[derive(Clone)]
pub enum FormDataValue { pub enum FormDataValue<'js> {
String(Persistent<String<'static>>), String(String<'js>),
Blob { Blob {
data: Persistent<Class<'static, BlobClass>>, data: Class<'js, Blob>,
filename: Option<Persistent<String<'static>>>, filename: Option<String<'js>>,
}, },
} }
impl FormDataValue { impl<'js> FormDataValue<'js> {
fn from_arguments<'js>( fn from_arguments(
ctx: Ctx<'js>, ctx: &Ctx<'js>,
value: Value<'js>, value: Value<'js>,
filename: Opt<Coerced<String<'js>>>, filename: Opt<Coerced<String<'js>>>,
error: &'static str, error: &'static str,
) -> Result<FormDataValue> { ) -> Result<Self> {
if let Some(blob) = if let Some(blob) =
value.as_object().and_then(|value| Class::<BlobClass>::from_object(value.clone()).ok()) value.as_object().and_then(|value| Class::<Blob>::from_object(value.clone()))
{ {
let blob = Persistent::save(ctx, blob); let filename = filename.into_inner().map(|x| x.0);
let filename = filename.into_inner().map(|x| Persistent::save(ctx, x.0));
Ok(FormDataValue::Blob { Ok(FormDataValue::Blob {
data: blob, data: blob,
@ -38,128 +40,109 @@ impl FormDataValue {
return Err(Exception::throw_type(ctx, error)); return Err(Exception::throw_type(ctx, error));
} else { } else {
let value = Coerced::<String>::from_js(ctx, value)?; let value = Coerced::<String>::from_js(ctx, value)?;
let value = Persistent::save(ctx, value.0); Ok(FormDataValue::String(value.0))
Ok(FormDataValue::String(value))
} }
} }
} }
pub use form_data::FormData as FormDataClass; #[js::class]
#[bind(object, public)] #[derive(Clone, Trace)]
#[quickjs(bare)] pub struct FormData<'js> {
#[allow(non_snake_case)] #[qjs(skip_trace)]
#[allow(unused_variables)] pub(crate) values: HashMap<StdString, Vec<FormDataValue<'js>>>,
#[allow(clippy::module_inception)] }
pub mod form_data {
use super::*;
use std::{cell::RefCell, collections::HashMap};
use js::{ #[js::methods]
function::Opt, impl<'js> FormData<'js> {
prelude::{Coerced, Rest}, // ------------------------------
Ctx, Result, String, Value, // Constructor
}; // ------------------------------
use reqwest::multipart::{Form, Part};
#[derive(Clone)] // FormData spec states that FormDa takes two html elements as arguments
#[quickjs(cloneable)] // which does not make sense implementing fetch outside a browser.
pub struct FormData { // So we ignore those arguments.
pub(crate) values: RefCell<HashMap<StdString, Vec<FormDataValue>>>, #[qjs(constructor)]
pub fn new(ctx: Ctx<'js>, args: Rest<()>) -> Result<Self> {
if args.len() > 0 {
return Err(Exception::throw_internal(
&ctx,
"Cant call FormData with arguments as the dom elements required are not available",
));
}
Ok(FormData {
values: HashMap::new(),
})
} }
impl FormData { pub fn append(
// ------------------------------ &mut self,
// Constructor ctx: Ctx<'js>,
// ------------------------------ name: Coerced<StdString>,
value: Value<'js>,
filename: Opt<Coerced<String<'js>>>,
) -> Result<()> {
let value = FormDataValue::from_arguments(
&ctx,
value,
filename,
"Can't call `append` on `FormData` with a filename when value isn't of type `Blob`",
)?;
// FormData spec states that FormDa takes two html elements as arguments self.values.entry(name.0).or_insert_with(Vec::new).push(value);
// which does not make sense implementing fetch outside a browser.
// So we ignore those arguments.
#[quickjs(constructor)]
pub fn new(ctx: Ctx<'_>, args: Rest<()>) -> Result<FormData> {
if args.len() > 0 {
return Err(Exception::throw_internal(ctx,"Cant call FormData with arguments as the dom elements required are not available"));
}
Ok(FormData {
values: RefCell::new(HashMap::new()),
})
}
pub fn append<'js>( Ok(())
&self, }
ctx: Ctx<'js>,
name: Coerced<StdString>,
value: Value<'js>,
filename: Opt<Coerced<String<'js>>>,
) -> Result<()> {
let value = FormDataValue::from_arguments(
ctx,
value,
filename,
"Can't call `append` on `FormData` with a filename when value isn't of type `Blob`",
)?;
self.values.borrow_mut().entry(name.0).or_insert_with(Vec::new).push(value); pub fn set(
&mut self,
ctx: Ctx<'js>,
name: Coerced<StdString>,
value: Value<'js>,
filename: Opt<Coerced<String<'js>>>,
) -> Result<()> {
let value = FormDataValue::from_arguments(
&ctx,
value,
filename,
"Can't call `set` on `FormData` with a filename when value isn't of type `Blob`",
)?;
Ok(()) self.values.insert(name.0, vec![value]);
}
pub fn set<'js>( Ok(())
&self, }
ctx: Ctx<'js>,
name: Coerced<StdString>,
value: Value<'js>,
filename: Opt<Coerced<String<'js>>>,
) -> Result<()> {
let value = FormDataValue::from_arguments(
ctx,
value,
filename,
"Can't call `set` on `FormData` with a filename when value isn't of type `Blob`",
)?;
self.values.borrow_mut().insert(name.0, vec![value]); pub fn has(&self, name: Coerced<StdString>) -> bool {
self.values.contains_key(&name.0)
}
Ok(()) pub fn delete(&mut self, name: Coerced<StdString>) {
} self.values.remove(&name.0);
}
pub fn has(&self, ctx: Ctx<'_>, name: Coerced<StdString>) -> bool { #[qjs(skip)]
self.values.borrow().contains_key(&name.0) pub fn to_form(&self) -> Result<Form> {
} let mut res = Form::new();
for (k, v) in self.values.iter() {
pub fn delete(&self, ctx: Ctx<'_>, name: Coerced<StdString>) { for v in v {
self.values.borrow_mut().remove(&name.0); match v {
} FormDataValue::String(x) => {
res = res.text(k.clone(), x.to_string()?);
#[quickjs(skip)] }
pub fn to_form(&self, ctx: Ctx<'_>) -> Result<Form> { FormDataValue::Blob {
let lock = self.values.borrow(); data,
let mut res = Form::new(); filename,
for (k, v) in lock.iter() { } => {
for v in v { let mut part = Part::bytes(data.borrow().data.to_vec());
match v { if let Some(filename) = filename {
FormDataValue::String(x) => { let filename = filename.to_string()?;
let x = x.clone().restore(ctx).unwrap(); part = part.file_name(filename);
res = res.text(k.clone(), x.to_string()?);
}
FormDataValue::Blob {
data,
filename,
} => {
let mut part = Part::bytes(
data.clone().restore(ctx).unwrap().borrow().data.to_vec(),
);
if let Some(filename) = filename {
let filename =
filename.clone().restore(ctx).unwrap().to_string()?;
part = part.file_name(filename);
}
res = res.part(k.clone(), part);
} }
res = res.part(k.clone(), part);
} }
} }
} }
Ok(res)
} }
Ok(res)
} }
} }

View file

@ -1,272 +1,258 @@
//! Headers class implementation //! Headers class implementation
use js::bind; use std::str::FromStr;
pub use headers::Headers as HeadersClass; use js::{
class::Trace,
prelude::{Coerced, List},
Array, Ctx, Exception, Result, Value,
};
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
#[bind(object, public)] #[derive(Clone, Trace)]
#[quickjs(bare)] #[js::class]
#[allow(non_snake_case)] pub struct Headers {
#[allow(unused_variables)] #[qjs(skip_trace)]
#[allow(clippy::module_inception)] pub(crate) inner: HeaderMap,
mod headers { }
use std::{cell::RefCell, str::FromStr};
use js::{function::Rest, prelude::Coerced, Array, Ctx, Exception, Result, Value}; #[js::methods]
use reqwest::header::{HeaderMap, HeaderName, HeaderValue}; impl Headers {
// ------------------------------
// Constructor
// ------------------------------
#[derive(Clone)] #[qjs(constructor)]
#[quickjs(cloneable)] pub fn new<'js>(ctx: Ctx<'js>, init: Value<'js>) -> Result<Self> {
#[allow(dead_code)] Headers::new_inner(&ctx, init)
pub struct Headers {
pub(crate) inner: RefCell<HeaderMap>,
} }
impl Headers { // ------------------------------
// ------------------------------ // Instance methods
// Constructor // ------------------------------
// ------------------------------
#[quickjs(constructor)] // Convert the object to a string
pub fn new<'js>(ctx: Ctx<'js>, init: Value<'js>, args: Rest<()>) -> Result<Self> { #[qjs(rename = "toString")]
Headers::new_inner(ctx, init) pub fn js_to_string(&self) -> String {
} String::from("[object Header]")
// ------------------------------
// Instance methods
// ------------------------------
// Convert the object to a string
pub fn toString(&self, args: Rest<()>) -> String {
String::from("[object Header]")
}
// Adds or appends a new value to a header
pub fn append(&self, ctx: Ctx<'_>, key: String, val: String, args: Rest<()>) -> Result<()> {
self.append_inner(ctx, &key, &val)
}
// Deletes a header from the header set
pub fn delete(&self, ctx: Ctx<'_>, key: String, args: Rest<()>) -> Result<()> {
// Process and check the header name is valid
let key = HeaderName::from_str(&key)
.map_err(|e| Exception::throw_type(ctx, &format!("{e}")))?;
// Remove the header entry from the map
self.inner.borrow_mut().remove(&key);
// Everything ok
Ok(())
}
// Returns all header entries in the header set
pub fn entries(&self, args: Rest<()>) -> Vec<(String, String)> {
let lock = self.inner.borrow();
let mut res = Vec::<(String, String)>::with_capacity(lock.len());
for (k, v) in lock.iter() {
let k = k.as_str();
if Some(k) == res.last().map(|x| x.0.as_str()) {
let ent = res.last_mut().unwrap();
ent.1.push_str(", ");
// Header value came from a string, so it should also be able to be cast back
// to a string
ent.1.push_str(v.to_str().unwrap());
} else {
res.push((k.to_owned(), v.to_str().unwrap().to_owned()));
}
}
res
}
// Returns all values of a header in the header set
pub fn get(&self, ctx: Ctx<'_>, key: String, args: Rest<()>) -> Result<Option<String>> {
// Process and check the header name is valid
let key = HeaderName::from_str(&key)
.map_err(|e| Exception::throw_type(ctx, &format!("{e}")))?;
// Convert the header values to strings
let lock = self.inner.borrow();
let all = lock.get_all(&key);
// Header value came from a string, so it should also be able to be cast back
// to a string
let mut res = String::new();
for (idx, v) in all.iter().enumerate() {
if idx != 0 {
res.push_str(", ");
}
res.push_str(v.to_str().unwrap());
}
if res.is_empty() {
return Ok(None);
}
Ok(Some(res))
}
// Returns all values for the `Set-Cookie` header.
#[quickjs(rename = "getSetCookie")]
pub fn get_set_cookie(&self, args: Rest<()>) -> Vec<String> {
// This should always be a correct cookie;
let key = HeaderName::from_str("set-cookie").unwrap();
self.inner
.borrow()
.get_all(key)
.iter()
.map(|x| x.to_str().unwrap().to_owned())
.collect()
}
// Checks to see if the header set contains a header
pub fn has(&self, ctx: Ctx<'_>, key: String, args: Rest<()>) -> Result<bool> {
// Process and check the header name is valid
let key = HeaderName::from_str(&key)
.map_err(|e| Exception::throw_type(ctx, &format!("{e}")))?;
// Check if the header entry exists
Ok(self.inner.borrow().contains_key(&key))
}
// Returns all header keys contained in the header set
pub fn keys(&self, args: Rest<()>) -> Vec<String> {
// TODO: Incorrect, should return an iterator but iterators are not supported yet by quickjs
self.inner.borrow().keys().map(|v| v.as_str().to_owned()).collect::<Vec<String>>()
}
// Sets a new value or adds a header to the header set
pub fn set(&self, ctx: Ctx<'_>, key: String, val: String, args: Rest<()>) -> Result<()> {
// Process and check the header name is valid
let key = HeaderName::from_str(&key)
.map_err(|e| Exception::throw_type(ctx, &format!("Invalid header name: {e}")))?;
// Process and check the header name is valid
let val = HeaderValue::from_str(&val)
.map_err(|e| Exception::throw_type(ctx, &format!("Invalid header value: {e}")))?;
// Insert and overwrite the header entry
self.inner.borrow_mut().insert(key, val);
// Everything ok
Ok(())
}
// Returns all header values contained in the header set
pub fn values(&self, args: Rest<()>) -> Vec<String> {
let lock = self.inner.borrow();
let mut res = Vec::<String>::with_capacity(lock.len());
let mut pref = None;
for (k, v) in lock.iter() {
if Some(k) == pref {
let ent = res.last_mut().unwrap();
ent.push_str(", ");
ent.push_str(v.to_str().unwrap())
} else {
pref = Some(k);
res.push(v.to_str().unwrap().to_owned());
}
}
res
}
} }
#[quickjs(skip)] // Adds or appends a new value to a header
impl Headers { pub fn append(&mut self, ctx: Ctx<'_>, key: String, val: String) -> Result<()> {
pub fn from_map(map: HeaderMap) -> Self { self.append_inner(&ctx, &key, &val)
Self { }
inner: RefCell::new(map),
}
}
pub fn new_empty() -> Self { // Deletes a header from the header set
Self::from_map(HeaderMap::new()) pub fn delete(&mut self, ctx: Ctx<'_>, key: String) -> Result<()> {
} // Process and check the header name is valid
let key =
HeaderName::from_str(&key).map_err(|e| Exception::throw_type(&ctx, &format!("{e}")))?;
// Remove the header entry from the map
self.inner.remove(&key);
// Everything ok
Ok(())
}
pub fn new_inner<'js>(ctx: Ctx<'js>, val: Value<'js>) -> Result<Self> { // Returns all header entries in the header set
static INVALID_ERROR: &str = "Headers constructor: init was neither sequence<sequence<ByteString>> or record<ByteString, ByteString>"; pub fn entries(&self) -> Vec<List<(String, String)>> {
let res = Self::new_empty(); let mut res = Vec::<List<(String, String)>>::with_capacity(self.inner.len());
// TODO Set and Map, for (k, v) in self.inner.iter() {
if let Some(array) = val.as_array() { let k = k.as_str();
// a sequence<sequence<String>>; if Some(k) == res.last().map(|x| x.0 .0.as_str()) {
for v in array.iter::<Array>() { let ent = res.last_mut().unwrap();
let v = match v { ent.0 .1.push_str(", ");
Ok(x) => x, // Header value came from a string, so it should also be able to be cast back
Err(e) => { // to a string
if e.is_from_js() { ent.0 .1.push_str(v.to_str().unwrap());
return Err(Exception::throw_type(ctx, INVALID_ERROR));
}
return Err(e);
}
};
let key = match v.get::<Coerced<String>>(0) {
Ok(x) => x,
Err(e) => {
if e.is_from_js() {
return Err(Exception::throw_type(ctx, INVALID_ERROR));
}
return Err(e);
}
};
let value = match v.get::<Coerced<String>>(1) {
Ok(x) => x,
Err(e) => {
if e.is_from_js() {
return Err(Exception::throw_type(ctx, INVALID_ERROR));
}
return Err(e);
}
};
res.append_inner(ctx, &key, &value)?;
}
} else if let Some(obj) = val.as_object() {
// a record<String,String>;
for prop in obj.props::<String, Coerced<String>>() {
let (key, value) = match prop {
Ok(x) => x,
Err(e) => {
if e.is_from_js() {
return Err(Exception::throw_type(ctx, INVALID_ERROR));
}
return Err(e);
}
};
res.append_inner(ctx, &key, &value.0)?;
}
} else { } else {
return Err(Exception::throw_type(ctx, INVALID_ERROR)); res.push(List((k.to_owned(), v.to_str().unwrap().to_owned())));
} }
Ok(res)
} }
fn append_inner(&self, ctx: Ctx<'_>, key: &str, val: &str) -> Result<()> { res
// Unsure what to do exactly here. }
// Spec dictates normalizing string before adding it as a header value, i.e. removing
// any leading and trailing whitespace:
// [`https://fetch.spec.whatwg.org/#concept-header-value-normalize`]
// But non of the platforms I tested, normalize, instead they throw an error
// with `Invalid header value`. I'll chose to just do what the platforms do.
let key = match HeaderName::from_bytes(key.as_bytes()) { // Returns all values of a header in the header set
Ok(x) => x, pub fn get(&self, ctx: Ctx<'_>, key: String) -> Result<Option<String>> {
Err(e) => { // Process and check the header name is valid
return Err(Exception::throw_type( let key =
ctx, HeaderName::from_str(&key).map_err(|e| Exception::throw_type(&ctx, &format!("{e}")))?;
&format!("invalid header name `{key}`: {e}"), // Convert the header values to strings
)) let all = self.inner.get_all(&key);
}
};
let val = match HeaderValue::from_bytes(val.as_bytes()) {
Ok(x) => x,
Err(e) => {
return Err(Exception::throw_type(
ctx,
&format!("invalid header value `{val}`: {e}"),
))
}
};
self.inner.borrow_mut().append(key, val); // Header value came from a string, so it should also be able to be cast back
// to a string
Ok(()) let mut res = String::new();
for (idx, v) in all.iter().enumerate() {
if idx != 0 {
res.push_str(", ");
}
res.push_str(v.to_str().unwrap());
} }
if res.is_empty() {
return Ok(None);
}
Ok(Some(res))
}
// Returns all values for the `Set-Cookie` header.
#[qjs(rename = "getSetCookie")]
pub fn get_set_cookie(&self) -> Vec<String> {
// This should always be a correct cookie;
let key = HeaderName::from_str("set-cookie").unwrap();
self.inner.get_all(key).iter().map(|x| x.to_str().unwrap().to_owned()).collect()
}
// Checks to see if the header set contains a header
pub fn has(&self, ctx: Ctx<'_>, key: String) -> Result<bool> {
// Process and check the header name is valid
let key =
HeaderName::from_str(&key).map_err(|e| Exception::throw_type(&ctx, &format!("{e}")))?;
// Check if the header entry exists
Ok(self.inner.contains_key(&key))
}
// Returns all header keys contained in the header set
pub fn keys(&self) -> Vec<String> {
// TODO: Incorrect, should return an iterator but iterators are not supported yet by quickjs
self.inner.keys().map(|v| v.as_str().to_owned()).collect::<Vec<String>>()
}
// Sets a new value or adds a header to the header set
pub fn set(&mut self, ctx: Ctx<'_>, key: String, val: String) -> Result<()> {
// Process and check the header name is valid
let key = HeaderName::from_str(&key)
.map_err(|e| Exception::throw_type(&ctx, &format!("Invalid header name: {e}")))?;
// Process and check the header name is valid
let val = HeaderValue::from_str(&val)
.map_err(|e| Exception::throw_type(&ctx, &format!("Invalid header value: {e}")))?;
// Insert and overwrite the header entry
self.inner.insert(key, val);
// Everything ok
Ok(())
}
// Returns all header values contained in the header set
pub fn values(&self) -> Vec<String> {
let mut res = Vec::<String>::with_capacity(self.inner.len());
let mut pref = None;
for (k, v) in self.inner.iter() {
if Some(k) == pref {
let ent = res.last_mut().unwrap();
ent.push_str(", ");
ent.push_str(v.to_str().unwrap())
} else {
pref = Some(k);
res.push(v.to_str().unwrap().to_owned());
}
}
res
}
}
impl Headers {
pub fn from_map(map: HeaderMap) -> Self {
Self {
inner: map,
}
}
pub fn new_empty() -> Self {
Self::from_map(HeaderMap::new())
}
pub fn new_inner<'js>(ctx: &Ctx<'js>, val: Value<'js>) -> Result<Self> {
static INVALID_ERROR: &str = "Headers constructor: init was neither sequence<sequence<ByteString>> or record<ByteString, ByteString>";
let mut res = Self::new_empty();
// TODO Set and Map,
if let Some(array) = val.as_array() {
// a sequence<sequence<String>>;
for v in array.iter::<Array>() {
let v = match v {
Ok(x) => x,
Err(e) => {
if e.is_from_js() {
return Err(Exception::throw_type(ctx, INVALID_ERROR));
}
return Err(e);
}
};
let key = match v.get::<Coerced<String>>(0) {
Ok(x) => x,
Err(e) => {
if e.is_from_js() {
return Err(Exception::throw_type(ctx, INVALID_ERROR));
}
return Err(e);
}
};
let value = match v.get::<Coerced<String>>(1) {
Ok(x) => x,
Err(e) => {
if e.is_from_js() {
return Err(Exception::throw_type(ctx, INVALID_ERROR));
}
return Err(e);
}
};
res.append_inner(ctx, &key, &value)?;
}
} else if let Some(obj) = val.as_object() {
// a record<String,String>;
for prop in obj.props::<String, Coerced<String>>() {
let (key, value) = match prop {
Ok(x) => x,
Err(e) => {
if e.is_from_js() {
return Err(Exception::throw_type(ctx, INVALID_ERROR));
}
return Err(e);
}
};
res.append_inner(ctx, &key, &value.0)?;
}
} else {
return Err(Exception::throw_type(ctx, INVALID_ERROR));
}
Ok(res)
}
fn append_inner(&mut self, ctx: &Ctx<'_>, key: &str, val: &str) -> Result<()> {
// Unsure what to do exactly here.
// Spec dictates normalizing string before adding it as a header value, i.e. removing
// any leading and trailing whitespace:
// [`https://fetch.spec.whatwg.org/#concept-header-value-normalize`]
// But non of the platforms I tested, normalize, instead they throw an error
// with `Invalid header value`. I'll chose to just do what the platforms do.
let key = match HeaderName::from_bytes(key.as_bytes()) {
Ok(x) => x,
Err(e) => {
return Err(Exception::throw_type(
ctx,
&format!("invalid header name `{key}`: {e}"),
))
}
};
let val = match HeaderValue::from_bytes(val.as_bytes()) {
Ok(x) => x,
Err(e) => {
return Err(Exception::throw_type(
ctx,
&format!("invalid header value `{val}`: {e}"),
))
}
};
self.inner.append(key, val);
Ok(())
} }
} }
@ -327,7 +313,7 @@ mod test {
}); });
assert.seq(headers.get("f"), "g"); assert.seq(headers.get("f"), "g");
assert.seq(headers.get("h"), "j"); assert.seq(headers.get("h"), "j");
"#).catch(ctx).unwrap(); "#).catch(&ctx).unwrap();
}) })
.await .await
} }

View file

@ -1,18 +1,9 @@
//! Request class implementation //! Request class implementation
//!
use js::{ use js::{class::Trace, prelude::Coerced, Class, Ctx, Exception, FromJs, Object, Result, Value};
bind,
class::{HasRefs, RefsMarker},
prelude::Coerced,
Class, Ctx, Exception, FromJs, Object, Persistent, Result, Value,
};
use reqwest::Method; use reqwest::Method;
use crate::fnc::script::fetch::{ use crate::fnc::script::fetch::{body::Body, RequestError};
body::Body,
classes::{BlobClass, HeadersClass},
RequestError,
};
#[derive(Clone, Copy, Eq, PartialEq)] #[derive(Clone, Copy, Eq, PartialEq)]
pub enum RequestMode { pub enum RequestMode {
@ -23,7 +14,7 @@ pub enum RequestMode {
} }
impl<'js> FromJs<'js> for RequestMode { impl<'js> FromJs<'js> for RequestMode {
fn from_js(ctx: Ctx<'js>, value: Value<'js>) -> Result<Self> { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
let res = if let Some(Coerced(x)) = <Option<Coerced<String>>>::from_js(ctx, value)? { let res = if let Some(Coerced(x)) = <Option<Coerced<String>>>::from_js(ctx, value)? {
match x.as_str() { match x.as_str() {
"navigate" => RequestMode::Navigate, "navigate" => RequestMode::Navigate,
@ -59,7 +50,7 @@ pub enum RequestCredentials {
} }
impl<'js> FromJs<'js> for RequestCredentials { impl<'js> FromJs<'js> for RequestCredentials {
fn from_js(ctx: Ctx<'js>, value: Value<'js>) -> Result<Self> { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
let res = if let Some(Coerced(x)) = <Option<Coerced<String>>>::from_js(ctx, value)? { let res = if let Some(Coerced(x)) = <Option<Coerced<String>>>::from_js(ctx, value)? {
match x.as_str() { match x.as_str() {
"omit" => RequestCredentials::Omit, "omit" => RequestCredentials::Omit,
@ -96,7 +87,7 @@ pub enum RequestCache {
} }
impl<'js> FromJs<'js> for RequestCache { impl<'js> FromJs<'js> for RequestCache {
fn from_js(ctx: Ctx<'js>, value: Value<'js>) -> Result<Self> { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
let res = if let Some(Coerced(x)) = <Option<Coerced<String>>>::from_js(ctx, value)? { let res = if let Some(Coerced(x)) = <Option<Coerced<String>>>::from_js(ctx, value)? {
match x.as_str() { match x.as_str() {
"default" => RequestCache::Default, "default" => RequestCache::Default,
@ -136,7 +127,7 @@ pub enum RequestRedirect {
} }
impl<'js> FromJs<'js> for RequestRedirect { impl<'js> FromJs<'js> for RequestRedirect {
fn from_js(ctx: Ctx<'js>, value: Value<'js>) -> Result<Self> { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
let res = if let Some(Coerced(x)) = <Option<Coerced<String>>>::from_js(ctx, value)? { let res = if let Some(Coerced(x)) = <Option<Coerced<String>>>::from_js(ctx, value)? {
match x.as_str() { match x.as_str() {
"follow" => RequestRedirect::Follow, "follow" => RequestRedirect::Follow,
@ -176,7 +167,7 @@ pub enum ReferrerPolicy {
} }
impl<'js> FromJs<'js> for ReferrerPolicy { impl<'js> FromJs<'js> for ReferrerPolicy {
fn from_js(ctx: Ctx<'js>, value: Value<'js>) -> Result<Self> { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
let res = if let Some(Coerced(x)) = <Option<Coerced<String>>>::from_js(ctx, value)? { let res = if let Some(Coerced(x)) = <Option<Coerced<String>>>::from_js(ctx, value)? {
match x.as_str() { match x.as_str() {
"" => ReferrerPolicy::Empty, "" => ReferrerPolicy::Empty,
@ -213,9 +204,9 @@ impl<'js> FromJs<'js> for ReferrerPolicy {
} }
} }
pub struct RequestInit { pub struct RequestInit<'js> {
pub method: Method, pub method: Method,
pub headers: Persistent<Class<'static, HeadersClass>>, pub headers: Class<'js, Headers>,
pub body: Option<Body>, pub body: Option<Body>,
pub referrer: String, pub referrer: String,
pub referrer_policy: ReferrerPolicy, pub referrer_policy: ReferrerPolicy,
@ -227,15 +218,15 @@ pub struct RequestInit {
pub keep_alive: bool, pub keep_alive: bool,
} }
impl HasRefs for RequestInit { impl<'js> Trace<'js> for RequestInit<'js> {
fn mark_refs(&self, marker: &RefsMarker) { fn trace<'a>(&self, tracer: js::class::Tracer<'a, 'js>) {
self.headers.mark_refs(marker); self.headers.trace(tracer);
} }
} }
impl RequestInit { impl<'js> RequestInit<'js> {
pub fn default(ctx: Ctx<'_>) -> Result<Self> { pub fn default(ctx: Ctx<'js>) -> Result<Self> {
let headers = Persistent::save(ctx, Class::instance(ctx, HeadersClass::new_empty())?); let headers = Class::instance(ctx, Headers::new_empty())?;
Ok(RequestInit { Ok(RequestInit {
method: Method::GET, method: Method::GET,
headers, headers,
@ -251,9 +242,9 @@ impl RequestInit {
}) })
} }
pub fn clone_js(&self, ctx: Ctx<'_>) -> Result<Self> { pub fn clone_js(&self, ctx: Ctx<'js>) -> Result<Self> {
let headers = self.headers.clone().restore(ctx).unwrap(); let headers = self.headers.clone();
let headers = Persistent::save(ctx, Class::instance(ctx, headers.borrow().clone())?); let headers = Class::instance(ctx.clone(), headers.borrow().clone())?;
let body = self.body.as_ref().map(|x| x.clone_js(ctx)); let body = self.body.as_ref().map(|x| x.clone_js(ctx));
@ -274,7 +265,7 @@ impl RequestInit {
} }
// Normalize method string according to spec. // Normalize method string according to spec.
fn normalize_method(ctx: Ctx<'_>, m: String) -> Result<Method> { fn normalize_method(ctx: &Ctx<'_>, m: String) -> Result<Method> {
if m.as_bytes().eq_ignore_ascii_case(b"CONNECT") if m.as_bytes().eq_ignore_ascii_case(b"CONNECT")
|| m.as_bytes().eq_ignore_ascii_case(b"TRACE") || m.as_bytes().eq_ignore_ascii_case(b"TRACE")
|| m.as_bytes().eq_ignore_ascii_case(b"TRACK") || m.as_bytes().eq_ignore_ascii_case(b"TRACK")
@ -309,8 +300,8 @@ fn normalize_method(ctx: Ctx<'_>, m: String) -> Result<Method> {
} }
} }
impl<'js> FromJs<'js> for RequestInit { impl<'js> FromJs<'js> for RequestInit<'js> {
fn from_js(ctx: Ctx<'js>, value: Value<'js>) -> Result<Self> { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
let object = Object::from_js(ctx, value)?; let object = Object::from_js(ctx, value)?;
let referrer = object let referrer = object
@ -346,15 +337,14 @@ impl<'js> FromJs<'js> for RequestInit {
} }
let headers = if let Some(hdrs) = object.get::<_, Option<Object>>("headers")? { let headers = if let Some(hdrs) = object.get::<_, Option<Object>>("headers")? {
if let Ok(cls) = Class::<HeadersClass>::from_object(hdrs.clone()) { if let Some(cls) = Class::<Headers>::from_object(hdrs.clone()) {
cls cls
} else { } else {
Class::instance(ctx, HeadersClass::new_inner(ctx, hdrs.into_value())?)? Class::instance(ctx.clone(), Headers::new_inner(ctx, hdrs.into_value())?)?
} }
} else { } else {
Class::instance(ctx, HeadersClass::new_empty())? Class::instance(ctx.clone(), Headers::new_empty())?
}; };
let headers = Persistent::save(ctx, headers);
let body = object.get::<_, Option<Body>>("body")?; let body = object.get::<_, Option<Body>>("body")?;
@ -376,192 +366,173 @@ impl<'js> FromJs<'js> for RequestInit {
pub use request::Request as RequestClass; pub use request::Request as RequestClass;
#[bind(object, public)] pub use super::*;
#[quickjs(bare)]
#[allow(non_snake_case)]
#[allow(unused_variables)]
#[allow(clippy::module_inception)]
mod request {
pub use super::*; use bytes::Bytes;
use js::function::Opt;
// TODO: change implementation based on features.
use reqwest::{header::HeaderName, Url};
use bytes::Bytes; #[allow(dead_code)]
use js::{ #[js::class]
function::{Opt, Rest}, #[derive(Trace)]
Class, Ctx, Exception, HasRefs, Result, Value, pub struct Request<'js> {
}; #[qjs(skip_trace)]
// TODO: change implementation based on features. pub(crate) url: Url,
use reqwest::{header::HeaderName, Url}; pub(crate) init: RequestInit<'js>,
}
#[allow(dead_code)] #[js::methods]
#[derive(HasRefs)] impl<'js> Request<'js> {
#[quickjs(has_refs)] // ------------------------------
pub struct Request { // Constructor
pub(crate) url: Url, // ------------------------------
#[quickjs(has_refs)]
pub(crate) init: RequestInit, #[qjs(constructor)]
pub fn new(ctx: Ctx<'js>, input: Value<'js>, init: Opt<RequestInit<'js>>) -> Result<Self> {
if let Some(url) = input.as_string() {
// url string
let url_str = url.to_string()?;
let url = Url::parse(&url_str)
.map_err(|e| Exception::throw_type(&ctx, &format!("failed to parse url: {e}")))?;
if !url.username().is_empty() || !url.password().map(str::is_empty).unwrap_or(true) {
// url cannot contain non empty username and passwords
return Err(Exception::throw_type(&ctx, "Url contained credentials."));
}
let init = init.into_inner().map_or_else(|| RequestInit::default(ctx.clone()), Ok)?;
// HEAD and GET methods can't have a body
if init.body.is_some() && init.method == Method::GET || init.method == Method::HEAD {
return Err(Exception::throw_type(
&ctx,
&format!("Request with method `{}` cannot have a body", init.method),
));
}
Ok(Self {
url,
init,
})
} else if let Some(request) = input.into_object().and_then(Class::<Self>::from_object) {
// existing request object, just return it
request.try_borrow()?.clone_js(ctx.clone())
} else {
Err(Exception::throw_type(
&ctx,
"request `init` paramater must either be a request object or a string",
))
}
} }
impl Request { /// Clone the response, teeing any possible underlying streams.
// ------------------------------ #[qjs(rename = "clone")]
// Constructor pub fn clone_js(&self, ctx: Ctx<'js>) -> Result<Self> {
// ------------------------------ Ok(Self {
url: self.url.clone(),
init: self.init.clone_js(ctx)?,
})
}
#[quickjs(constructor)] // ------------------------------
pub fn new<'js>( // Instance properties
ctx: Ctx<'js>, // ------------------------------
input: Value<'js>, #[qjs(get, rename = "body_used")]
init: Opt<RequestInit>, pub fn body_used(&self) -> bool {
args: Rest<()>, self.init.body.as_ref().map(Body::used).unwrap_or(true)
) -> Result<Self> { }
if let Some(url) = input.as_string() {
// url string #[qjs(get)]
let url_str = url.to_string()?; pub fn method(&self) -> String {
let url = Url::parse(&url_str).map_err(|e| { self.init.method.to_string()
Exception::throw_type(ctx, &format!("failed to parse url: {e}")) }
})?;
if !url.username().is_empty() || !url.password().map(str::is_empty).unwrap_or(true) #[qjs(get)]
{ pub fn url(&self) -> String {
// url cannot contain non empty username and passwords self.url.to_string()
return Err(Exception::throw_type(ctx, "Url contained credentials.")); }
#[qjs(get)]
pub fn headers(&self) -> Class<'js, Headers> {
self.init.headers.clone()
}
#[qjs(get)]
pub fn referrer(&self) -> String {
self.init.referrer.clone()
}
// TODO
// ------------------------------
// Instance methods
// ------------------------------
// Convert the object to a string
#[qjs(rename = "toString")]
pub fn js_to_string(&self) -> String {
String::from("[object Request]")
}
/// Takes the buffer from the body leaving it used.
#[qjs(skip)]
async fn take_buffer(&self, ctx: &Ctx<'js>) -> Result<Bytes> {
let Some(body) = self.init.body.as_ref() else {
return Ok(Bytes::new());
};
match body.to_buffer().await {
Ok(Some(x)) => Ok(x),
Ok(None) => Err(Exception::throw_type(ctx, "Body unusable")),
Err(e) => match e {
RequestError::Reqwest(e) => {
Err(Exception::throw_type(ctx, &format!("stream failed: {e}")))
} }
let init = init.into_inner().map_or_else(|| RequestInit::default(ctx), Ok)?; },
// HEAD and GET methods can't have a body
if init.body.is_some() && init.method == Method::GET || init.method == Method::HEAD
{
return Err(Exception::throw_type(
ctx,
&format!("Request with method `{}` cannot have a body", init.method),
));
}
Ok(Self {
url,
init,
})
} else if let Some(request) = input
.into_object()
.and_then(|obj| Class::<Self>::from_object(obj).ok().map(|x| x.borrow()))
{
// existing request object, just return it
request.clone_js(ctx, Default::default())
} else {
Err(Exception::throw_type(
ctx,
"request `init` paramater must either be a request object or a string",
))
}
} }
}
/// Clone the response, teeing any possible underlying streams. // Returns a promise with the request body as a Blob
#[quickjs(rename = "clone")] pub async fn blob(&self, ctx: Ctx<'js>) -> Result<Blob> {
pub fn clone_js(&self, ctx: Ctx<'_>, _rest: Rest<()>) -> Result<Self> { let headers = self.init.headers.clone();
Ok(Self { let mime = {
url: self.url.clone(), let headers = headers.borrow();
init: self.init.clone_js(ctx)?, let headers = &headers.inner;
}) let key = HeaderName::from_static("content-type");
} let types = headers.get_all(key);
// TODO: This is not according to spec.
types
.iter()
.next()
.map(|x| x.to_str().unwrap_or("text/html"))
.unwrap_or("text/html")
.to_owned()
};
// ------------------------------ let data = self.take_buffer(&ctx).await?;
// Instance properties Ok(Blob {
// ------------------------------ mime,
#[quickjs(get)] data,
pub fn bodyUsed(&self) -> bool { })
self.init.body.as_ref().map(Body::used).unwrap_or(true) }
}
#[quickjs(get)] // Returns a promise with the request body as FormData
pub fn method(&self) -> String { #[qjs(rename = "formData")]
self.init.method.to_string() pub async fn form_data(&self, ctx: Ctx<'js>) -> Result<Value<'js>> {
} Err(Exception::throw_internal(&ctx, "Not yet implemented"))
}
#[quickjs(get)] // Returns a promise with the request body as JSON
pub fn url(&self) -> String { pub async fn json(&self, ctx: Ctx<'js>) -> Result<Value<'js>> {
self.url.to_string() let text = self.text(ctx.clone()).await?;
} ctx.json_parse(text)
}
#[quickjs(get)] // Returns a promise with the request body as text
pub fn headers<'js>(&self, ctx: Ctx<'js>) -> Class<'js, HeadersClass> { pub async fn text(&self, ctx: Ctx<'js>) -> Result<String> {
self.init.headers.clone().restore(ctx).unwrap() let data = self.take_buffer(&ctx).await?;
}
#[quickjs(get)] // Skip UTF-BOM
pub fn referrer(&self, ctx: Ctx<'_>) -> String { if data.starts_with(&[0xEF, 0xBB, 0xBF]) {
self.init.referrer.clone() Ok(String::from_utf8_lossy(&data[3..]).into_owned())
} } else {
// TODO Ok(String::from_utf8_lossy(&data).into_owned())
// ------------------------------
// Instance methods
// ------------------------------
// Convert the object to a string
pub fn toString(&self) -> String {
String::from("[object Request]")
}
/// Takes the buffer from the body leaving it used.
#[quickjs(skip)]
async fn take_buffer<'js>(&self, ctx: Ctx<'js>) -> Result<Bytes> {
let Some(body) = self.init.body.as_ref() else {
return Ok(Bytes::new())
};
match body.to_buffer().await {
Ok(Some(x)) => Ok(x),
Ok(None) => Err(Exception::throw_type(ctx, "Body unusable")),
Err(e) => match e {
RequestError::Reqwest(e) => {
Err(Exception::throw_type(ctx, &format!("stream failed: {e}")))
}
},
}
}
// Returns a promise with the request body as a Blob
pub async fn blob(&self, ctx: Ctx<'_>, args: Rest<()>) -> Result<BlobClass> {
let headers = self.init.headers.clone().restore(ctx).unwrap();
let mime = {
let headers = headers.borrow();
let headers = headers.inner.borrow();
let key = HeaderName::from_static("content-type");
let types = headers.get_all(key);
// TODO: This is not according to spec.
types
.iter()
.next()
.map(|x| x.to_str().unwrap_or("text/html"))
.unwrap_or("text/html")
.to_owned()
};
let data = self.take_buffer(ctx).await?;
Ok(BlobClass {
mime,
data,
})
}
// Returns a promise with the request body as FormData
pub async fn formData<'js>(&self, ctx: Ctx<'js>, args: Rest<()>) -> Result<Value<'js>> {
Err(Exception::throw_internal(ctx, "Not yet implemented"))
}
// Returns a promise with the request body as JSON
pub async fn json<'js>(&self, ctx: Ctx<'js>, args: Rest<()>) -> Result<Value<'js>> {
let text = self.text(ctx, args).await?;
ctx.json_parse(text)
}
// Returns a promise with the request body as text
pub async fn text<'js>(&self, ctx: Ctx<'js>, args: Rest<()>) -> Result<String> {
let data = self.take_buffer(ctx).await?;
// Skip UTF-BOM
if data.starts_with(&[0xEF, 0xBB, 0xBF]) {
Ok(String::from_utf8_lossy(&data[3..]).into_owned())
} else {
Ok(String::from_utf8_lossy(&data).into_owned())
}
} }
} }
} }
@ -644,7 +615,7 @@ mod test {
assert.seq(await req_2.text(),"some text"); assert.seq(await req_2.text(),"some text");
})() })()
"#).catch(ctx).unwrap().await.catch(ctx).unwrap(); "#).catch(&ctx).unwrap().await.catch(&ctx).unwrap();
}) })
.await; .await;
} }

View file

@ -1,34 +1,33 @@
use std::string::String as StdString; use std::string::String as StdString;
use js::{ use js::{
class::{HasRefs, RefsMarker}, class::{Trace, Tracer},
prelude::*, prelude::*,
Class, Ctx, Exception, FromJs, Object, Persistent, Result, Value, Class, Ctx, Exception, FromJs, Object, Result, Value,
}; };
use crate::fnc::script::fetch::{classes::HeadersClass, util}; use crate::fnc::script::fetch::{classes::Headers, util};
/// Struct containing data from the init argument from the Response constructor. /// Struct containing data from the init argument from the Response constructor.
#[derive(Clone)] #[derive(Clone)]
pub struct ResponseInit { pub struct ResponseInit<'js> {
// u16 instead of reqwest::StatusCode since javascript allows non valid status codes in some // u16 instead of reqwest::StatusCode since javascript allows non valid status codes in some
// circumstances. // circumstances.
pub status: u16, pub status: u16,
pub status_text: StdString, pub status_text: StdString,
pub headers: Persistent<Class<'static, HeadersClass>>, pub headers: Class<'js, Headers>,
} }
impl HasRefs for ResponseInit { impl<'js> Trace<'js> for ResponseInit<'js> {
fn mark_refs(&self, marker: &RefsMarker) { fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
self.headers.mark_refs(marker); self.headers.trace(tracer);
} }
} }
impl ResponseInit { impl<'js> ResponseInit<'js> {
/// Returns a ResponseInit object with all values as the default value. /// Returns a ResponseInit object with all values as the default value.
pub fn default(ctx: Ctx<'_>) -> Result<ResponseInit> { pub fn default(ctx: Ctx<'js>) -> Result<Self> {
let headers = Class::instance(ctx, HeadersClass::new_empty())?; let headers = Class::instance(ctx, Headers::new_empty())?;
let headers = Persistent::save(ctx, headers);
Ok(ResponseInit { Ok(ResponseInit {
status: 200, status: 200,
status_text: StdString::new(), status_text: StdString::new(),
@ -37,8 +36,8 @@ impl ResponseInit {
} }
} }
impl<'js> FromJs<'js> for ResponseInit { impl<'js> FromJs<'js> for ResponseInit<'js> {
fn from_js(ctx: Ctx<'js>, value: Value<'js>) -> Result<Self> { fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
let object = Object::from_js(ctx, value)?; let object = Object::from_js(ctx, value)?;
// Extract status. // Extract status.
@ -66,12 +65,11 @@ impl<'js> FromJs<'js> for ResponseInit {
// Extract headers. // Extract headers.
let headers = if let Some(headers) = object.get::<_, Option<Value>>("headers")? { let headers = if let Some(headers) = object.get::<_, Option<Value>>("headers")? {
let headers = HeadersClass::new_inner(ctx, headers)?; let headers = Headers::new_inner(ctx, headers)?;
Class::instance(ctx, headers)? Class::instance(ctx.clone(), headers)?
} else { } else {
Class::instance(ctx, HeadersClass::new_empty())? Class::instance(ctx.clone(), Headers::new_empty())?
}; };
let headers = Persistent::save(ctx, headers);
Ok(ResponseInit { Ok(ResponseInit {
status, status,

View file

@ -1,11 +1,10 @@
//! Response class implementation //! Response class implementation
use js::bind;
mod init; mod init;
use bytes::Bytes;
pub use init::ResponseInit; pub use init::ResponseInit;
pub use response::Response as ResponseClass; use js::{class::Trace, prelude::Opt, ArrayBuffer, Class, Ctx, Exception, Result, Value};
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@ -18,304 +17,284 @@ pub enum ResponseType {
OpaqueRedirect, OpaqueRedirect,
} }
#[bind(object, public)] use reqwest::Url;
#[quickjs(bare)]
#[allow(non_snake_case)]
#[allow(unused_variables)]
#[allow(clippy::module_inception)]
pub mod response {
use crate::fnc::script::fetch::{ use crate::fnc::script::fetch::{
body::{Body, BodyKind}, body::{Body, BodyKind},
classes::{BlobClass, HeadersClass}, util, RequestError,
util, RequestError, };
};
use super::{ResponseInit, ResponseType}; use super::{Blob, Headers};
use bytes::Bytes;
use js::{
function::{Opt, Rest},
ArrayBuffer, Class, Ctx, Exception, HasRefs, Persistent, Result, Value,
};
use reqwest::Url;
#[derive(HasRefs)] #[allow(dead_code)]
#[allow(dead_code)] #[derive(Trace)]
#[quickjs(has_refs)] #[js::class]
pub struct Response { pub struct Response<'js> {
#[quickjs(has_refs)] #[qjs(skip_trace)]
pub(crate) body: Body, pub(crate) body: Body,
#[quickjs(has_refs)] pub(crate) init: ResponseInit<'js>,
pub(crate) init: ResponseInit, #[qjs(skip_trace)]
pub(crate) url: Option<Url>, pub(crate) url: Option<Url>,
pub(crate) r#type: ResponseType, #[qjs(skip_trace)]
pub(crate) was_redirected: bool, pub(crate) r#type: ResponseType,
pub(crate) was_redirected: bool,
}
#[js::methods]
impl<'js> Response<'js> {
// ------------------------------
// Constructor
// ------------------------------
#[qjs(constructor)]
pub fn new(
ctx: Ctx<'js>,
body: Opt<Option<Body>>,
init: Opt<ResponseInit<'js>>,
) -> Result<Self> {
let init = match init.into_inner() {
Some(x) => x,
None => ResponseInit::default(ctx.clone())?,
};
let body = body.into_inner().and_then(|x| x);
if body.is_some() && util::is_null_body_status(init.status) {
// Null body statuses are not allowed to have a body.
return Err(Exception::throw_type(
&ctx,
&format!("Response with status `{}` is not allowed to have a body", init.status),
));
}
let body = body.unwrap_or_default();
Ok(Response {
body,
init,
url: None,
r#type: ResponseType::Default,
was_redirected: false,
})
} }
impl Response { // ------------------------------
// ------------------------------ // Instance properties
// Constructor // ------------------------------
// ------------------------------
#[quickjs(constructor)] #[qjs(get, rename = "bodyUsed")]
pub fn new( pub fn body_used(&self) -> bool {
ctx: Ctx<'_>, self.body.used()
body: Opt<Option<Body>>, }
init: Opt<ResponseInit>,
args: Rest<()>, #[qjs(get)]
) -> Result<Self> { pub fn status(&self) -> u16 {
let init = match init.into_inner() { self.init.status
Some(x) => x, }
None => ResponseInit::default(ctx)?,
}; #[qjs(get)]
let body = body.into_inner().and_then(|x| x); pub fn ok(&self) -> bool {
if body.is_some() && util::is_null_body_status(init.status) { util::is_ok_status(self.init.status)
// Null body statuses are not allowed to have a body. }
return Err(Exception::throw_type(
ctx, #[qjs(get)]
&format!( pub fn redirected(&self) -> bool {
"Response with status `{}` is not allowed to have a body", self.was_redirected
init.status }
),
)); #[qjs(get, rename = "statusText")]
pub fn status_text(&self) -> String {
self.init.status_text.clone()
}
#[qjs(get, rename = "type")]
pub fn r#type(&self) -> &'static str {
match self.r#type {
ResponseType::Basic => "basic",
ResponseType::Cors => "cors",
ResponseType::Default => "default",
ResponseType::Error => "error",
ResponseType::Opaque => "opaque",
ResponseType::OpaqueRedirect => "opaqueredirect",
}
}
#[qjs(get)]
pub fn headers(&self) -> Class<'js, Headers> {
self.init.headers.clone()
}
#[qjs(get)]
pub fn url(&self) -> Option<String> {
self.url.as_ref().map(|x| {
if x.fragment().is_some() {
let mut res = x.clone();
res.set_fragment(None);
res.to_string()
} else {
x.to_string()
} }
let body = body.unwrap_or_default(); })
}
Ok(Response { // ------------------------------
body, // Instance methods
init, // ------------------------------
url: None,
r#type: ResponseType::Default, // Convert the object to a string
was_redirected: false, #[qjs(rename = "toString")]
}) pub fn js_to_string(&self) -> String {
String::from("[object Response]")
}
// Creates a copy of the request object
#[qjs(rename = "clone")]
pub fn clone_js(&self, ctx: Ctx<'js>) -> Self {
Response {
body: self.body.clone_js(ctx),
init: self.init.clone(),
url: self.url.clone(),
r#type: self.r#type,
was_redirected: self.was_redirected,
} }
}
// ------------------------------ #[qjs(skip)]
// Instance properties async fn take_buffer(&self, ctx: &Ctx<'js>) -> Result<Bytes> {
// ------------------------------ match self.body.to_buffer().await {
Ok(Some(x)) => Ok(x),
#[quickjs(get)] Ok(None) => Err(Exception::throw_type(ctx, "Body unusable")),
pub fn bodyUsed(&self) -> bool { Err(e) => match e {
self.body.used() RequestError::Reqwest(e) => {
} Err(Exception::throw_type(ctx, &format!("stream failed: {e}")))
#[quickjs(get)]
pub fn status(&self) -> u16 {
self.init.status
}
#[quickjs(get)]
pub fn ok(&self) -> bool {
util::is_ok_status(self.init.status)
}
#[quickjs(get)]
pub fn redirected(&self) -> bool {
self.was_redirected
}
#[quickjs(get)]
pub fn statusText(&self) -> String {
self.init.status_text.clone()
}
#[quickjs(get)]
pub fn r#type(&self) -> &'static str {
match self.r#type {
ResponseType::Basic => "basic",
ResponseType::Cors => "cors",
ResponseType::Default => "default",
ResponseType::Error => "error",
ResponseType::Opaque => "opaque",
ResponseType::OpaqueRedirect => "opaqueredirect",
}
}
#[quickjs(get)]
pub fn headers<'js>(&self, ctx: Ctx<'js>) -> Class<'js, HeadersClass> {
self.init.headers.clone().restore(ctx).unwrap()
}
#[quickjs(get)]
pub fn url(&self) -> Option<String> {
self.url.as_ref().map(|x| {
if x.fragment().is_some() {
let mut res = x.clone();
res.set_fragment(None);
res.to_string()
} else {
x.to_string()
} }
}) },
}
}
// Returns a promise with the response body as a Blob
pub async fn blob(&self, ctx: Ctx<'js>) -> Result<Blob> {
let headers = self.init.headers.clone();
let mime = {
let headers = headers.borrow();
let headers = &headers.inner;
let types = headers.get_all(reqwest::header::CONTENT_TYPE);
// TODO: This is not according to spec.
types
.iter()
.next()
.map(|x| x.to_str().unwrap_or("text/html"))
.unwrap_or("text/html")
.to_owned()
};
let data = self.take_buffer(&ctx).await?;
Ok(Blob {
mime,
data,
})
}
// Returns a promise with the response body as FormData
#[qjs(rename = "formData")]
pub async fn form_data(&self, ctx: Ctx<'js>) -> Result<Value<'js>> {
Err(Exception::throw_internal(&ctx, "Not yet implemented"))
}
// Returns a promise with the response body as JSON
pub async fn json(&self, ctx: Ctx<'js>) -> Result<Value<'js>> {
let text = self.text(ctx.clone()).await?;
ctx.json_parse(text)
}
// Returns a promise with the response body as text
pub async fn text(&self, ctx: Ctx<'js>) -> Result<String> {
let data = self.take_buffer(&ctx).await?;
// Skip UTF-BOM
if data.starts_with(&[0xEF, 0xBB, 0xBF]) {
Ok(String::from_utf8_lossy(&data[3..]).into_owned())
} else {
Ok(String::from_utf8_lossy(&data).into_owned())
}
}
// Returns a promise with the response body as text
#[qjs(rename = "arrayBuffer")]
pub async fn array_buffer(&self, ctx: Ctx<'js>) -> Result<ArrayBuffer<'js>> {
let data = self.take_buffer(&ctx).await?;
ArrayBuffer::new(ctx, data)
}
// ------------------------------
// Static methods
// ------------------------------
#[qjs(r#static, rename = "json")]
pub fn static_json(
ctx: Ctx<'js>,
data: Value<'js>,
init: Opt<ResponseInit<'js>>,
) -> Result<Self> {
let json = ctx.json_stringify(data)?;
let json =
json.ok_or_else(|| Exception::throw_type(&ctx, "Value is not JSON serializable"))?;
let json = json.to_string()?;
let init = if let Some(init) = init.into_inner() {
init
} else {
ResponseInit::default(ctx)?
};
Ok(Response {
url: None,
body: Body::buffer(BodyKind::Buffer, json),
init,
r#type: ResponseType::Default,
was_redirected: false,
})
}
// Returns a new response representing a network error
#[qjs(r#static)]
pub fn error(ctx: Ctx<'js>) -> Result<Self> {
let headers = Class::instance(ctx, Headers::new_empty())?;
Ok(Response {
url: None,
body: Body::new(),
init: ResponseInit {
status: 0,
status_text: String::new(),
headers,
},
r#type: ResponseType::Error,
was_redirected: false,
})
}
// Creates a new response with a different URL
#[qjs(r#static)]
pub fn redirect(ctx: Ctx<'_>, url: String, status: Opt<u32>) -> Result<Response> {
let url = url
.parse::<Url>()
.map_err(|e| Exception::throw_type(&ctx, &format!("Invalid url: {e}")))?;
let status = status.into_inner().unwrap_or(302) as u16;
if !util::is_redirect_status(status) {
return Err(Exception::throw_range(&ctx, "Status code is not a redirect status"));
} }
// ------------------------------ let headers = Class::instance(ctx, Headers::new_empty())?;
// Instance methods
// ------------------------------
// Convert the object to a string Ok(Response {
pub fn toString(&self, args: Rest<()>) -> String { url: Some(url),
String::from("[object Response]") body: Body::new(),
} init: ResponseInit {
status,
// Creates a copy of the request object status_text: String::new(),
#[quickjs(rename = "clone")] headers,
pub fn clone_js(&self, ctx: Ctx<'_>, args: Rest<()>) -> Response { },
Response { r#type: ResponseType::Default,
body: self.body.clone_js(ctx), was_redirected: false,
init: self.init.clone(), })
url: self.url.clone(),
r#type: self.r#type,
was_redirected: self.was_redirected,
}
}
#[quickjs(skip)]
async fn take_buffer<'js>(&self, ctx: Ctx<'js>) -> Result<Bytes> {
match self.body.to_buffer().await {
Ok(Some(x)) => Ok(x),
Ok(None) => Err(Exception::throw_type(ctx, "Body unusable")),
Err(e) => match e {
RequestError::Reqwest(e) => {
Err(Exception::throw_type(ctx, &format!("stream failed: {e}")))
}
},
}
}
// Returns a promise with the response body as a Blob
pub async fn blob<'js>(&self, ctx: Ctx<'js>, args: Rest<()>) -> Result<BlobClass> {
let headers = self.init.headers.clone().restore(ctx).unwrap();
let mime = {
let headers = headers.borrow();
let headers = headers.inner.borrow();
let types = headers.get_all(reqwest::header::CONTENT_TYPE);
// TODO: This is not according to spec.
types
.iter()
.next()
.map(|x| x.to_str().unwrap_or("text/html"))
.unwrap_or("text/html")
.to_owned()
};
let data = self.take_buffer(ctx).await?;
Ok(BlobClass {
mime,
data,
})
}
// Returns a promise with the response body as FormData
pub async fn formData<'js>(&self, ctx: Ctx<'js>, args: Rest<()>) -> Result<Value<'js>> {
Err(Exception::throw_internal(ctx, "Not yet implemented"))
}
// Returns a promise with the response body as JSON
pub async fn json<'js>(&self, ctx: Ctx<'js>, args: Rest<()>) -> Result<Value<'js>> {
let text = self.text(ctx, args).await?;
ctx.json_parse(text)
}
// Returns a promise with the response body as text
pub async fn text<'js>(&self, ctx: Ctx<'js>, args: Rest<()>) -> Result<String> {
let data = self.take_buffer(ctx).await?;
// Skip UTF-BOM
if data.starts_with(&[0xEF, 0xBB, 0xBF]) {
Ok(String::from_utf8_lossy(&data[3..]).into_owned())
} else {
Ok(String::from_utf8_lossy(&data).into_owned())
}
}
// Returns a promise with the response body as text
pub async fn arrayBuffer<'js>(
&self,
ctx: Ctx<'js>,
args: Rest<()>,
) -> Result<ArrayBuffer<'js>> {
let data = self.take_buffer(ctx).await?;
ArrayBuffer::new(ctx, data)
}
// ------------------------------
// Static methods
// ------------------------------
#[quickjs(rename = "json")]
pub fn static_json<'js>(
ctx: Ctx<'js>,
data: Value<'js>,
init: Opt<ResponseInit>,
args: Rest<()>,
) -> Result<Self> {
let json = ctx.json_stringify(data)?;
let json =
json.ok_or_else(|| Exception::throw_type(ctx, "Value is not JSON serializable"))?;
let json = json.to_string()?;
let init = if let Some(init) = init.into_inner() {
init
} else {
ResponseInit::default(ctx)?
};
Ok(Response {
url: None,
body: Body::buffer(BodyKind::Buffer, json),
init,
r#type: ResponseType::Default,
was_redirected: false,
})
}
// Returns a new response representing a network error
pub fn error(ctx: Ctx<'_>, args: Rest<()>) -> Result<Self> {
let headers = Persistent::save(ctx, Class::instance(ctx, HeadersClass::new_empty())?);
Ok(Response {
url: None,
body: Body::new(),
init: ResponseInit {
status: 0,
status_text: String::new(),
headers,
},
r#type: ResponseType::Error,
was_redirected: false,
})
}
// Creates a new response with a different URL
pub fn redirect(
ctx: Ctx<'_>,
url: String,
status: Opt<u32>,
args: Rest<()>,
) -> Result<Response> {
let url = url
.parse::<Url>()
.map_err(|e| Exception::throw_type(ctx, &format!("Invalid url: {e}")))?;
let status = status.into_inner().unwrap_or(302) as u16;
if !util::is_redirect_status(status) {
return Err(Exception::throw_range(ctx, "Status code is not a redirect status"));
}
let headers = Persistent::save(ctx, Class::instance(ctx, HeadersClass::new_empty())?);
Ok(Response {
url: Some(url),
body: Body::new(),
init: ResponseInit {
status,
status_text: String::new(),
headers,
},
r#type: ResponseType::Default,
was_redirected: false,
})
}
} }
} }
@ -388,7 +367,7 @@ mod test {
})() })()
"#).catch(ctx).unwrap().await.catch(ctx).unwrap(); "#).catch(&ctx).unwrap().await.catch(&ctx).unwrap();
}) })
.await; .await;
} }

View file

@ -2,29 +2,28 @@
use crate::fnc::script::fetch::{ use crate::fnc::script::fetch::{
body::{Body, BodyData, BodyKind}, body::{Body, BodyData, BodyKind},
classes::{ classes::{self, Request, RequestInit, Response, ResponseInit, ResponseType},
self, HeadersClass, RequestClass, RequestInit, ResponseClass, ResponseInit, ResponseType,
},
RequestError, RequestError,
}; };
use futures::TryStreamExt; use futures::TryStreamExt;
use js::{bind, function::Opt, prelude::*, Class, Ctx, Exception, Persistent, Result, Value}; use js::{function::Opt, Class, Ctx, Exception, Result, Value};
use reqwest::{ use reqwest::{
header::{HeaderValue, CONTENT_TYPE}, header::{HeaderValue, CONTENT_TYPE},
redirect, Body as ReqBody, redirect, Body as ReqBody,
}; };
use std::sync::Arc; use std::sync::Arc;
#[bind(object, public)] use super::classes::Headers;
#[js::function]
#[allow(unused_variables)] #[allow(unused_variables)]
pub async fn fetch<'js>( pub async fn fetch<'js>(
ctx: Ctx<'js>, ctx: Ctx<'js>,
input: Value<'js>, input: Value<'js>,
init: Opt<RequestInit>, init: Opt<RequestInit<'js>>,
args: Rest<()>, ) -> Result<Response<'js>> {
) -> Result<ResponseClass> {
// Create a request from the input. // Create a request from the input.
let js_req = RequestClass::new(ctx, input, init, args)?; let js_req = Request::new(ctx.clone(), input, init)?;
let url = js_req.url; let url = js_req.url;
@ -32,9 +31,9 @@ pub async fn fetch<'js>(
// SurrealDB Implementation keeps all javascript parts inside the context::with scope so this // SurrealDB Implementation keeps all javascript parts inside the context::with scope so this
// unwrap should never panic. // unwrap should never panic.
let headers = js_req.init.headers.restore(ctx).unwrap(); let headers = js_req.init.headers;
let headers = headers.borrow(); let headers = headers.borrow();
let mut headers = headers.inner.borrow().clone(); let mut headers = headers.inner.clone();
let redirect = js_req.init.request_redirect; let redirect = js_req.init.request_redirect;
@ -55,7 +54,7 @@ pub async fn fetch<'js>(
}); });
let client = reqwest::Client::builder().redirect(policy).build().map_err(|e| { let client = reqwest::Client::builder().redirect(policy).build().map_err(|e| {
Exception::throw_internal(ctx, &format!("Could not initialize http client: {e}")) Exception::throw_internal(&ctx, &format!("Could not initialize http client: {e}"))
})?; })?;
// Set the body for the request. // Set the body for the request.
@ -70,7 +69,7 @@ pub async fn fetch<'js>(
let body = ReqBody::from(x); let body = ReqBody::from(x);
req_builder = req_builder.body(body); req_builder = req_builder.body(body);
} }
BodyData::Used => return Err(Exception::throw_type(ctx, "Body unusable")), BodyData::Used => return Err(Exception::throw_type(&ctx, "Body unusable")),
}; };
match body.kind { match body.kind {
BodyKind::Buffer => {} BodyKind::Buffer => {}
@ -94,12 +93,11 @@ pub async fn fetch<'js>(
.headers(headers) .headers(headers)
.send() .send()
.await .await
.map_err(|e| Exception::throw_type(ctx, &e.to_string()))?; .map_err(|e| Exception::throw_type(&ctx, &e.to_string()))?;
// Extract the headers // Extract the headers
let headers = HeadersClass::from_map(response.headers().clone()); let headers = Headers::from_map(response.headers().clone());
let headers = Class::instance(ctx, headers)?; let headers = Class::instance(ctx, headers)?;
let headers = Persistent::save(ctx, headers);
let init = ResponseInit { let init = ResponseInit {
headers, headers,
status: response.status().as_u16(), status: response.status().as_u16(),
@ -111,7 +109,7 @@ pub async fn fetch<'js>(
BodyKind::Buffer, BodyKind::Buffer,
response.bytes_stream().map_err(Arc::new).map_err(RequestError::Reqwest), response.bytes_stream().map_err(Arc::new).map_err(RequestError::Reqwest),
); );
let response = ResponseClass { let response = Response {
body, body,
init, init,
url: Some(url), url: Some(url),

View file

@ -1,6 +1,6 @@
use std::{error::Error, fmt, sync::Arc}; use std::{error::Error, fmt, sync::Arc};
use js::{Ctx, Result}; use js::{Class, Ctx, Result};
mod body; mod body;
mod classes; mod classes;
@ -9,7 +9,7 @@ mod stream;
mod util; mod util;
use classes::{Blob, FormData, Headers, Request, Response}; use classes::{Blob, FormData, Headers, Request, Response};
use func::Fetch; use func::js_fetch;
// Anoyingly errors aren't clone, // Anoyingly errors aren't clone,
// But with how we implement streams RequestError must be clone. // But with how we implement streams RequestError must be clone.
@ -30,15 +30,14 @@ impl fmt::Display for RequestError {
impl Error for RequestError {} impl Error for RequestError {}
/// Register the fetch types in the context. /// Register the fetch types in the context.
pub fn register(ctx: Ctx<'_>) -> Result<()> { pub fn register(ctx: &Ctx<'_>) -> Result<()> {
let globals = ctx.globals(); let globals = ctx.globals();
globals.init_def::<Fetch>()?; globals.set("fetch", js_fetch)?;
Class::<Response>::define(&globals)?;
globals.init_def::<Response>()?; Class::<Request>::define(&globals)?;
globals.init_def::<Request>()?; Class::<Blob>::define(&globals)?;
globals.init_def::<Blob>()?; Class::<FormData>::define(&globals)?;
globals.init_def::<FormData>()?; Class::<Headers>::define(&globals)?;
globals.init_def::<Headers>()?;
Ok(()) Ok(())
} }
@ -55,9 +54,9 @@ mod test {
let ctx = js::AsyncContext::full(&rt).await.unwrap(); let ctx = js::AsyncContext::full(&rt).await.unwrap();
js::async_with!(ctx => |$ctx|{ js::async_with!(ctx => |$ctx|{
crate::fnc::script::fetch::register($ctx).unwrap(); crate::fnc::script::fetch::register(&$ctx).unwrap();
$ctx.eval::<(),_>(r#" $ctx.eval::<(),_>(r"
globalThis.assert = (...arg) => { globalThis.assert = (...arg) => {
arg.forEach(x => { arg.forEach(x => {
if (!x) { if (!x) {
@ -91,7 +90,7 @@ mod test {
} }
throw new Error(`Code which should throw, didnt: \n${cb}`) throw new Error(`Code which should throw, didnt: \n${cb}`)
} }
"#).unwrap(); ").unwrap();
$($t)* $($t)*
}).await; }).await;

View file

@ -1,4 +1,5 @@
/// The stub implementations for the fetch API when `http` is not enabled. //! stub implementations for the fetch API when `http` is not enabled.
use js::{bind, prelude::*, Ctx, Exception, Result}; use js::{bind, prelude::*, Ctx, Exception, Result};
#[cfg(test)] #[cfg(test)]

View file

@ -5,6 +5,7 @@ use crate::sql::object::Object;
use crate::sql::value::Value; use crate::sql::value::Value;
use crate::sql::Id; use crate::sql::Id;
use chrono::{TimeZone, Utc}; use chrono::{TimeZone, Utc};
use js::prelude::This;
use js::Ctx; use js::Ctx;
use js::Error; use js::Error;
use js::Exception; use js::Exception;
@ -20,7 +21,7 @@ fn check_nul(s: &str) -> Result<(), Error> {
} }
impl<'js> FromJs<'js> for Value { impl<'js> FromJs<'js> for Value {
fn from_js(ctx: Ctx<'js>, val: js::Value<'js>) -> Result<Self, Error> { fn from_js(ctx: &Ctx<'js>, val: js::Value<'js>) -> Result<Self, Error> {
match val { match val {
val if val.type_name() == "null" => Ok(Value::Null), val if val.type_name() == "null" => Ok(Value::Null),
val if val.type_name() == "undefined" => Ok(Value::None), val if val.type_name() == "undefined" => Ok(Value::None),
@ -49,14 +50,15 @@ impl<'js> FromJs<'js> for Value {
// Check to see if this object is an error // Check to see if this object is an error
if v.is_error() { if v.is_error() {
let e: String = v.get("message")?; let e: String = v.get("message")?;
let (Ok(e) | Err(e)) = Exception::from_message(ctx, &e).map(|x| x.throw()); let (Ok(e) | Err(e)) =
Exception::from_message(ctx.clone(), &e).map(|x| x.throw());
return Err(e); return Err(e);
} }
// Check to see if this object is a record // Check to see if this object is a record
if (v).instance_of::<classes::record::record::Record>() { if (v).instance_of::<classes::record::Record>() {
let v = v.into_instance::<classes::record::record::Record>().unwrap(); let v = v.into_class::<classes::record::Record>().unwrap();
let borrow = v.borrow(); let borrow = v.borrow();
let v: &classes::record::record::Record = &borrow; let v: &classes::record::Record = &borrow;
check_nul(&v.value.tb)?; check_nul(&v.value.tb)?;
if let Id::String(s) = &v.value.id { if let Id::String(s) = &v.value.id {
check_nul(s)?; check_nul(s)?;
@ -64,20 +66,20 @@ impl<'js> FromJs<'js> for Value {
return Ok(v.value.clone().into()); return Ok(v.value.clone().into());
} }
// Check to see if this object is a duration // Check to see if this object is a duration
if (v).instance_of::<classes::duration::duration::Duration>() { if (v).instance_of::<classes::duration::Duration>() {
let v = v.into_instance::<classes::duration::duration::Duration>().unwrap(); let v = v.into_class::<classes::duration::Duration>().unwrap();
let borrow = v.borrow(); let borrow = v.borrow();
let v: &classes::duration::duration::Duration = &borrow; let v: &classes::duration::Duration = &borrow;
return match &v.value { return match &v.value {
Some(v) => Ok(v.clone().into()), Some(v) => Ok(v.clone().into()),
None => Ok(Value::None), None => Ok(Value::None),
}; };
} }
// Check to see if this object is a uuid // Check to see if this object is a uuid
if (v).instance_of::<classes::uuid::uuid::Uuid>() { if (v).instance_of::<classes::uuid::Uuid>() {
let v = v.into_instance::<classes::uuid::uuid::Uuid>().unwrap(); let v = v.into_class::<classes::uuid::Uuid>().unwrap();
let borrow = v.borrow(); let borrow = v.borrow();
let v: &classes::uuid::uuid::Uuid = &borrow; let v: &classes::uuid::Uuid = &borrow;
return match &v.value { return match &v.value {
Some(v) => Ok(v.clone().into()), Some(v) => Ok(v.clone().into()),
None => Ok(Value::None), None => Ok(Value::None),
@ -87,7 +89,7 @@ impl<'js> FromJs<'js> for Value {
let date: js::Object = ctx.globals().get("Date")?; let date: js::Object = ctx.globals().get("Date")?;
if (v).is_instance_of(&date) { if (v).is_instance_of(&date) {
let f: js::Function = v.get("getTime")?; let f: js::Function = v.get("getTime")?;
let m: i64 = f.call((js::prelude::This(v),))?; let m: i64 = f.call((This(v),))?;
let d = Utc.timestamp_millis_opt(m).unwrap(); let d = Utc.timestamp_millis_opt(m).unwrap();
return Ok(Datetime::from(d).into()); return Ok(Datetime::from(d).into());
} }

View file

@ -1,32 +1,44 @@
#[js::bind(object, public)] // Specify the imports
#[quickjs(rename = "console")] use crate::sql::value::Value;
#[allow(clippy::module_inception)] use js::{prelude::Rest, Ctx, Object, Result};
pub mod console { /// Log the input values as INFO
// Specify the imports #[js::function]
use crate::sql::value::Value; pub fn log(args: Rest<Value>) {
use js::prelude::Rest; info!("{}", args.iter().map(|v| v.to_raw_string()).collect::<Vec<String>>().join(" "));
/// Log the input values as INFO }
pub fn log(args: Rest<Value>) { /// Log the input values as INFO
info!("{}", args.iter().map(|v| v.to_raw_string()).collect::<Vec<String>>().join(" ")); #[js::function]
} pub fn info(args: Rest<Value>) {
/// Log the input values as INFO info!("{}", args.iter().map(|v| v.to_raw_string()).collect::<Vec<String>>().join(" "));
pub fn info(args: Rest<Value>) { }
info!("{}", args.iter().map(|v| v.to_raw_string()).collect::<Vec<String>>().join(" ")); /// Log the input values as WARN
} #[js::function]
/// Log the input values as WARN pub fn warn(args: Rest<Value>) {
pub fn warn(args: Rest<Value>) { warn!("{}", args.iter().map(|v| v.to_raw_string()).collect::<Vec<String>>().join(" "));
warn!("{}", args.iter().map(|v| v.to_raw_string()).collect::<Vec<String>>().join(" ")); }
} /// Log the input values as ERROR
/// Log the input values as ERROR #[js::function]
pub fn error(args: Rest<Value>) { pub fn error(args: Rest<Value>) {
error!("{}", args.iter().map(|v| v.to_raw_string()).collect::<Vec<String>>().join(" ")); error!("{}", args.iter().map(|v| v.to_raw_string()).collect::<Vec<String>>().join(" "));
} }
/// Log the input values as DEBUG /// Log the input values as DEBUG
pub fn debug(args: Rest<Value>) { #[js::function]
debug!("{}", args.iter().map(|v| v.to_raw_string()).collect::<Vec<String>>().join(" ")); pub fn debug(args: Rest<Value>) {
} debug!("{}", args.iter().map(|v| v.to_raw_string()).collect::<Vec<String>>().join(" "));
/// Log the input values as TRACE }
pub fn trace(args: Rest<Value>) { /// Log the input values as TRACE
trace!("{}", args.iter().map(|v| v.to_raw_string()).collect::<Vec<String>>().join(" ")); #[js::function]
} pub fn trace(args: Rest<Value>) {
trace!("{}", args.iter().map(|v| v.to_raw_string()).collect::<Vec<String>>().join(" "));
}
pub fn console<'js>(ctx: &Ctx<'js>) -> Result<Object<'js>> {
let console = Object::new(ctx.clone())?;
console.set("log", js_log)?;
console.set("info", js_info)?;
console.set("warn", js_warn)?;
console.set("error", js_error)?;
console.set("debug", js_debug)?;
console.set("trace", js_trace)?;
Ok(console)
} }

View file

@ -12,58 +12,58 @@ use js::Object;
use js::Undefined; use js::Undefined;
impl<'js> IntoJs<'js> for Value { impl<'js> IntoJs<'js> for Value {
fn into_js(self, ctx: Ctx<'js>) -> Result<js::Value<'js>, Error> { fn into_js(self, ctx: &Ctx<'js>) -> Result<js::Value<'js>, Error> {
(&self).into_js(ctx) (&self).into_js(ctx)
} }
} }
impl<'js> IntoJs<'js> for &Value { impl<'js> IntoJs<'js> for &Value {
fn into_js(self, ctx: Ctx<'js>) -> Result<js::Value<'js>, Error> { fn into_js(self, ctx: &Ctx<'js>) -> Result<js::Value<'js>, Error> {
match self { match self {
Value::Null => Null.into_js(ctx), Value::Null => Null.into_js(ctx),
Value::None => Undefined.into_js(ctx), Value::None => Undefined.into_js(ctx),
Value::Bool(boolean) => Ok(js::Value::new_bool(ctx, *boolean)), Value::Bool(boolean) => Ok(js::Value::new_bool(ctx.clone(), *boolean)),
Value::Strand(v) => js::String::from_str(ctx, v)?.into_js(ctx), Value::Strand(v) => js::String::from_str(ctx.clone(), v)?.into_js(ctx),
Value::Number(Number::Int(v)) => Ok(js::Value::new_int(ctx, *v as i32)), Value::Number(Number::Int(v)) => Ok(js::Value::new_int(ctx.clone(), *v as i32)),
Value::Number(Number::Float(v)) => Ok(js::Value::new_float(ctx, *v)), Value::Number(Number::Float(v)) => Ok(js::Value::new_float(ctx.clone(), *v)),
&Value::Number(Number::Decimal(v)) => match decimal_is_integer(&v) { &Value::Number(Number::Decimal(v)) => match decimal_is_integer(&v) {
true => Ok(js::Value::new_int(ctx, v.try_into().unwrap_or_default())), true => Ok(js::Value::new_int(ctx.clone(), v.try_into().unwrap_or_default())),
false => Ok(js::Value::new_float(ctx, v.try_into().unwrap_or_default())), false => Ok(js::Value::new_float(ctx.clone(), v.try_into().unwrap_or_default())),
}, },
Value::Datetime(v) => { Value::Datetime(v) => {
let date: js::Function = ctx.globals().get("Date")?; let date: js::function::Constructor = ctx.globals().get("Date")?;
date.construct((v.0.timestamp_millis(),)) date.construct((v.0.timestamp_millis(),))
} }
Value::Thing(v) => Ok(Class::<classes::record::record::Record>::instance( Value::Thing(v) => Ok(Class::<classes::record::Record>::instance(
ctx, ctx.clone(),
classes::record::record::Record { classes::record::Record {
value: v.to_owned(), value: v.to_owned(),
}, },
)? )?
.into_value()), .into_value()),
Value::Duration(v) => Ok(Class::<classes::duration::duration::Duration>::instance( Value::Duration(v) => Ok(Class::<classes::duration::Duration>::instance(
ctx, ctx.clone(),
classes::duration::duration::Duration { classes::duration::Duration {
value: Some(v.to_owned()), value: Some(v.to_owned()),
}, },
)? )?
.into_value()), .into_value()),
Value::Uuid(v) => Ok(Class::<classes::uuid::uuid::Uuid>::instance( Value::Uuid(v) => Ok(Class::<classes::uuid::Uuid>::instance(
ctx, ctx.clone(),
classes::uuid::uuid::Uuid { classes::uuid::Uuid {
value: Some(v.to_owned()), value: Some(v.to_owned()),
}, },
)? )?
.into_value()), .into_value()),
Value::Array(v) => { Value::Array(v) => {
let x = Array::new(ctx)?; let x = Array::new(ctx.clone())?;
for (i, v) in v.iter().enumerate() { for (i, v) in v.iter().enumerate() {
x.set(i, v)?; x.set(i, v)?;
} }
x.into_js(ctx) x.into_js(ctx)
} }
Value::Object(v) => { Value::Object(v) => {
let x = Object::new(ctx)?; let x = Object::new(ctx.clone())?;
for (k, v) in v.iter() { for (k, v) in v.iter() {
x.set(k, v)?; x.set(k, v)?;
} }

View file

@ -50,23 +50,24 @@ pub async fn run(
// Attempt to execute the script // Attempt to execute the script
async_with!(ctx => |ctx|{ async_with!(ctx => |ctx|{
let res = async move { let res = async{
// register all classes to the runtime. // register all classes to the runtime.
// Get the context global object // Get the context global object
let global = ctx.globals(); let global = ctx.globals();
// Register the surrealdb module as a global object // Register the surrealdb module as a global object
global.set( global.set(
"surrealdb", "surrealdb",
Module::evaluate_def::<modules::surrealdb::Package, _>(ctx, "surrealdb")? Module::evaluate_def::<modules::surrealdb::Package, _>(ctx.clone(), "surrealdb")?
.get::<_, js::Value>("default")?, .get::<_, js::Value>("default")?,
)?; )?;
fetch::register(ctx)?; fetch::register(&ctx)?;
let console = globals::console::console(&ctx)?;
// Register the console function to the globals // Register the console function to the globals
global.init_def::<globals::console::Console>()?; global.set("console",console)?;
// Register the special SurrealDB types as classes // Register the special SurrealDB types as classes
classes::init(ctx)?; classes::init(&ctx)?;
// Attempt to compile the script // Attempt to compile the script
let res = ctx.compile("script", src)?; let res = ctx.clone().compile("script", src)?;
// Attempt to fetch the main export // Attempt to fetch the main export
let fnc = res.get::<_, Function>("default")?; let fnc = res.get::<_, Function>("default")?;
// Extract the doc if any // Extract the doc if any
@ -76,7 +77,7 @@ pub async fn run(
promise.await promise.await
}.await; }.await;
res.catch(ctx).map_err(Error::from) res.catch(&ctx).map_err(Error::from)
}) })
.await .await
} }

View file

@ -54,8 +54,8 @@ macro_rules! impl_module_def {
Ok(()) Ok(())
} }
fn evaluate<'js>(ctx: js::Ctx<'js>, exports: &mut js::module::Exports<'js>) -> js::Result<()> { fn evaluate<'js>(ctx: &js::Ctx<'js>, exports: &mut js::module::Exports<'js>) -> js::Result<()> {
let default = js::Object::new(ctx)?; let default = js::Object::new(ctx.clone())?;
$( $(
exports.export($name, crate::fnc::script::modules::impl_module_def!(ctx, $path, $name, $action, $($wrapper)?))?; exports.export($name, crate::fnc::script::modules::impl_module_def!(ctx, $path, $name, $action, $($wrapper)?))?;
default.set($name, crate::fnc::script::modules::impl_module_def!(ctx, $path, $name, $action, $($wrapper)?))?; default.set($name, crate::fnc::script::modules::impl_module_def!(ctx, $path, $name, $action, $($wrapper)?))?;

View file

@ -1,13 +1,31 @@
#[js::bind(module, public)] use js::{
#[quickjs(bare)] module::{Declarations, Exports, ModuleDef},
#[allow(non_upper_case_globals)] Result,
pub mod package { };
/// Get the target system architecture
pub fn arch() -> &'static str { /// Get the target system architecture
crate::env::arch() #[js::function]
pub fn arch() -> &'static str {
crate::env::arch()
}
/// Get the target operating system
#[js::function]
pub fn platform() -> &'static str {
crate::env::os()
}
pub struct Package;
impl ModuleDef for Package {
fn declare(declare: &mut Declarations) -> Result<()> {
declare.declare("arch")?;
declare.declare("platform")?;
Ok(())
} }
/// Get the target operating system
pub fn platform() -> &'static str { fn evaluate<'js>(_ctx: &js::Ctx<'js>, exports: &mut Exports<'js>) -> Result<()> {
crate::env::os() exports.export("arch", js_arch)?;
exports.export("platform", js_platform)?;
Ok(())
} }
} }

View file

@ -12,9 +12,9 @@ impl_module_def!(
"version" => (env!("CARGO_PKG_VERSION")) "version" => (env!("CARGO_PKG_VERSION"))
); );
fn pkg<'js, D>(ctx: Ctx<'js>, name: &str) -> Result<Value<'js>> fn pkg<'js, D>(ctx: &Ctx<'js>, name: &str) -> Result<Value<'js>>
where where
D: ModuleDef, D: ModuleDef,
{ {
Module::evaluate_def::<D, _>(ctx, name)?.get::<_, js::Value>("default") Module::evaluate_def::<D, _>(ctx.clone(), name)?.get::<_, js::Value>("default")
} }

View file

@ -2938,7 +2938,7 @@ mod tests {
assert_eq!(24, std::mem::size_of::<crate::sql::table::Table>()); assert_eq!(24, std::mem::size_of::<crate::sql::table::Table>());
assert_eq!(56, std::mem::size_of::<crate::sql::thing::Thing>()); assert_eq!(56, std::mem::size_of::<crate::sql::thing::Thing>());
assert_eq!(40, std::mem::size_of::<crate::sql::model::Model>()); assert_eq!(40, std::mem::size_of::<crate::sql::model::Model>());
assert_eq!(16, std::mem::size_of::<crate::sql::regex::Regex>()); assert_eq!(32, std::mem::size_of::<crate::sql::regex::Regex>());
assert_eq!(8, std::mem::size_of::<Box<crate::sql::range::Range>>()); assert_eq!(8, std::mem::size_of::<Box<crate::sql::range::Range>>());
assert_eq!(8, std::mem::size_of::<Box<crate::sql::edges::Edges>>()); assert_eq!(8, std::mem::size_of::<Box<crate::sql::edges::Edges>>());
assert_eq!(8, std::mem::size_of::<Box<crate::sql::function::Function>>()); assert_eq!(8, std::mem::size_of::<Box<crate::sql::function::Function>>());