Revert "Update js functions to new rquickjs version" (#2262)
This commit is contained in:
parent
4f4339848e
commit
5c08be973d
24 changed files with 1496 additions and 1360 deletions
Cargo.lock
lib
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"] }
|
||||
indexmap = { version = "1.9.3", features = ["serde"] }
|
||||
indxdb = { version = "0.3.0", 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 }
|
||||
js = { version = "0.3.1" , package = "rquickjs", features = ["array-buffer", "bindgen", "classes", "futures", "loader", "macro", "parallel", "properties","rust-alloc"], optional = true }
|
||||
jsonwebtoken = "8.3.0"
|
||||
lexicmp = "0.1.0"
|
||||
lru = "0.10.1"
|
||||
|
@ -155,4 +155,4 @@ harness = false
|
|||
|
||||
[[bench]]
|
||||
name = "index_btree"
|
||||
harness = false
|
||||
harness = false
|
|
@ -1,48 +1,51 @@
|
|||
use js::class::Trace;
|
||||
#[js::bind(object, public)]
|
||||
#[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, Trace)]
|
||||
#[js::class]
|
||||
pub struct Duration {
|
||||
#[qjs(skip_trace)]
|
||||
pub(crate) value: Option<duration::Duration>,
|
||||
}
|
||||
#[derive(Clone)]
|
||||
#[quickjs(cloneable)]
|
||||
pub struct Duration {
|
||||
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 {
|
||||
#[quickjs(constructor)]
|
||||
pub fn new(value: String, args: Rest<Value>) -> Self {
|
||||
Self {
|
||||
value: duration::Duration::try_from(value).ok(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[qjs(get)]
|
||||
pub fn value(&self) -> String {
|
||||
match &self.value {
|
||||
Some(v) => v.to_raw(),
|
||||
None => String::from("Invalid Duration"),
|
||||
#[quickjs(get)]
|
||||
pub fn value(&self) -> String {
|
||||
match &self.value {
|
||||
Some(v) => v.to_raw(),
|
||||
None => String::from("Invalid Duration"),
|
||||
}
|
||||
}
|
||||
}
|
||||
// Compare two Duration instances
|
||||
pub fn is(a: &Duration, b: &Duration) -> bool {
|
||||
a.value.is_some() && b.value.is_some() && a.value == b.value
|
||||
}
|
||||
/// 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 {
|
||||
a.value.is_some() && b.value.is_some() && a.value == b.value
|
||||
}
|
||||
}
|
||||
/// Convert the object to JSON
|
||||
#[qjs(rename = "toJSON")]
|
||||
pub fn to_json(&self) -> String {
|
||||
match &self.value {
|
||||
Some(v) => v.to_raw(),
|
||||
None => String::from("Invalid Duration"),
|
||||
/// Convert the object to a string
|
||||
pub fn toString(&self, args: Rest<()>) -> String {
|
||||
match &self.value {
|
||||
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::{Class, Ctx, Result};
|
||||
use js::{Ctx, Result};
|
||||
|
||||
pub mod duration;
|
||||
pub mod record;
|
||||
pub mod uuid;
|
||||
|
||||
pub fn init(ctx: &Ctx<'_>) -> Result<()> {
|
||||
pub fn init(ctx: Ctx<'_>) -> Result<()> {
|
||||
let globals = ctx.globals();
|
||||
Class::<duration::Duration>::define(&globals)?;
|
||||
Class::<record::Record>::define(&globals)?;
|
||||
Class::<uuid::Uuid>::define(&globals)?;
|
||||
globals.init_def::<duration::Duration>()?;
|
||||
globals.init_def::<record::Record>()?;
|
||||
globals.init_def::<uuid::Uuid>()?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,52 +1,56 @@
|
|||
use crate::sql::thing;
|
||||
use crate::sql::value::Value;
|
||||
use js::class::Trace;
|
||||
#[js::bind(object, public)]
|
||||
#[quickjs(bare)]
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(unused_variables)]
|
||||
#[allow(clippy::module_inception)]
|
||||
pub mod record {
|
||||
|
||||
#[derive(Clone, Trace)]
|
||||
#[js::class]
|
||||
pub struct Record {
|
||||
#[qjs(skip_trace)]
|
||||
pub(crate) value: thing::Thing,
|
||||
}
|
||||
use crate::sql::thing;
|
||||
use crate::sql::value::Value;
|
||||
use js::{class::Ref, function::Rest};
|
||||
|
||||
#[js::methods]
|
||||
impl Record {
|
||||
#[qjs(constructor)]
|
||||
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(),
|
||||
#[derive(Clone)]
|
||||
#[quickjs(cloneable)]
|
||||
pub struct Record {
|
||||
pub(crate) value: thing::Thing,
|
||||
}
|
||||
|
||||
impl Record {
|
||||
#[quickjs(constructor)]
|
||||
pub fn new(tb: String, id: Value, args: Rest<()>) -> 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(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[quickjs(get)]
|
||||
pub fn tb(&self) -> String {
|
||||
self.value.tb.clone()
|
||||
}
|
||||
|
||||
#[quickjs(get)]
|
||||
pub fn id(&self) -> String {
|
||||
self.value.id.to_raw()
|
||||
}
|
||||
// Compare two Record instances
|
||||
pub fn is<'js>(a: Ref<'js, Record>, b: Ref<'js, Record>, args: Rest<()>) -> bool {
|
||||
a.value == b.value
|
||||
}
|
||||
/// Convert the object to a string
|
||||
pub fn toString(&self, args: Rest<()>) -> String {
|
||||
self.value.to_raw()
|
||||
}
|
||||
/// Convert the object to JSON
|
||||
pub fn toJSON(&self, args: Rest<()>) -> String {
|
||||
self.value.to_raw()
|
||||
}
|
||||
}
|
||||
|
||||
#[qjs(get)]
|
||||
pub fn tb(&self) -> String {
|
||||
self.value.tb.clone()
|
||||
}
|
||||
|
||||
#[qjs(get)]
|
||||
pub fn id(&self) -> String {
|
||||
self.value.id.to_raw()
|
||||
}
|
||||
// Compare two Record instances
|
||||
pub fn is(a: &Record, b: &Record) -> bool {
|
||||
a.value == b.value
|
||||
}
|
||||
/// Convert the object to a string
|
||||
#[qjs(rename = "toString")]
|
||||
pub fn js_to_string(&self) -> String {
|
||||
self.value.to_raw()
|
||||
}
|
||||
/// Convert the object to JSON
|
||||
#[qjs(rename = "toJSON")]
|
||||
pub fn to_json(&self) -> String {
|
||||
self.value.to_raw()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,46 +1,51 @@
|
|||
use crate::sql::uuid;
|
||||
use js::class::Trace;
|
||||
#[js::bind(object, public)]
|
||||
#[quickjs(bare)]
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(unused_variables)]
|
||||
#[allow(clippy::module_inception)]
|
||||
pub mod uuid {
|
||||
|
||||
#[derive(Clone, Trace)]
|
||||
#[js::class]
|
||||
pub struct Uuid {
|
||||
#[qjs(skip_trace)]
|
||||
pub(crate) value: Option<uuid::Uuid>,
|
||||
}
|
||||
use crate::sql::uuid;
|
||||
use crate::sql::value::Value;
|
||||
use js::{class::Ref, function::Rest};
|
||||
|
||||
#[js::methods]
|
||||
impl Uuid {
|
||||
#[qjs(constructor)]
|
||||
pub fn new(value: String) -> Self {
|
||||
Self {
|
||||
value: uuid::Uuid::try_from(value).ok(),
|
||||
#[derive(Clone)]
|
||||
#[quickjs(cloneable)]
|
||||
pub struct Uuid {
|
||||
pub(crate) value: Option<uuid::Uuid>,
|
||||
}
|
||||
|
||||
impl Uuid {
|
||||
#[quickjs(constructor)]
|
||||
pub fn new(value: String, args: Rest<Value>) -> Self {
|
||||
Self {
|
||||
value: uuid::Uuid::try_from(value).ok(),
|
||||
}
|
||||
}
|
||||
}
|
||||
#[qjs(get)]
|
||||
pub fn value(&self) -> String {
|
||||
match &self.value {
|
||||
Some(v) => v.to_raw(),
|
||||
None => String::from("Invalid Uuid"),
|
||||
#[quickjs(get)]
|
||||
pub fn value(&self) -> String {
|
||||
match &self.value {
|
||||
Some(v) => v.to_raw(),
|
||||
None => String::from("Invalid Uuid"),
|
||||
}
|
||||
}
|
||||
}
|
||||
// Compare two Uuid instances
|
||||
pub fn is(a: &Uuid, b: &Uuid) -> bool {
|
||||
a.value.is_some() && b.value.is_some() && a.value == b.value
|
||||
}
|
||||
/// 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 {
|
||||
a.value.is_some() && b.value.is_some() && a.value == b.value
|
||||
}
|
||||
}
|
||||
/// Convert the object to JSON
|
||||
#[qjs(rename = "toJSON")]
|
||||
pub fn to_json(&self) -> String {
|
||||
match &self.value {
|
||||
Some(v) => v.to_raw(),
|
||||
None => String::from("Invalid Uuid"),
|
||||
/// Convert the object to a string
|
||||
pub fn toString(&self, _args: Rest<()>) -> String {
|
||||
match &self.value {
|
||||
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::{stream::ReadableStream, RequestError};
|
||||
use crate::fnc::script::fetch::{classes::BlobClass, stream::ReadableStream, RequestError};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::{future, Stream, TryStreamExt};
|
||||
use js::{ArrayBuffer, Class, Ctx, Error, Exception, FromJs, Result, Type, TypedArray, Value};
|
||||
|
@ -7,8 +7,6 @@ use std::{
|
|||
result::Result as StdResult,
|
||||
};
|
||||
|
||||
use super::classes::Blob;
|
||||
|
||||
pub type StreamItem = StdResult<Bytes, RequestError>;
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -129,7 +127,7 @@ impl 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() {
|
||||
Type::String => {
|
||||
let string = value.as_string().unwrap().to_string()?;
|
||||
|
@ -144,7 +142,7 @@ impl<'js> FromJs<'js> for Body {
|
|||
})
|
||||
}
|
||||
};
|
||||
if let Some(x) = Class::<Blob>::from_object(object.clone()) {
|
||||
if let Ok(x) = Class::<BlobClass>::from_object(object.clone()) {
|
||||
let borrow = x.borrow();
|
||||
return Ok(Body::buffer(BodyKind::Blob(borrow.mime.clone()), borrow.data.clone()));
|
||||
}
|
||||
|
@ -196,7 +194,7 @@ impl<'js> FromJs<'js> for Body {
|
|||
.ok_or_else(|| Exception::throw_type(ctx, "Buffer is already detached"))?;
|
||||
return Ok(Body::buffer(BodyKind::Buffer, Bytes::copy_from_slice(bytes)));
|
||||
}
|
||||
if let Some(x) = ArrayBuffer::from_object(object.clone()) {
|
||||
if let Ok(x) = ArrayBuffer::from_object(object.clone()) {
|
||||
let bytes = x
|
||||
.as_bytes()
|
||||
.ok_or_else(|| Exception::throw_type(ctx, "Buffer is already detached"))?;
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
//! Blob class implementation
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use js::{
|
||||
class::Trace,
|
||||
prelude::{Coerced, Opt},
|
||||
ArrayBuffer, Class, Ctx, Exception, FromJs, Object, Result, Value,
|
||||
};
|
||||
use bytes::BytesMut;
|
||||
use js::{bind, prelude::Coerced, ArrayBuffer, Class, Ctx, Exception, FromJs, Result, Value};
|
||||
|
||||
pub use blob::Blob as BlobClass;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum EndingType {
|
||||
|
@ -14,7 +12,7 @@ pub enum EndingType {
|
|||
}
|
||||
|
||||
fn append_blob_part<'js>(
|
||||
ctx: &Ctx<'js>,
|
||||
ctx: Ctx<'js>,
|
||||
value: Value<'js>,
|
||||
ending: EndingType,
|
||||
data: &mut BytesMut,
|
||||
|
@ -25,11 +23,11 @@ fn append_blob_part<'js>(
|
|||
const LINE_ENDING: &[u8] = b"\n";
|
||||
|
||||
if let Some(object) = value.as_object() {
|
||||
if let Some(x) = Class::<Blob>::from_object(object.clone()) {
|
||||
if let Ok(x) = Class::<BlobClass>::from_object(object.clone()) {
|
||||
data.extend_from_slice(&x.borrow().data);
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(x) = ArrayBuffer::from_object(object.clone()) {
|
||||
if let Ok(x) = ArrayBuffer::from_object(object.clone()) {
|
||||
data.extend_from_slice(x.as_bytes().ok_or_else(|| {
|
||||
Exception::throw_type(ctx, "Tried to construct blob with detached buffer")
|
||||
})?);
|
||||
|
@ -76,121 +74,144 @@ fn normalize_type(mut ty: String) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Trace)]
|
||||
#[js::class]
|
||||
pub struct Blob {
|
||||
pub(crate) mime: String,
|
||||
// TODO: make bytes?
|
||||
#[qjs(skip_trace)]
|
||||
pub(crate) data: Bytes,
|
||||
}
|
||||
#[bind(object, public)]
|
||||
#[quickjs(bare)]
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(unused_variables)]
|
||||
#[allow(clippy::module_inception)]
|
||||
mod blob {
|
||||
use super::*;
|
||||
|
||||
#[js::methods]
|
||||
impl Blob {
|
||||
// ------------------------------
|
||||
// Constructor
|
||||
// ------------------------------
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use js::{
|
||||
function::{Opt, Rest},
|
||||
ArrayBuffer, Ctx, Exception, Object, Result, Value,
|
||||
};
|
||||
|
||||
#[qjs(constructor)]
|
||||
pub fn new<'js>(
|
||||
ctx: Ctx<'js>,
|
||||
parts: Opt<Value<'js>>,
|
||||
options: Opt<Object<'js>>,
|
||||
) -> Result<Self> {
|
||||
let mut r#type = String::new();
|
||||
let mut endings = EndingType::Transparent;
|
||||
#[derive(Clone)]
|
||||
#[quickjs(cloneable)]
|
||||
pub struct Blob {
|
||||
pub(crate) mime: String,
|
||||
// TODO: make bytes?
|
||||
pub(crate) data: Bytes,
|
||||
}
|
||||
|
||||
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'",
|
||||
));
|
||||
impl Blob {
|
||||
// ------------------------------
|
||||
// Constructor
|
||||
// ------------------------------
|
||||
|
||||
#[quickjs(constructor)]
|
||||
pub fn new<'js>(
|
||||
ctx: Ctx<'js>,
|
||||
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() {
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
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"))?;
|
||||
// ------------------------------
|
||||
// Instance properties
|
||||
// ------------------------------
|
||||
|
||||
let mut buffer = BytesMut::new();
|
||||
#[quickjs(get)]
|
||||
pub fn size(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
|
||||
for elem in array.iter::<Value>() {
|
||||
let elem = elem?;
|
||||
append_blob_part(&ctx, elem, endings, &mut buffer)?;
|
||||
#[quickjs(get)]
|
||||
#[quickjs(rename = "type")]
|
||||
pub fn r#type(&self) -> String {
|
||||
self.mime.clone()
|
||||
}
|
||||
|
||||
pub fn slice(
|
||||
&self,
|
||||
start: Opt<isize>,
|
||||
end: Opt<isize>,
|
||||
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,
|
||||
}
|
||||
buffer.freeze()
|
||||
} else {
|
||||
Bytes::new()
|
||||
};
|
||||
Ok(Self {
|
||||
mime: r#type,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// Instance properties
|
||||
// ------------------------------
|
||||
|
||||
#[qjs(get)]
|
||||
pub fn size(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
|
||||
#[qjs(get, rename = "type")]
|
||||
pub fn r#type(&self) -> String {
|
||||
self.mime.clone()
|
||||
}
|
||||
|
||||
pub fn slice(&self, start: Opt<isize>, end: Opt<isize>, content_type: Opt<String>) -> 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) -> Result<String> {
|
||||
let text = String::from_utf8(self.data.to_vec())?;
|
||||
Ok(text)
|
||||
}
|
||||
pub async fn text(&self, _rest: Rest<()>) -> Result<String> {
|
||||
let text = String::from_utf8(self.data.to_vec())?;
|
||||
Ok(text)
|
||||
}
|
||||
|
||||
#[qjs(rename = "arrayBuffer")]
|
||||
pub async fn array_buffer<'js>(&self, ctx: Ctx<'js>) -> Result<ArrayBuffer<'js>> {
|
||||
ArrayBuffer::new(ctx, self.data.to_vec())
|
||||
}
|
||||
pub async fn arrayBuffer<'js>(
|
||||
&self,
|
||||
ctx: Ctx<'js>,
|
||||
_rest: Rest<()>,
|
||||
) -> Result<ArrayBuffer<'js>> {
|
||||
ArrayBuffer::new(ctx, self.data.to_vec())
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// Instance methods
|
||||
// ------------------------------
|
||||
// ------------------------------
|
||||
// Instance methods
|
||||
// ------------------------------
|
||||
|
||||
// Convert the object to a string
|
||||
#[qjs(rename = "toString")]
|
||||
pub fn js_to_string(&self) -> String {
|
||||
String::from("[object Blob]")
|
||||
// Convert the object to a string
|
||||
pub fn toString(&self, _rest: Rest<()>) -> String {
|
||||
String::from("[object Blob]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -221,17 +242,17 @@ mod test {
|
|||
|
||||
blob = new Blob(["\n\r\n \n\r"],{endings: "transparent"});
|
||||
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"});
|
||||
// \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
|
||||
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.mustThrow(() => new Blob("text"));
|
||||
assert.mustThrow(() => new Blob("text"));
|
||||
assert.mustThrow(() => new Blob(["text"], {endings: "invalid value"}));
|
||||
})()
|
||||
"#).catch(&ctx).unwrap().await.catch(&ctx).unwrap();
|
||||
"#).catch(ctx).unwrap().await.catch(ctx).unwrap();
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
|
|
@ -1,36 +1,34 @@
|
|||
//! FormData class implementation
|
||||
|
||||
use js::{
|
||||
class::{Class, Trace},
|
||||
function::{Opt, Rest},
|
||||
prelude::Coerced,
|
||||
Ctx, Exception, FromJs, Result, String, Value,
|
||||
bind, function::Opt, prelude::Coerced, Class, Ctx, Exception, FromJs, Persistent, Result,
|
||||
String, Value,
|
||||
};
|
||||
use reqwest::multipart::{Form, Part};
|
||||
use std::{collections::HashMap, string::String as StdString};
|
||||
use std::string::String as StdString;
|
||||
|
||||
use crate::fnc::script::fetch::classes::Blob;
|
||||
use crate::fnc::script::fetch::classes::BlobClass;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum FormDataValue<'js> {
|
||||
String(String<'js>),
|
||||
pub enum FormDataValue {
|
||||
String(Persistent<String<'static>>),
|
||||
Blob {
|
||||
data: Class<'js, Blob>,
|
||||
filename: Option<String<'js>>,
|
||||
data: Persistent<Class<'static, BlobClass>>,
|
||||
filename: Option<Persistent<String<'static>>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'js> FormDataValue<'js> {
|
||||
fn from_arguments(
|
||||
ctx: &Ctx<'js>,
|
||||
impl FormDataValue {
|
||||
fn from_arguments<'js>(
|
||||
ctx: Ctx<'js>,
|
||||
value: Value<'js>,
|
||||
filename: Opt<Coerced<String<'js>>>,
|
||||
error: &'static str,
|
||||
) -> Result<Self> {
|
||||
) -> Result<FormDataValue> {
|
||||
if let Some(blob) =
|
||||
value.as_object().and_then(|value| Class::<Blob>::from_object(value.clone()))
|
||||
value.as_object().and_then(|value| Class::<BlobClass>::from_object(value.clone()).ok())
|
||||
{
|
||||
let filename = filename.into_inner().map(|x| x.0);
|
||||
let blob = Persistent::save(ctx, blob);
|
||||
let filename = filename.into_inner().map(|x| Persistent::save(ctx, x.0));
|
||||
|
||||
Ok(FormDataValue::Blob {
|
||||
data: blob,
|
||||
|
@ -40,109 +38,128 @@ impl<'js> FormDataValue<'js> {
|
|||
return Err(Exception::throw_type(ctx, error));
|
||||
} else {
|
||||
let value = Coerced::<String>::from_js(ctx, value)?;
|
||||
Ok(FormDataValue::String(value.0))
|
||||
let value = Persistent::save(ctx, value.0);
|
||||
Ok(FormDataValue::String(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[js::class]
|
||||
#[derive(Clone, Trace)]
|
||||
pub struct FormData<'js> {
|
||||
#[qjs(skip_trace)]
|
||||
pub(crate) values: HashMap<StdString, Vec<FormDataValue<'js>>>,
|
||||
}
|
||||
pub use form_data::FormData as FormDataClass;
|
||||
#[bind(object, public)]
|
||||
#[quickjs(bare)]
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(unused_variables)]
|
||||
#[allow(clippy::module_inception)]
|
||||
pub mod form_data {
|
||||
use super::*;
|
||||
use std::{cell::RefCell, collections::HashMap};
|
||||
|
||||
#[js::methods]
|
||||
impl<'js> FormData<'js> {
|
||||
// ------------------------------
|
||||
// Constructor
|
||||
// ------------------------------
|
||||
use js::{
|
||||
function::Opt,
|
||||
prelude::{Coerced, Rest},
|
||||
Ctx, Result, String, Value,
|
||||
};
|
||||
use reqwest::multipart::{Form, Part};
|
||||
|
||||
// FormData spec states that FormDa takes two html elements as arguments
|
||||
// which does not make sense implementing fetch outside a browser.
|
||||
// So we ignore those arguments.
|
||||
#[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",
|
||||
));
|
||||
#[derive(Clone)]
|
||||
#[quickjs(cloneable)]
|
||||
pub struct FormData {
|
||||
pub(crate) values: RefCell<HashMap<StdString, Vec<FormDataValue>>>,
|
||||
}
|
||||
|
||||
impl FormData {
|
||||
// ------------------------------
|
||||
// Constructor
|
||||
// ------------------------------
|
||||
|
||||
// FormData spec states that FormDa takes two html elements as arguments
|
||||
// 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()),
|
||||
})
|
||||
}
|
||||
Ok(FormData {
|
||||
values: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn append(
|
||||
&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 `append` on `FormData` with a filename when value isn't of type `Blob`",
|
||||
)?;
|
||||
pub fn append<'js>(
|
||||
&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.entry(name.0).or_insert_with(Vec::new).push(value);
|
||||
self.values.borrow_mut().entry(name.0).or_insert_with(Vec::new).push(value);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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`",
|
||||
)?;
|
||||
pub fn set<'js>(
|
||||
&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.insert(name.0, vec![value]);
|
||||
self.values.borrow_mut().insert(name.0, vec![value]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn has(&self, name: Coerced<StdString>) -> bool {
|
||||
self.values.contains_key(&name.0)
|
||||
}
|
||||
pub fn has(&self, ctx: Ctx<'_>, name: Coerced<StdString>) -> bool {
|
||||
self.values.borrow().contains_key(&name.0)
|
||||
}
|
||||
|
||||
pub fn delete(&mut self, name: Coerced<StdString>) {
|
||||
self.values.remove(&name.0);
|
||||
}
|
||||
pub fn delete(&self, ctx: Ctx<'_>, name: Coerced<StdString>) {
|
||||
self.values.borrow_mut().remove(&name.0);
|
||||
}
|
||||
|
||||
#[qjs(skip)]
|
||||
pub fn to_form(&self) -> Result<Form> {
|
||||
let mut res = Form::new();
|
||||
for (k, v) in self.values.iter() {
|
||||
for v in v {
|
||||
match v {
|
||||
FormDataValue::String(x) => {
|
||||
res = res.text(k.clone(), x.to_string()?);
|
||||
}
|
||||
FormDataValue::Blob {
|
||||
data,
|
||||
filename,
|
||||
} => {
|
||||
let mut part = Part::bytes(data.borrow().data.to_vec());
|
||||
if let Some(filename) = filename {
|
||||
let filename = filename.to_string()?;
|
||||
part = part.file_name(filename);
|
||||
#[quickjs(skip)]
|
||||
pub fn to_form(&self, ctx: Ctx<'_>) -> Result<Form> {
|
||||
let lock = self.values.borrow();
|
||||
let mut res = Form::new();
|
||||
for (k, v) in lock.iter() {
|
||||
for v in v {
|
||||
match v {
|
||||
FormDataValue::String(x) => {
|
||||
let x = x.clone().restore(ctx).unwrap();
|
||||
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,258 +1,272 @@
|
|||
//! Headers class implementation
|
||||
|
||||
use std::str::FromStr;
|
||||
use js::bind;
|
||||
|
||||
use js::{
|
||||
class::Trace,
|
||||
prelude::{Coerced, List},
|
||||
Array, Ctx, Exception, Result, Value,
|
||||
};
|
||||
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
|
||||
pub use headers::Headers as HeadersClass;
|
||||
|
||||
#[derive(Clone, Trace)]
|
||||
#[js::class]
|
||||
pub struct Headers {
|
||||
#[qjs(skip_trace)]
|
||||
pub(crate) inner: HeaderMap,
|
||||
}
|
||||
#[bind(object, public)]
|
||||
#[quickjs(bare)]
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(unused_variables)]
|
||||
#[allow(clippy::module_inception)]
|
||||
mod headers {
|
||||
use std::{cell::RefCell, str::FromStr};
|
||||
|
||||
#[js::methods]
|
||||
impl Headers {
|
||||
// ------------------------------
|
||||
// Constructor
|
||||
// ------------------------------
|
||||
use js::{function::Rest, prelude::Coerced, Array, Ctx, Exception, Result, Value};
|
||||
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
|
||||
|
||||
#[qjs(constructor)]
|
||||
pub fn new<'js>(ctx: Ctx<'js>, init: Value<'js>) -> Result<Self> {
|
||||
Headers::new_inner(&ctx, init)
|
||||
#[derive(Clone)]
|
||||
#[quickjs(cloneable)]
|
||||
#[allow(dead_code)]
|
||||
pub struct Headers {
|
||||
pub(crate) inner: RefCell<HeaderMap>,
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// Instance methods
|
||||
// ------------------------------
|
||||
impl Headers {
|
||||
// ------------------------------
|
||||
// Constructor
|
||||
// ------------------------------
|
||||
|
||||
// Convert the object to a string
|
||||
#[qjs(rename = "toString")]
|
||||
pub fn js_to_string(&self) -> String {
|
||||
String::from("[object Header]")
|
||||
#[quickjs(constructor)]
|
||||
pub fn new<'js>(ctx: Ctx<'js>, init: Value<'js>, args: Rest<()>) -> Result<Self> {
|
||||
Headers::new_inner(ctx, init)
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
// Adds or appends a new value to a header
|
||||
pub fn append(&mut self, ctx: Ctx<'_>, key: String, val: String) -> Result<()> {
|
||||
self.append_inner(&ctx, &key, &val)
|
||||
}
|
||||
#[quickjs(skip)]
|
||||
impl Headers {
|
||||
pub fn from_map(map: HeaderMap) -> Self {
|
||||
Self {
|
||||
inner: RefCell::new(map),
|
||||
}
|
||||
}
|
||||
|
||||
// Deletes a header from the header set
|
||||
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_empty() -> Self {
|
||||
Self::from_map(HeaderMap::new())
|
||||
}
|
||||
|
||||
// Returns all header entries in the header set
|
||||
pub fn entries(&self) -> Vec<List<(String, String)>> {
|
||||
let mut res = Vec::<List<(String, String)>>::with_capacity(self.inner.len());
|
||||
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 res = Self::new_empty();
|
||||
|
||||
for (k, v) in self.inner.iter() {
|
||||
let k = k.as_str();
|
||||
if Some(k) == res.last().map(|x| x.0 .0.as_str()) {
|
||||
let ent = res.last_mut().unwrap();
|
||||
ent.0 .1.push_str(", ");
|
||||
// Header value came from a string, so it should also be able to be cast back
|
||||
// to a string
|
||||
ent.0 .1.push_str(v.to_str().unwrap());
|
||||
// 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 {
|
||||
res.push(List((k.to_owned(), v.to_str().unwrap().to_owned())));
|
||||
return Err(Exception::throw_type(ctx, INVALID_ERROR));
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
fn append_inner(&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.
|
||||
|
||||
// Returns all values of a header in the header set
|
||||
pub fn get(&self, ctx: Ctx<'_>, key: String) -> 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 all = self.inner.get_all(&key);
|
||||
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}"),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
// 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());
|
||||
self.inner.borrow_mut().append(key, val);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -313,7 +327,7 @@ mod test {
|
|||
});
|
||||
assert.seq(headers.get("f"), "g");
|
||||
assert.seq(headers.get("h"), "j");
|
||||
"#).catch(&ctx).unwrap();
|
||||
"#).catch(ctx).unwrap();
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
|
|
@ -1,9 +1,18 @@
|
|||
//! Request class implementation
|
||||
|
||||
use js::{class::Trace, prelude::Coerced, Class, Ctx, Exception, FromJs, Object, Result, Value};
|
||||
//!
|
||||
use js::{
|
||||
bind,
|
||||
class::{HasRefs, RefsMarker},
|
||||
prelude::Coerced,
|
||||
Class, Ctx, Exception, FromJs, Object, Persistent, Result, Value,
|
||||
};
|
||||
use reqwest::Method;
|
||||
|
||||
use crate::fnc::script::fetch::{body::Body, RequestError};
|
||||
use crate::fnc::script::fetch::{
|
||||
body::Body,
|
||||
classes::{BlobClass, HeadersClass},
|
||||
RequestError,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub enum RequestMode {
|
||||
|
@ -14,7 +23,7 @@ pub enum 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)? {
|
||||
match x.as_str() {
|
||||
"navigate" => RequestMode::Navigate,
|
||||
|
@ -50,7 +59,7 @@ pub enum 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)? {
|
||||
match x.as_str() {
|
||||
"omit" => RequestCredentials::Omit,
|
||||
|
@ -87,7 +96,7 @@ pub enum 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)? {
|
||||
match x.as_str() {
|
||||
"default" => RequestCache::Default,
|
||||
|
@ -127,7 +136,7 @@ pub enum 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)? {
|
||||
match x.as_str() {
|
||||
"follow" => RequestRedirect::Follow,
|
||||
|
@ -167,7 +176,7 @@ pub enum 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)? {
|
||||
match x.as_str() {
|
||||
"" => ReferrerPolicy::Empty,
|
||||
|
@ -204,9 +213,9 @@ impl<'js> FromJs<'js> for ReferrerPolicy {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct RequestInit<'js> {
|
||||
pub struct RequestInit {
|
||||
pub method: Method,
|
||||
pub headers: Class<'js, Headers>,
|
||||
pub headers: Persistent<Class<'static, HeadersClass>>,
|
||||
pub body: Option<Body>,
|
||||
pub referrer: String,
|
||||
pub referrer_policy: ReferrerPolicy,
|
||||
|
@ -218,15 +227,15 @@ pub struct RequestInit<'js> {
|
|||
pub keep_alive: bool,
|
||||
}
|
||||
|
||||
impl<'js> Trace<'js> for RequestInit<'js> {
|
||||
fn trace<'a>(&self, tracer: js::class::Tracer<'a, 'js>) {
|
||||
self.headers.trace(tracer);
|
||||
impl HasRefs for RequestInit {
|
||||
fn mark_refs(&self, marker: &RefsMarker) {
|
||||
self.headers.mark_refs(marker);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'js> RequestInit<'js> {
|
||||
pub fn default(ctx: Ctx<'js>) -> Result<Self> {
|
||||
let headers = Class::instance(ctx, Headers::new_empty())?;
|
||||
impl RequestInit {
|
||||
pub fn default(ctx: Ctx<'_>) -> Result<Self> {
|
||||
let headers = Persistent::save(ctx, Class::instance(ctx, HeadersClass::new_empty())?);
|
||||
Ok(RequestInit {
|
||||
method: Method::GET,
|
||||
headers,
|
||||
|
@ -242,9 +251,9 @@ impl<'js> RequestInit<'js> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn clone_js(&self, ctx: Ctx<'js>) -> Result<Self> {
|
||||
let headers = self.headers.clone();
|
||||
let headers = Class::instance(ctx.clone(), headers.borrow().clone())?;
|
||||
pub fn clone_js(&self, ctx: Ctx<'_>) -> Result<Self> {
|
||||
let headers = self.headers.clone().restore(ctx).unwrap();
|
||||
let headers = Persistent::save(ctx, Class::instance(ctx, headers.borrow().clone())?);
|
||||
|
||||
let body = self.body.as_ref().map(|x| x.clone_js(ctx));
|
||||
|
||||
|
@ -265,7 +274,7 @@ impl<'js> RequestInit<'js> {
|
|||
}
|
||||
|
||||
// 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")
|
||||
|| m.as_bytes().eq_ignore_ascii_case(b"TRACE")
|
||||
|| m.as_bytes().eq_ignore_ascii_case(b"TRACK")
|
||||
|
@ -300,8 +309,8 @@ fn normalize_method(ctx: &Ctx<'_>, m: String) -> Result<Method> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'js> FromJs<'js> for RequestInit<'js> {
|
||||
fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
|
||||
impl<'js> FromJs<'js> for RequestInit {
|
||||
fn from_js(ctx: Ctx<'js>, value: Value<'js>) -> Result<Self> {
|
||||
let object = Object::from_js(ctx, value)?;
|
||||
|
||||
let referrer = object
|
||||
|
@ -337,14 +346,15 @@ impl<'js> FromJs<'js> for RequestInit<'js> {
|
|||
}
|
||||
|
||||
let headers = if let Some(hdrs) = object.get::<_, Option<Object>>("headers")? {
|
||||
if let Some(cls) = Class::<Headers>::from_object(hdrs.clone()) {
|
||||
if let Ok(cls) = Class::<HeadersClass>::from_object(hdrs.clone()) {
|
||||
cls
|
||||
} else {
|
||||
Class::instance(ctx.clone(), Headers::new_inner(ctx, hdrs.into_value())?)?
|
||||
Class::instance(ctx, HeadersClass::new_inner(ctx, hdrs.into_value())?)?
|
||||
}
|
||||
} else {
|
||||
Class::instance(ctx.clone(), Headers::new_empty())?
|
||||
Class::instance(ctx, HeadersClass::new_empty())?
|
||||
};
|
||||
let headers = Persistent::save(ctx, headers);
|
||||
|
||||
let body = object.get::<_, Option<Body>>("body")?;
|
||||
|
||||
|
@ -366,173 +376,192 @@ impl<'js> FromJs<'js> for RequestInit<'js> {
|
|||
|
||||
pub use request::Request as RequestClass;
|
||||
|
||||
pub use super::*;
|
||||
#[bind(object, public)]
|
||||
#[quickjs(bare)]
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(unused_variables)]
|
||||
#[allow(clippy::module_inception)]
|
||||
mod request {
|
||||
|
||||
use bytes::Bytes;
|
||||
use js::function::Opt;
|
||||
// TODO: change implementation based on features.
|
||||
use reqwest::{header::HeaderName, Url};
|
||||
pub use super::*;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[js::class]
|
||||
#[derive(Trace)]
|
||||
pub struct Request<'js> {
|
||||
#[qjs(skip_trace)]
|
||||
pub(crate) url: Url,
|
||||
pub(crate) init: RequestInit<'js>,
|
||||
}
|
||||
use bytes::Bytes;
|
||||
use js::{
|
||||
function::{Opt, Rest},
|
||||
Class, Ctx, Exception, HasRefs, Result, Value,
|
||||
};
|
||||
// TODO: change implementation based on features.
|
||||
use reqwest::{header::HeaderName, Url};
|
||||
|
||||
#[js::methods]
|
||||
impl<'js> Request<'js> {
|
||||
// ------------------------------
|
||||
// Constructor
|
||||
// ------------------------------
|
||||
|
||||
#[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",
|
||||
))
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
#[derive(HasRefs)]
|
||||
#[quickjs(has_refs)]
|
||||
pub struct Request {
|
||||
pub(crate) url: Url,
|
||||
#[quickjs(has_refs)]
|
||||
pub(crate) init: RequestInit,
|
||||
}
|
||||
|
||||
/// Clone the response, teeing any possible underlying streams.
|
||||
#[qjs(rename = "clone")]
|
||||
pub fn clone_js(&self, ctx: Ctx<'js>) -> Result<Self> {
|
||||
Ok(Self {
|
||||
url: self.url.clone(),
|
||||
init: self.init.clone_js(ctx)?,
|
||||
})
|
||||
}
|
||||
impl Request {
|
||||
// ------------------------------
|
||||
// Constructor
|
||||
// ------------------------------
|
||||
|
||||
// ------------------------------
|
||||
// Instance properties
|
||||
// ------------------------------
|
||||
#[qjs(get, rename = "body_used")]
|
||||
pub fn body_used(&self) -> bool {
|
||||
self.init.body.as_ref().map(Body::used).unwrap_or(true)
|
||||
}
|
||||
|
||||
#[qjs(get)]
|
||||
pub fn method(&self) -> String {
|
||||
self.init.method.to_string()
|
||||
}
|
||||
|
||||
#[qjs(get)]
|
||||
pub fn url(&self) -> String {
|
||||
self.url.to_string()
|
||||
}
|
||||
|
||||
#[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}")))
|
||||
#[quickjs(constructor)]
|
||||
pub fn new<'js>(
|
||||
ctx: Ctx<'js>,
|
||||
input: Value<'js>,
|
||||
init: Opt<RequestInit>,
|
||||
args: Rest<()>,
|
||||
) -> 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), 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",
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a promise with the request 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 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()
|
||||
};
|
||||
/// Clone the response, teeing any possible underlying streams.
|
||||
#[quickjs(rename = "clone")]
|
||||
pub fn clone_js(&self, ctx: Ctx<'_>, _rest: Rest<()>) -> Result<Self> {
|
||||
Ok(Self {
|
||||
url: self.url.clone(),
|
||||
init: self.init.clone_js(ctx)?,
|
||||
})
|
||||
}
|
||||
|
||||
let data = self.take_buffer(&ctx).await?;
|
||||
Ok(Blob {
|
||||
mime,
|
||||
data,
|
||||
})
|
||||
}
|
||||
// ------------------------------
|
||||
// Instance properties
|
||||
// ------------------------------
|
||||
#[quickjs(get)]
|
||||
pub fn bodyUsed(&self) -> bool {
|
||||
self.init.body.as_ref().map(Body::used).unwrap_or(true)
|
||||
}
|
||||
|
||||
// Returns a promise with the request 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"))
|
||||
}
|
||||
#[quickjs(get)]
|
||||
pub fn method(&self) -> String {
|
||||
self.init.method.to_string()
|
||||
}
|
||||
|
||||
// Returns a promise with the request 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)
|
||||
}
|
||||
#[quickjs(get)]
|
||||
pub fn url(&self) -> String {
|
||||
self.url.to_string()
|
||||
}
|
||||
|
||||
// Returns a promise with the request body as text
|
||||
pub async fn text(&self, ctx: Ctx<'js>) -> Result<String> {
|
||||
let data = self.take_buffer(&ctx).await?;
|
||||
#[quickjs(get)]
|
||||
pub fn headers<'js>(&self, ctx: Ctx<'js>) -> Class<'js, HeadersClass> {
|
||||
self.init.headers.clone().restore(ctx).unwrap()
|
||||
}
|
||||
|
||||
// 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())
|
||||
#[quickjs(get)]
|
||||
pub fn referrer(&self, ctx: Ctx<'_>) -> String {
|
||||
self.init.referrer.clone()
|
||||
}
|
||||
// TODO
|
||||
|
||||
// ------------------------------
|
||||
// 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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -615,7 +644,7 @@ mod test {
|
|||
assert.seq(await req_2.text(),"some text");
|
||||
|
||||
})()
|
||||
"#).catch(&ctx).unwrap().await.catch(&ctx).unwrap();
|
||||
"#).catch(ctx).unwrap().await.catch(ctx).unwrap();
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
|
|
@ -1,33 +1,34 @@
|
|||
use std::string::String as StdString;
|
||||
|
||||
use js::{
|
||||
class::{Trace, Tracer},
|
||||
class::{HasRefs, RefsMarker},
|
||||
prelude::*,
|
||||
Class, Ctx, Exception, FromJs, Object, Result, Value,
|
||||
Class, Ctx, Exception, FromJs, Object, Persistent, Result, Value,
|
||||
};
|
||||
|
||||
use crate::fnc::script::fetch::{classes::Headers, util};
|
||||
use crate::fnc::script::fetch::{classes::HeadersClass, util};
|
||||
|
||||
/// Struct containing data from the init argument from the Response constructor.
|
||||
#[derive(Clone)]
|
||||
pub struct ResponseInit<'js> {
|
||||
pub struct ResponseInit {
|
||||
// u16 instead of reqwest::StatusCode since javascript allows non valid status codes in some
|
||||
// circumstances.
|
||||
pub status: u16,
|
||||
pub status_text: StdString,
|
||||
pub headers: Class<'js, Headers>,
|
||||
pub headers: Persistent<Class<'static, HeadersClass>>,
|
||||
}
|
||||
|
||||
impl<'js> Trace<'js> for ResponseInit<'js> {
|
||||
fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
|
||||
self.headers.trace(tracer);
|
||||
impl HasRefs for ResponseInit {
|
||||
fn mark_refs(&self, marker: &RefsMarker) {
|
||||
self.headers.mark_refs(marker);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'js> ResponseInit<'js> {
|
||||
impl ResponseInit {
|
||||
/// Returns a ResponseInit object with all values as the default value.
|
||||
pub fn default(ctx: Ctx<'js>) -> Result<Self> {
|
||||
let headers = Class::instance(ctx, Headers::new_empty())?;
|
||||
pub fn default(ctx: Ctx<'_>) -> Result<ResponseInit> {
|
||||
let headers = Class::instance(ctx, HeadersClass::new_empty())?;
|
||||
let headers = Persistent::save(ctx, headers);
|
||||
Ok(ResponseInit {
|
||||
status: 200,
|
||||
status_text: StdString::new(),
|
||||
|
@ -36,8 +37,8 @@ impl<'js> ResponseInit<'js> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'js> FromJs<'js> for ResponseInit<'js> {
|
||||
fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
|
||||
impl<'js> FromJs<'js> for ResponseInit {
|
||||
fn from_js(ctx: Ctx<'js>, value: Value<'js>) -> Result<Self> {
|
||||
let object = Object::from_js(ctx, value)?;
|
||||
|
||||
// Extract status.
|
||||
|
@ -65,11 +66,12 @@ impl<'js> FromJs<'js> for ResponseInit<'js> {
|
|||
|
||||
// Extract headers.
|
||||
let headers = if let Some(headers) = object.get::<_, Option<Value>>("headers")? {
|
||||
let headers = Headers::new_inner(ctx, headers)?;
|
||||
Class::instance(ctx.clone(), headers)?
|
||||
let headers = HeadersClass::new_inner(ctx, headers)?;
|
||||
Class::instance(ctx, headers)?
|
||||
} else {
|
||||
Class::instance(ctx.clone(), Headers::new_empty())?
|
||||
Class::instance(ctx, HeadersClass::new_empty())?
|
||||
};
|
||||
let headers = Persistent::save(ctx, headers);
|
||||
|
||||
Ok(ResponseInit {
|
||||
status,
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
//! Response class implementation
|
||||
|
||||
use js::bind;
|
||||
|
||||
mod init;
|
||||
use bytes::Bytes;
|
||||
pub use init::ResponseInit;
|
||||
|
||||
use js::{class::Trace, prelude::Opt, ArrayBuffer, Class, Ctx, Exception, Result, Value};
|
||||
pub use response::Response as ResponseClass;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy)]
|
||||
|
@ -17,284 +18,304 @@ pub enum ResponseType {
|
|||
OpaqueRedirect,
|
||||
}
|
||||
|
||||
use reqwest::Url;
|
||||
#[bind(object, public)]
|
||||
#[quickjs(bare)]
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(unused_variables)]
|
||||
#[allow(clippy::module_inception)]
|
||||
pub mod response {
|
||||
|
||||
use crate::fnc::script::fetch::{
|
||||
body::{Body, BodyKind},
|
||||
util, RequestError,
|
||||
};
|
||||
use crate::fnc::script::fetch::{
|
||||
body::{Body, BodyKind},
|
||||
classes::{BlobClass, HeadersClass},
|
||||
util, RequestError,
|
||||
};
|
||||
|
||||
use super::{Blob, Headers};
|
||||
use super::{ResponseInit, ResponseType};
|
||||
use bytes::Bytes;
|
||||
use js::{
|
||||
function::{Opt, Rest},
|
||||
ArrayBuffer, Class, Ctx, Exception, HasRefs, Persistent, Result, Value,
|
||||
};
|
||||
use reqwest::Url;
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Trace)]
|
||||
#[js::class]
|
||||
pub struct Response<'js> {
|
||||
#[qjs(skip_trace)]
|
||||
pub(crate) body: Body,
|
||||
pub(crate) init: ResponseInit<'js>,
|
||||
#[qjs(skip_trace)]
|
||||
pub(crate) url: Option<Url>,
|
||||
#[qjs(skip_trace)]
|
||||
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,
|
||||
})
|
||||
#[derive(HasRefs)]
|
||||
#[allow(dead_code)]
|
||||
#[quickjs(has_refs)]
|
||||
pub struct Response {
|
||||
#[quickjs(has_refs)]
|
||||
pub(crate) body: Body,
|
||||
#[quickjs(has_refs)]
|
||||
pub(crate) init: ResponseInit,
|
||||
pub(crate) url: Option<Url>,
|
||||
pub(crate) r#type: ResponseType,
|
||||
pub(crate) was_redirected: bool,
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// Instance properties
|
||||
// ------------------------------
|
||||
impl Response {
|
||||
// ------------------------------
|
||||
// Constructor
|
||||
// ------------------------------
|
||||
|
||||
#[qjs(get, rename = "bodyUsed")]
|
||||
pub fn body_used(&self) -> bool {
|
||||
self.body.used()
|
||||
}
|
||||
|
||||
#[qjs(get)]
|
||||
pub fn status(&self) -> u16 {
|
||||
self.init.status
|
||||
}
|
||||
|
||||
#[qjs(get)]
|
||||
pub fn ok(&self) -> bool {
|
||||
util::is_ok_status(self.init.status)
|
||||
}
|
||||
|
||||
#[qjs(get)]
|
||||
pub fn redirected(&self) -> bool {
|
||||
self.was_redirected
|
||||
}
|
||||
|
||||
#[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()
|
||||
#[quickjs(constructor)]
|
||||
pub fn new(
|
||||
ctx: Ctx<'_>,
|
||||
body: Opt<Option<Body>>,
|
||||
init: Opt<ResponseInit>,
|
||||
args: Rest<()>,
|
||||
) -> Result<Self> {
|
||||
let init = match init.into_inner() {
|
||||
Some(x) => x,
|
||||
None => ResponseInit::default(ctx)?,
|
||||
};
|
||||
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();
|
||||
|
||||
// ------------------------------
|
||||
// Instance methods
|
||||
// ------------------------------
|
||||
|
||||
// Convert the object to a string
|
||||
#[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,
|
||||
Ok(Response {
|
||||
body,
|
||||
init,
|
||||
url: None,
|
||||
r#type: ResponseType::Default,
|
||||
was_redirected: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[qjs(skip)]
|
||||
async fn take_buffer(&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}")))
|
||||
// ------------------------------
|
||||
// Instance properties
|
||||
// ------------------------------
|
||||
|
||||
#[quickjs(get)]
|
||||
pub fn bodyUsed(&self) -> bool {
|
||||
self.body.used()
|
||||
}
|
||||
|
||||
#[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
|
||||
// ------------------------------
|
||||
|
||||
Ok(Response {
|
||||
url: Some(url),
|
||||
body: Body::new(),
|
||||
init: ResponseInit {
|
||||
status,
|
||||
status_text: String::new(),
|
||||
headers,
|
||||
},
|
||||
r#type: ResponseType::Default,
|
||||
was_redirected: false,
|
||||
})
|
||||
// Convert the object to a string
|
||||
pub fn toString(&self, args: Rest<()>) -> String {
|
||||
String::from("[object Response]")
|
||||
}
|
||||
|
||||
// Creates a copy of the request object
|
||||
#[quickjs(rename = "clone")]
|
||||
pub fn clone_js(&self, ctx: Ctx<'_>, args: Rest<()>) -> Response {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -367,7 +388,7 @@ mod test {
|
|||
|
||||
|
||||
})()
|
||||
"#).catch(&ctx).unwrap().await.catch(&ctx).unwrap();
|
||||
"#).catch(ctx).unwrap().await.catch(ctx).unwrap();
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
|
|
@ -2,28 +2,29 @@
|
|||
|
||||
use crate::fnc::script::fetch::{
|
||||
body::{Body, BodyData, BodyKind},
|
||||
classes::{self, Request, RequestInit, Response, ResponseInit, ResponseType},
|
||||
classes::{
|
||||
self, HeadersClass, RequestClass, RequestInit, ResponseClass, ResponseInit, ResponseType,
|
||||
},
|
||||
RequestError,
|
||||
};
|
||||
use futures::TryStreamExt;
|
||||
use js::{function::Opt, Class, Ctx, Exception, Result, Value};
|
||||
use js::{bind, function::Opt, prelude::*, Class, Ctx, Exception, Persistent, Result, Value};
|
||||
use reqwest::{
|
||||
header::{HeaderValue, CONTENT_TYPE},
|
||||
redirect, Body as ReqBody,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::classes::Headers;
|
||||
|
||||
#[js::function]
|
||||
#[bind(object, public)]
|
||||
#[allow(unused_variables)]
|
||||
pub async fn fetch<'js>(
|
||||
ctx: Ctx<'js>,
|
||||
input: Value<'js>,
|
||||
init: Opt<RequestInit<'js>>,
|
||||
) -> Result<Response<'js>> {
|
||||
init: Opt<RequestInit>,
|
||||
args: Rest<()>,
|
||||
) -> Result<ResponseClass> {
|
||||
// Create a request from the input.
|
||||
let js_req = Request::new(ctx.clone(), input, init)?;
|
||||
let js_req = RequestClass::new(ctx, input, init, args)?;
|
||||
|
||||
let url = js_req.url;
|
||||
|
||||
|
@ -31,9 +32,9 @@ pub async fn fetch<'js>(
|
|||
|
||||
// SurrealDB Implementation keeps all javascript parts inside the context::with scope so this
|
||||
// unwrap should never panic.
|
||||
let headers = js_req.init.headers;
|
||||
let headers = js_req.init.headers.restore(ctx).unwrap();
|
||||
let headers = headers.borrow();
|
||||
let mut headers = headers.inner.clone();
|
||||
let mut headers = headers.inner.borrow().clone();
|
||||
|
||||
let redirect = js_req.init.request_redirect;
|
||||
|
||||
|
@ -54,7 +55,7 @@ pub async fn fetch<'js>(
|
|||
});
|
||||
|
||||
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.
|
||||
|
@ -69,7 +70,7 @@ pub async fn fetch<'js>(
|
|||
let body = ReqBody::from(x);
|
||||
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 {
|
||||
BodyKind::Buffer => {}
|
||||
|
@ -93,11 +94,12 @@ pub async fn fetch<'js>(
|
|||
.headers(headers)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|e| Exception::throw_type(&ctx, &e.to_string()))?;
|
||||
.map_err(|e| Exception::throw_type(ctx, &e.to_string()))?;
|
||||
|
||||
// Extract the headers
|
||||
let headers = Headers::from_map(response.headers().clone());
|
||||
let headers = HeadersClass::from_map(response.headers().clone());
|
||||
let headers = Class::instance(ctx, headers)?;
|
||||
let headers = Persistent::save(ctx, headers);
|
||||
let init = ResponseInit {
|
||||
headers,
|
||||
status: response.status().as_u16(),
|
||||
|
@ -109,7 +111,7 @@ pub async fn fetch<'js>(
|
|||
BodyKind::Buffer,
|
||||
response.bytes_stream().map_err(Arc::new).map_err(RequestError::Reqwest),
|
||||
);
|
||||
let response = Response {
|
||||
let response = ResponseClass {
|
||||
body,
|
||||
init,
|
||||
url: Some(url),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::{error::Error, fmt, sync::Arc};
|
||||
|
||||
use js::{Class, Ctx, Result};
|
||||
use js::{Ctx, Result};
|
||||
|
||||
mod body;
|
||||
mod classes;
|
||||
|
@ -9,7 +9,7 @@ mod stream;
|
|||
mod util;
|
||||
|
||||
use classes::{Blob, FormData, Headers, Request, Response};
|
||||
use func::js_fetch;
|
||||
use func::Fetch;
|
||||
|
||||
// Anoyingly errors aren't clone,
|
||||
// But with how we implement streams RequestError must be clone.
|
||||
|
@ -30,14 +30,15 @@ impl fmt::Display for RequestError {
|
|||
impl Error for RequestError {}
|
||||
|
||||
/// Register the fetch types in the context.
|
||||
pub fn register(ctx: &Ctx<'_>) -> Result<()> {
|
||||
pub fn register(ctx: Ctx<'_>) -> Result<()> {
|
||||
let globals = ctx.globals();
|
||||
globals.set("fetch", js_fetch)?;
|
||||
Class::<Response>::define(&globals)?;
|
||||
Class::<Request>::define(&globals)?;
|
||||
Class::<Blob>::define(&globals)?;
|
||||
Class::<FormData>::define(&globals)?;
|
||||
Class::<Headers>::define(&globals)?;
|
||||
globals.init_def::<Fetch>()?;
|
||||
|
||||
globals.init_def::<Response>()?;
|
||||
globals.init_def::<Request>()?;
|
||||
globals.init_def::<Blob>()?;
|
||||
globals.init_def::<FormData>()?;
|
||||
globals.init_def::<Headers>()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -54,9 +55,9 @@ mod test {
|
|||
let ctx = js::AsyncContext::full(&rt).await.unwrap();
|
||||
|
||||
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) => {
|
||||
arg.forEach(x => {
|
||||
if (!x) {
|
||||
|
@ -90,7 +91,7 @@ mod test {
|
|||
}
|
||||
throw new Error(`Code which should throw, didnt: \n${cb}`)
|
||||
}
|
||||
").unwrap();
|
||||
"#).unwrap();
|
||||
|
||||
$($t)*
|
||||
}).await;
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
//! stub implementations for the fetch API when `http` is not enabled.
|
||||
|
||||
/// The stub implementations for the fetch API when `http` is not enabled.
|
||||
use js::{bind, prelude::*, Ctx, Exception, Result};
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -5,7 +5,6 @@ use crate::sql::object::Object;
|
|||
use crate::sql::value::Value;
|
||||
use crate::sql::Id;
|
||||
use chrono::{TimeZone, Utc};
|
||||
use js::prelude::This;
|
||||
use js::Ctx;
|
||||
use js::Error;
|
||||
use js::Exception;
|
||||
|
@ -21,7 +20,7 @@ fn check_nul(s: &str) -> Result<(), Error> {
|
|||
}
|
||||
|
||||
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 {
|
||||
val if val.type_name() == "null" => Ok(Value::Null),
|
||||
val if val.type_name() == "undefined" => Ok(Value::None),
|
||||
|
@ -50,15 +49,14 @@ impl<'js> FromJs<'js> for Value {
|
|||
// Check to see if this object is an error
|
||||
if v.is_error() {
|
||||
let e: String = v.get("message")?;
|
||||
let (Ok(e) | Err(e)) =
|
||||
Exception::from_message(ctx.clone(), &e).map(|x| x.throw());
|
||||
let (Ok(e) | Err(e)) = Exception::from_message(ctx, &e).map(|x| x.throw());
|
||||
return Err(e);
|
||||
}
|
||||
// Check to see if this object is a record
|
||||
if (v).instance_of::<classes::record::Record>() {
|
||||
let v = v.into_class::<classes::record::Record>().unwrap();
|
||||
if (v).instance_of::<classes::record::record::Record>() {
|
||||
let v = v.into_instance::<classes::record::record::Record>().unwrap();
|
||||
let borrow = v.borrow();
|
||||
let v: &classes::record::Record = &borrow;
|
||||
let v: &classes::record::record::Record = &borrow;
|
||||
check_nul(&v.value.tb)?;
|
||||
if let Id::String(s) = &v.value.id {
|
||||
check_nul(s)?;
|
||||
|
@ -66,20 +64,20 @@ impl<'js> FromJs<'js> for Value {
|
|||
return Ok(v.value.clone().into());
|
||||
}
|
||||
// Check to see if this object is a duration
|
||||
if (v).instance_of::<classes::duration::Duration>() {
|
||||
let v = v.into_class::<classes::duration::Duration>().unwrap();
|
||||
if (v).instance_of::<classes::duration::duration::Duration>() {
|
||||
let v = v.into_instance::<classes::duration::duration::Duration>().unwrap();
|
||||
let borrow = v.borrow();
|
||||
let v: &classes::duration::Duration = &borrow;
|
||||
let v: &classes::duration::duration::Duration = &borrow;
|
||||
return match &v.value {
|
||||
Some(v) => Ok(v.clone().into()),
|
||||
None => Ok(Value::None),
|
||||
};
|
||||
}
|
||||
// Check to see if this object is a uuid
|
||||
if (v).instance_of::<classes::uuid::Uuid>() {
|
||||
let v = v.into_class::<classes::uuid::Uuid>().unwrap();
|
||||
if (v).instance_of::<classes::uuid::uuid::Uuid>() {
|
||||
let v = v.into_instance::<classes::uuid::uuid::Uuid>().unwrap();
|
||||
let borrow = v.borrow();
|
||||
let v: &classes::uuid::Uuid = &borrow;
|
||||
let v: &classes::uuid::uuid::Uuid = &borrow;
|
||||
return match &v.value {
|
||||
Some(v) => Ok(v.clone().into()),
|
||||
None => Ok(Value::None),
|
||||
|
@ -89,7 +87,7 @@ impl<'js> FromJs<'js> for Value {
|
|||
let date: js::Object = ctx.globals().get("Date")?;
|
||||
if (v).is_instance_of(&date) {
|
||||
let f: js::Function = v.get("getTime")?;
|
||||
let m: i64 = f.call((This(v),))?;
|
||||
let m: i64 = f.call((js::prelude::This(v),))?;
|
||||
let d = Utc.timestamp_millis_opt(m).unwrap();
|
||||
return Ok(Datetime::from(d).into());
|
||||
}
|
||||
|
|
|
@ -1,44 +1,32 @@
|
|||
// Specify the imports
|
||||
use crate::sql::value::Value;
|
||||
use js::{prelude::Rest, Ctx, Object, Result};
|
||||
/// Log the input values as INFO
|
||||
#[js::function]
|
||||
pub fn log(args: Rest<Value>) {
|
||||
info!("{}", args.iter().map(|v| v.to_raw_string()).collect::<Vec<String>>().join(" "));
|
||||
}
|
||||
/// Log the input values as INFO
|
||||
#[js::function]
|
||||
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]
|
||||
pub fn warn(args: Rest<Value>) {
|
||||
warn!("{}", args.iter().map(|v| v.to_raw_string()).collect::<Vec<String>>().join(" "));
|
||||
}
|
||||
/// Log the input values as ERROR
|
||||
#[js::function]
|
||||
pub fn error(args: Rest<Value>) {
|
||||
error!("{}", args.iter().map(|v| v.to_raw_string()).collect::<Vec<String>>().join(" "));
|
||||
}
|
||||
/// Log the input values as DEBUG
|
||||
#[js::function]
|
||||
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
|
||||
#[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)
|
||||
#[js::bind(object, public)]
|
||||
#[quickjs(rename = "console")]
|
||||
#[allow(clippy::module_inception)]
|
||||
pub mod console {
|
||||
// Specify the imports
|
||||
use crate::sql::value::Value;
|
||||
use js::prelude::Rest;
|
||||
/// Log the input values as INFO
|
||||
pub fn log(args: Rest<Value>) {
|
||||
info!("{}", args.iter().map(|v| v.to_raw_string()).collect::<Vec<String>>().join(" "));
|
||||
}
|
||||
/// Log the input values as INFO
|
||||
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
|
||||
pub fn warn(args: Rest<Value>) {
|
||||
warn!("{}", args.iter().map(|v| v.to_raw_string()).collect::<Vec<String>>().join(" "));
|
||||
}
|
||||
/// Log the input values as ERROR
|
||||
pub fn error(args: Rest<Value>) {
|
||||
error!("{}", args.iter().map(|v| v.to_raw_string()).collect::<Vec<String>>().join(" "));
|
||||
}
|
||||
/// Log the input values as DEBUG
|
||||
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>) {
|
||||
trace!("{}", args.iter().map(|v| v.to_raw_string()).collect::<Vec<String>>().join(" "));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,58 +12,58 @@ use js::Object;
|
|||
use js::Undefined;
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Value::Null => Null.into_js(ctx),
|
||||
Value::None => Undefined.into_js(ctx),
|
||||
Value::Bool(boolean) => Ok(js::Value::new_bool(ctx.clone(), *boolean)),
|
||||
Value::Strand(v) => js::String::from_str(ctx.clone(), v)?.into_js(ctx),
|
||||
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.clone(), *v)),
|
||||
Value::Bool(boolean) => Ok(js::Value::new_bool(ctx, *boolean)),
|
||||
Value::Strand(v) => js::String::from_str(ctx, v)?.into_js(ctx),
|
||||
Value::Number(Number::Int(v)) => Ok(js::Value::new_int(ctx, *v as i32)),
|
||||
Value::Number(Number::Float(v)) => Ok(js::Value::new_float(ctx, *v)),
|
||||
&Value::Number(Number::Decimal(v)) => match decimal_is_integer(&v) {
|
||||
true => Ok(js::Value::new_int(ctx.clone(), v.try_into().unwrap_or_default())),
|
||||
false => Ok(js::Value::new_float(ctx.clone(), v.try_into().unwrap_or_default())),
|
||||
true => Ok(js::Value::new_int(ctx, v.try_into().unwrap_or_default())),
|
||||
false => Ok(js::Value::new_float(ctx, v.try_into().unwrap_or_default())),
|
||||
},
|
||||
Value::Datetime(v) => {
|
||||
let date: js::function::Constructor = ctx.globals().get("Date")?;
|
||||
let date: js::Function = ctx.globals().get("Date")?;
|
||||
date.construct((v.0.timestamp_millis(),))
|
||||
}
|
||||
Value::Thing(v) => Ok(Class::<classes::record::Record>::instance(
|
||||
ctx.clone(),
|
||||
classes::record::Record {
|
||||
Value::Thing(v) => Ok(Class::<classes::record::record::Record>::instance(
|
||||
ctx,
|
||||
classes::record::record::Record {
|
||||
value: v.to_owned(),
|
||||
},
|
||||
)?
|
||||
.into_value()),
|
||||
Value::Duration(v) => Ok(Class::<classes::duration::Duration>::instance(
|
||||
ctx.clone(),
|
||||
classes::duration::Duration {
|
||||
Value::Duration(v) => Ok(Class::<classes::duration::duration::Duration>::instance(
|
||||
ctx,
|
||||
classes::duration::duration::Duration {
|
||||
value: Some(v.to_owned()),
|
||||
},
|
||||
)?
|
||||
.into_value()),
|
||||
Value::Uuid(v) => Ok(Class::<classes::uuid::Uuid>::instance(
|
||||
ctx.clone(),
|
||||
classes::uuid::Uuid {
|
||||
Value::Uuid(v) => Ok(Class::<classes::uuid::uuid::Uuid>::instance(
|
||||
ctx,
|
||||
classes::uuid::uuid::Uuid {
|
||||
value: Some(v.to_owned()),
|
||||
},
|
||||
)?
|
||||
.into_value()),
|
||||
Value::Array(v) => {
|
||||
let x = Array::new(ctx.clone())?;
|
||||
let x = Array::new(ctx)?;
|
||||
for (i, v) in v.iter().enumerate() {
|
||||
x.set(i, v)?;
|
||||
}
|
||||
x.into_js(ctx)
|
||||
}
|
||||
Value::Object(v) => {
|
||||
let x = Object::new(ctx.clone())?;
|
||||
let x = Object::new(ctx)?;
|
||||
for (k, v) in v.iter() {
|
||||
x.set(k, v)?;
|
||||
}
|
||||
|
|
|
@ -50,24 +50,23 @@ pub async fn run(
|
|||
|
||||
// Attempt to execute the script
|
||||
async_with!(ctx => |ctx|{
|
||||
let res = async{
|
||||
let res = async move {
|
||||
// register all classes to the runtime.
|
||||
// Get the context global object
|
||||
let global = ctx.globals();
|
||||
// Register the surrealdb module as a global object
|
||||
global.set(
|
||||
"surrealdb",
|
||||
Module::evaluate_def::<modules::surrealdb::Package, _>(ctx.clone(), "surrealdb")?
|
||||
Module::evaluate_def::<modules::surrealdb::Package, _>(ctx, "surrealdb")?
|
||||
.get::<_, js::Value>("default")?,
|
||||
)?;
|
||||
fetch::register(&ctx)?;
|
||||
let console = globals::console::console(&ctx)?;
|
||||
fetch::register(ctx)?;
|
||||
// Register the console function to the globals
|
||||
global.set("console",console)?;
|
||||
global.init_def::<globals::console::Console>()?;
|
||||
// Register the special SurrealDB types as classes
|
||||
classes::init(&ctx)?;
|
||||
classes::init(ctx)?;
|
||||
// Attempt to compile the script
|
||||
let res = ctx.clone().compile("script", src)?;
|
||||
let res = ctx.compile("script", src)?;
|
||||
// Attempt to fetch the main export
|
||||
let fnc = res.get::<_, Function>("default")?;
|
||||
// Extract the doc if any
|
||||
|
@ -77,7 +76,7 @@ pub async fn run(
|
|||
promise.await
|
||||
}.await;
|
||||
|
||||
res.catch(&ctx).map_err(Error::from)
|
||||
res.catch(ctx).map_err(Error::from)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
|
|
@ -54,8 +54,8 @@ macro_rules! impl_module_def {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn evaluate<'js>(ctx: &js::Ctx<'js>, exports: &mut js::module::Exports<'js>) -> js::Result<()> {
|
||||
let default = js::Object::new(ctx.clone())?;
|
||||
fn evaluate<'js>(ctx: js::Ctx<'js>, exports: &mut js::module::Exports<'js>) -> js::Result<()> {
|
||||
let default = js::Object::new(ctx)?;
|
||||
$(
|
||||
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)?))?;
|
||||
|
|
|
@ -1,31 +1,13 @@
|
|||
use js::{
|
||||
module::{Declarations, Exports, ModuleDef},
|
||||
Result,
|
||||
};
|
||||
|
||||
/// Get the target system architecture
|
||||
#[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(())
|
||||
#[js::bind(module, public)]
|
||||
#[quickjs(bare)]
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub mod package {
|
||||
/// Get the target system architecture
|
||||
pub fn arch() -> &'static str {
|
||||
crate::env::arch()
|
||||
}
|
||||
|
||||
fn evaluate<'js>(_ctx: &js::Ctx<'js>, exports: &mut Exports<'js>) -> Result<()> {
|
||||
exports.export("arch", js_arch)?;
|
||||
exports.export("platform", js_platform)?;
|
||||
Ok(())
|
||||
/// Get the target operating system
|
||||
pub fn platform() -> &'static str {
|
||||
crate::env::os()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,9 +12,9 @@ impl_module_def!(
|
|||
"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
|
||||
D: ModuleDef,
|
||||
{
|
||||
Module::evaluate_def::<D, _>(ctx.clone(), name)?.get::<_, js::Value>("default")
|
||||
Module::evaluate_def::<D, _>(ctx, name)?.get::<_, js::Value>("default")
|
||||
}
|
||||
|
|
|
@ -2938,7 +2938,7 @@ mod tests {
|
|||
assert_eq!(24, std::mem::size_of::<crate::sql::table::Table>());
|
||||
assert_eq!(56, std::mem::size_of::<crate::sql::thing::Thing>());
|
||||
assert_eq!(40, std::mem::size_of::<crate::sql::model::Model>());
|
||||
assert_eq!(32, std::mem::size_of::<crate::sql::regex::Regex>());
|
||||
assert_eq!(16, 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::edges::Edges>>());
|
||||
assert_eq!(8, std::mem::size_of::<Box<crate::sql::function::Function>>());
|
||||
|
|
Loading…
Reference in a new issue