Update js functions to new rquickjs version (#2252)
This commit is contained in:
parent
1e30eb4aa1
commit
4f4339848e
24 changed files with 1362 additions and 1498 deletions
389
Cargo.lock
generated
389
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||||
|
|
|
@ -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"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"))?;
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)?;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)?))?;
|
||||||
|
|
|
@ -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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>>());
|
||||||
|
|
Loading…
Reference in a new issue