Update js functions to new rquickjs version (#2252)
This commit is contained in:
parent
1e30eb4aa1
commit
4f4339848e
24 changed files with 1362 additions and 1498 deletions
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.3.1" , package = "rquickjs", features = ["array-buffer", "bindgen", "classes", "futures", "loader", "macro", "parallel", "properties","rust-alloc"], optional = true }
|
||||
js = { version = "0.4.0-beta.0" , package = "rquickjs", features = ["array-buffer", "bindgen", "classes", "futures", "loader", "macro", "parallel", "properties","rust-alloc"], optional = true }
|
||||
jsonwebtoken = "8.3.0"
|
||||
lexicmp = "0.1.0"
|
||||
lru = "0.10.1"
|
||||
|
@ -155,4 +155,4 @@ harness = false
|
|||
|
||||
[[bench]]
|
||||
name = "index_btree"
|
||||
harness = false
|
||||
harness = false
|
||||
|
|
|
@ -1,51 +1,48 @@
|
|||
#[js::bind(object, public)]
|
||||
#[quickjs(bare)]
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(unused_variables)]
|
||||
#[allow(clippy::module_inception)]
|
||||
pub mod duration {
|
||||
use js::class::Trace;
|
||||
|
||||
use crate::sql::duration;
|
||||
use crate::sql::value::Value;
|
||||
use js::{class::Ref, function::Rest};
|
||||
use crate::sql::duration;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[quickjs(cloneable)]
|
||||
pub struct Duration {
|
||||
pub(crate) value: Option<duration::Duration>,
|
||||
#[derive(Clone, Trace)]
|
||||
#[js::class]
|
||||
pub struct Duration {
|
||||
#[qjs(skip_trace)]
|
||||
pub(crate) value: Option<duration::Duration>,
|
||||
}
|
||||
|
||||
#[js::methods]
|
||||
impl Duration {
|
||||
#[qjs(constructor)]
|
||||
pub fn new(value: String) -> Self {
|
||||
Self {
|
||||
value: duration::Duration::try_from(value).ok(),
|
||||
}
|
||||
}
|
||||
|
||||
impl Duration {
|
||||
#[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 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"),
|
||||
}
|
||||
}
|
||||
/// 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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use js::{Ctx, Result};
|
||||
use js::{Class, 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();
|
||||
globals.init_def::<duration::Duration>()?;
|
||||
globals.init_def::<record::Record>()?;
|
||||
globals.init_def::<uuid::Uuid>()?;
|
||||
Class::<duration::Duration>::define(&globals)?;
|
||||
Class::<record::Record>::define(&globals)?;
|
||||
Class::<uuid::Uuid>::define(&globals)?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,56 +1,52 @@
|
|||
#[js::bind(object, public)]
|
||||
#[quickjs(bare)]
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(unused_variables)]
|
||||
#[allow(clippy::module_inception)]
|
||||
pub mod record {
|
||||
use crate::sql::thing;
|
||||
use crate::sql::value::Value;
|
||||
use js::class::Trace;
|
||||
|
||||
use crate::sql::thing;
|
||||
use crate::sql::value::Value;
|
||||
use js::{class::Ref, function::Rest};
|
||||
#[derive(Clone, Trace)]
|
||||
#[js::class]
|
||||
pub struct Record {
|
||||
#[qjs(skip_trace)]
|
||||
pub(crate) value: thing::Thing,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[quickjs(cloneable)]
|
||||
pub struct Record {
|
||||
pub(crate) value: thing::Thing,
|
||||
#[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(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
#[qjs(get)]
|
||||
pub fn tb(&self) -> String {
|
||||
self.value.tb.clone()
|
||||
}
|
||||
|
||||
#[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 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,51 +1,46 @@
|
|||
#[js::bind(object, public)]
|
||||
#[quickjs(bare)]
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(unused_variables)]
|
||||
#[allow(clippy::module_inception)]
|
||||
pub mod uuid {
|
||||
use crate::sql::uuid;
|
||||
use js::class::Trace;
|
||||
|
||||
use crate::sql::uuid;
|
||||
use crate::sql::value::Value;
|
||||
use js::{class::Ref, function::Rest};
|
||||
#[derive(Clone, Trace)]
|
||||
#[js::class]
|
||||
pub struct Uuid {
|
||||
#[qjs(skip_trace)]
|
||||
pub(crate) value: Option<uuid::Uuid>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
#[quickjs(cloneable)]
|
||||
pub struct Uuid {
|
||||
pub(crate) value: Option<uuid::Uuid>,
|
||||
#[js::methods]
|
||||
impl Uuid {
|
||||
#[qjs(constructor)]
|
||||
pub fn new(value: String) -> Self {
|
||||
Self {
|
||||
value: uuid::Uuid::try_from(value).ok(),
|
||||
}
|
||||
}
|
||||
|
||||
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 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"),
|
||||
}
|
||||
}
|
||||
/// 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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::fnc::script::fetch::{classes::BlobClass, stream::ReadableStream, RequestError};
|
||||
use crate::fnc::script::fetch::{stream::ReadableStream, RequestError};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use futures::{future, Stream, TryStreamExt};
|
||||
use js::{ArrayBuffer, Class, Ctx, Error, Exception, FromJs, Result, Type, TypedArray, Value};
|
||||
|
@ -7,6 +7,8 @@ use std::{
|
|||
result::Result as StdResult,
|
||||
};
|
||||
|
||||
use super::classes::Blob;
|
||||
|
||||
pub type StreamItem = StdResult<Bytes, RequestError>;
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -127,7 +129,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()?;
|
||||
|
@ -142,7 +144,7 @@ impl<'js> FromJs<'js> for Body {
|
|||
})
|
||||
}
|
||||
};
|
||||
if let Ok(x) = Class::<BlobClass>::from_object(object.clone()) {
|
||||
if let Some(x) = Class::<Blob>::from_object(object.clone()) {
|
||||
let borrow = x.borrow();
|
||||
return Ok(Body::buffer(BodyKind::Blob(borrow.mime.clone()), borrow.data.clone()));
|
||||
}
|
||||
|
@ -194,7 +196,7 @@ impl<'js> FromJs<'js> for Body {
|
|||
.ok_or_else(|| Exception::throw_type(ctx, "Buffer is already detached"))?;
|
||||
return Ok(Body::buffer(BodyKind::Buffer, Bytes::copy_from_slice(bytes)));
|
||||
}
|
||||
if let Ok(x) = ArrayBuffer::from_object(object.clone()) {
|
||||
if let Some(x) = ArrayBuffer::from_object(object.clone()) {
|
||||
let bytes = x
|
||||
.as_bytes()
|
||||
.ok_or_else(|| Exception::throw_type(ctx, "Buffer is already detached"))?;
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
//! Blob class implementation
|
||||
|
||||
use bytes::BytesMut;
|
||||
use js::{bind, prelude::Coerced, ArrayBuffer, Class, Ctx, Exception, FromJs, Result, Value};
|
||||
|
||||
pub use blob::Blob as BlobClass;
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use js::{
|
||||
class::Trace,
|
||||
prelude::{Coerced, Opt},
|
||||
ArrayBuffer, Class, Ctx, Exception, FromJs, Object, Result, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum EndingType {
|
||||
|
@ -12,7 +14,7 @@ pub enum EndingType {
|
|||
}
|
||||
|
||||
fn append_blob_part<'js>(
|
||||
ctx: Ctx<'js>,
|
||||
ctx: &Ctx<'js>,
|
||||
value: Value<'js>,
|
||||
ending: EndingType,
|
||||
data: &mut BytesMut,
|
||||
|
@ -23,11 +25,11 @@ fn append_blob_part<'js>(
|
|||
const LINE_ENDING: &[u8] = b"\n";
|
||||
|
||||
if let Some(object) = value.as_object() {
|
||||
if let Ok(x) = Class::<BlobClass>::from_object(object.clone()) {
|
||||
if let Some(x) = Class::<Blob>::from_object(object.clone()) {
|
||||
data.extend_from_slice(&x.borrow().data);
|
||||
return Ok(());
|
||||
}
|
||||
if let Ok(x) = ArrayBuffer::from_object(object.clone()) {
|
||||
if let Some(x) = ArrayBuffer::from_object(object.clone()) {
|
||||
data.extend_from_slice(x.as_bytes().ok_or_else(|| {
|
||||
Exception::throw_type(ctx, "Tried to construct blob with detached buffer")
|
||||
})?);
|
||||
|
@ -74,144 +76,121 @@ fn normalize_type(mut ty: String) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
#[bind(object, public)]
|
||||
#[quickjs(bare)]
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(unused_variables)]
|
||||
#[allow(clippy::module_inception)]
|
||||
mod blob {
|
||||
use super::*;
|
||||
#[derive(Clone, Trace)]
|
||||
#[js::class]
|
||||
pub struct Blob {
|
||||
pub(crate) mime: String,
|
||||
// TODO: make bytes?
|
||||
#[qjs(skip_trace)]
|
||||
pub(crate) data: Bytes,
|
||||
}
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use js::{
|
||||
function::{Opt, Rest},
|
||||
ArrayBuffer, Ctx, Exception, Object, Result, Value,
|
||||
};
|
||||
#[js::methods]
|
||||
impl Blob {
|
||||
// ------------------------------
|
||||
// Constructor
|
||||
// ------------------------------
|
||||
|
||||
#[derive(Clone)]
|
||||
#[quickjs(cloneable)]
|
||||
pub struct Blob {
|
||||
pub(crate) mime: String,
|
||||
// TODO: make bytes?
|
||||
pub(crate) data: Bytes,
|
||||
#[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;
|
||||
|
||||
if let Some(obj) = options.into_inner() {
|
||||
if let Some(x) = obj.get::<_, Option<Coerced<String>>>("type")? {
|
||||
r#type = normalize_type(x.to_string());
|
||||
}
|
||||
if let Some(Coerced(x)) = obj.get::<_, Option<Coerced<String>>>("endings")? {
|
||||
if x == "native" {
|
||||
endings = EndingType::Native;
|
||||
} else if x != "transparent" {
|
||||
return Err(Exception::throw_type(
|
||||
&ctx,
|
||||
",expected endings to be either 'transparent' or 'native'",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let data = if let Some(parts) = parts.into_inner() {
|
||||
let array = parts
|
||||
.into_array()
|
||||
.ok_or_else(|| Exception::throw_type(&ctx, "Blob parts are not a sequence"))?;
|
||||
|
||||
let mut buffer = BytesMut::new();
|
||||
|
||||
for elem in array.iter::<Value>() {
|
||||
let elem = elem?;
|
||||
append_blob_part(&ctx, elem, endings, &mut buffer)?;
|
||||
}
|
||||
buffer.freeze()
|
||||
} else {
|
||||
Bytes::new()
|
||||
};
|
||||
Ok(Self {
|
||||
mime: r#type,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
impl Blob {
|
||||
// ------------------------------
|
||||
// Constructor
|
||||
// ------------------------------
|
||||
// ------------------------------
|
||||
// Instance properties
|
||||
// ------------------------------
|
||||
|
||||
#[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;
|
||||
#[qjs(get)]
|
||||
pub fn size(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
|
||||
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'",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
#[qjs(get, rename = "type")]
|
||||
pub fn r#type(&self) -> String {
|
||||
self.mime.clone()
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// Instance properties
|
||||
// ------------------------------
|
||||
pub async fn text(&self) -> Result<String> {
|
||||
let text = String::from_utf8(self.data.to_vec())?;
|
||||
Ok(text)
|
||||
}
|
||||
|
||||
#[quickjs(get)]
|
||||
pub fn size(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
#[qjs(rename = "arrayBuffer")]
|
||||
pub async fn array_buffer<'js>(&self, ctx: Ctx<'js>) -> Result<ArrayBuffer<'js>> {
|
||||
ArrayBuffer::new(ctx, self.data.to_vec())
|
||||
}
|
||||
|
||||
#[quickjs(get)]
|
||||
#[quickjs(rename = "type")]
|
||||
pub fn r#type(&self) -> String {
|
||||
self.mime.clone()
|
||||
}
|
||||
// ------------------------------
|
||||
// Instance methods
|
||||
// ------------------------------
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn text(&self, _rest: Rest<()>) -> Result<String> {
|
||||
let text = String::from_utf8(self.data.to_vec())?;
|
||||
Ok(text)
|
||||
}
|
||||
|
||||
pub async fn arrayBuffer<'js>(
|
||||
&self,
|
||||
ctx: Ctx<'js>,
|
||||
_rest: Rest<()>,
|
||||
) -> Result<ArrayBuffer<'js>> {
|
||||
ArrayBuffer::new(ctx, self.data.to_vec())
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// Instance methods
|
||||
// ------------------------------
|
||||
|
||||
// Convert the object to a string
|
||||
pub fn toString(&self, _rest: Rest<()>) -> String {
|
||||
String::from("[object Blob]")
|
||||
}
|
||||
// Convert the object to a string
|
||||
#[qjs(rename = "toString")]
|
||||
pub fn js_to_string(&self) -> String {
|
||||
String::from("[object Blob]")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -242,17 +221,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,34 +1,36 @@
|
|||
//! FormData class implementation
|
||||
|
||||
use js::{
|
||||
bind, function::Opt, prelude::Coerced, Class, Ctx, Exception, FromJs, Persistent, Result,
|
||||
String, Value,
|
||||
class::{Class, Trace},
|
||||
function::{Opt, Rest},
|
||||
prelude::Coerced,
|
||||
Ctx, Exception, FromJs, Result, String, Value,
|
||||
};
|
||||
use std::string::String as StdString;
|
||||
use reqwest::multipart::{Form, Part};
|
||||
use std::{collections::HashMap, string::String as StdString};
|
||||
|
||||
use crate::fnc::script::fetch::classes::BlobClass;
|
||||
use crate::fnc::script::fetch::classes::Blob;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum FormDataValue {
|
||||
String(Persistent<String<'static>>),
|
||||
pub enum FormDataValue<'js> {
|
||||
String(String<'js>),
|
||||
Blob {
|
||||
data: Persistent<Class<'static, BlobClass>>,
|
||||
filename: Option<Persistent<String<'static>>>,
|
||||
data: Class<'js, Blob>,
|
||||
filename: Option<String<'js>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl FormDataValue {
|
||||
fn from_arguments<'js>(
|
||||
ctx: Ctx<'js>,
|
||||
impl<'js> FormDataValue<'js> {
|
||||
fn from_arguments(
|
||||
ctx: &Ctx<'js>,
|
||||
value: Value<'js>,
|
||||
filename: Opt<Coerced<String<'js>>>,
|
||||
error: &'static str,
|
||||
) -> Result<FormDataValue> {
|
||||
) -> Result<Self> {
|
||||
if let Some(blob) =
|
||||
value.as_object().and_then(|value| Class::<BlobClass>::from_object(value.clone()).ok())
|
||||
value.as_object().and_then(|value| Class::<Blob>::from_object(value.clone()))
|
||||
{
|
||||
let blob = Persistent::save(ctx, blob);
|
||||
let filename = filename.into_inner().map(|x| Persistent::save(ctx, x.0));
|
||||
let filename = filename.into_inner().map(|x| x.0);
|
||||
|
||||
Ok(FormDataValue::Blob {
|
||||
data: blob,
|
||||
|
@ -38,128 +40,109 @@ impl FormDataValue {
|
|||
return Err(Exception::throw_type(ctx, error));
|
||||
} else {
|
||||
let value = Coerced::<String>::from_js(ctx, value)?;
|
||||
let value = Persistent::save(ctx, value.0);
|
||||
Ok(FormDataValue::String(value))
|
||||
Ok(FormDataValue::String(value.0))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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::class]
|
||||
#[derive(Clone, Trace)]
|
||||
pub struct FormData<'js> {
|
||||
#[qjs(skip_trace)]
|
||||
pub(crate) values: HashMap<StdString, Vec<FormDataValue<'js>>>,
|
||||
}
|
||||
|
||||
use js::{
|
||||
function::Opt,
|
||||
prelude::{Coerced, Rest},
|
||||
Ctx, Result, String, Value,
|
||||
};
|
||||
use reqwest::multipart::{Form, Part};
|
||||
#[js::methods]
|
||||
impl<'js> FormData<'js> {
|
||||
// ------------------------------
|
||||
// Constructor
|
||||
// ------------------------------
|
||||
|
||||
#[derive(Clone)]
|
||||
#[quickjs(cloneable)]
|
||||
pub struct FormData {
|
||||
pub(crate) values: RefCell<HashMap<StdString, Vec<FormDataValue>>>,
|
||||
// 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",
|
||||
));
|
||||
}
|
||||
Ok(FormData {
|
||||
values: HashMap::new(),
|
||||
})
|
||||
}
|
||||
|
||||
impl FormData {
|
||||
// ------------------------------
|
||||
// Constructor
|
||||
// ------------------------------
|
||||
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`",
|
||||
)?;
|
||||
|
||||
// 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()),
|
||||
})
|
||||
}
|
||||
self.values.entry(name.0).or_insert_with(Vec::new).push(value);
|
||||
|
||||
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`",
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
self.values.borrow_mut().entry(name.0).or_insert_with(Vec::new).push(value);
|
||||
pub fn set(
|
||||
&mut self,
|
||||
ctx: Ctx<'js>,
|
||||
name: Coerced<StdString>,
|
||||
value: Value<'js>,
|
||||
filename: Opt<Coerced<String<'js>>>,
|
||||
) -> Result<()> {
|
||||
let value = FormDataValue::from_arguments(
|
||||
&ctx,
|
||||
value,
|
||||
filename,
|
||||
"Can't call `set` on `FormData` with a filename when value isn't of type `Blob`",
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
self.values.insert(name.0, vec![value]);
|
||||
|
||||
pub fn set<'js>(
|
||||
&self,
|
||||
ctx: Ctx<'js>,
|
||||
name: Coerced<StdString>,
|
||||
value: Value<'js>,
|
||||
filename: Opt<Coerced<String<'js>>>,
|
||||
) -> Result<()> {
|
||||
let value = FormDataValue::from_arguments(
|
||||
ctx,
|
||||
value,
|
||||
filename,
|
||||
"Can't call `set` on `FormData` with a filename when value isn't of type `Blob`",
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
self.values.borrow_mut().insert(name.0, vec![value]);
|
||||
pub fn has(&self, name: Coerced<StdString>) -> bool {
|
||||
self.values.contains_key(&name.0)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
pub fn delete(&mut self, name: Coerced<StdString>) {
|
||||
self.values.remove(&name.0);
|
||||
}
|
||||
|
||||
pub fn has(&self, ctx: Ctx<'_>, name: Coerced<StdString>) -> bool {
|
||||
self.values.borrow().contains_key(&name.0)
|
||||
}
|
||||
|
||||
pub fn delete(&self, ctx: Ctx<'_>, name: Coerced<StdString>) {
|
||||
self.values.borrow_mut().remove(&name.0);
|
||||
}
|
||||
|
||||
#[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);
|
||||
#[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);
|
||||
}
|
||||
res = res.part(k.clone(), part);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,272 +1,258 @@
|
|||
//! Headers class implementation
|
||||
|
||||
use js::bind;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub use headers::Headers as HeadersClass;
|
||||
use js::{
|
||||
class::Trace,
|
||||
prelude::{Coerced, List},
|
||||
Array, Ctx, Exception, Result, Value,
|
||||
};
|
||||
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
|
||||
|
||||
#[bind(object, public)]
|
||||
#[quickjs(bare)]
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(unused_variables)]
|
||||
#[allow(clippy::module_inception)]
|
||||
mod headers {
|
||||
use std::{cell::RefCell, str::FromStr};
|
||||
#[derive(Clone, Trace)]
|
||||
#[js::class]
|
||||
pub struct Headers {
|
||||
#[qjs(skip_trace)]
|
||||
pub(crate) inner: HeaderMap,
|
||||
}
|
||||
|
||||
use js::{function::Rest, prelude::Coerced, Array, Ctx, Exception, Result, Value};
|
||||
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
|
||||
#[js::methods]
|
||||
impl Headers {
|
||||
// ------------------------------
|
||||
// Constructor
|
||||
// ------------------------------
|
||||
|
||||
#[derive(Clone)]
|
||||
#[quickjs(cloneable)]
|
||||
#[allow(dead_code)]
|
||||
pub struct Headers {
|
||||
pub(crate) inner: RefCell<HeaderMap>,
|
||||
#[qjs(constructor)]
|
||||
pub fn new<'js>(ctx: Ctx<'js>, init: Value<'js>) -> Result<Self> {
|
||||
Headers::new_inner(&ctx, init)
|
||||
}
|
||||
|
||||
impl Headers {
|
||||
// ------------------------------
|
||||
// Constructor
|
||||
// ------------------------------
|
||||
// ------------------------------
|
||||
// Instance methods
|
||||
// ------------------------------
|
||||
|
||||
#[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
|
||||
}
|
||||
// Convert the object to a string
|
||||
#[qjs(rename = "toString")]
|
||||
pub fn js_to_string(&self) -> String {
|
||||
String::from("[object Header]")
|
||||
}
|
||||
|
||||
#[quickjs(skip)]
|
||||
impl Headers {
|
||||
pub fn from_map(map: HeaderMap) -> Self {
|
||||
Self {
|
||||
inner: RefCell::new(map),
|
||||
}
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
|
||||
pub fn new_empty() -> Self {
|
||||
Self::from_map(HeaderMap::new())
|
||||
}
|
||||
// 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_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();
|
||||
// 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());
|
||||
|
||||
// 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)?;
|
||||
}
|
||||
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());
|
||||
} else {
|
||||
return Err(Exception::throw_type(ctx, INVALID_ERROR));
|
||||
res.push(List((k.to_owned(), v.to_str().unwrap().to_owned())));
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn append_inner(&self, ctx: Ctx<'_>, key: &str, val: &str) -> Result<()> {
|
||||
// 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.
|
||||
res
|
||||
}
|
||||
|
||||
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}"),
|
||||
))
|
||||
}
|
||||
};
|
||||
// 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);
|
||||
|
||||
self.inner.borrow_mut().append(key, val);
|
||||
|
||||
Ok(())
|
||||
// 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.
|
||||
#[qjs(rename = "getSetCookie")]
|
||||
pub fn get_set_cookie(&self) -> Vec<String> {
|
||||
// This should always be a correct cookie;
|
||||
let key = HeaderName::from_str("set-cookie").unwrap();
|
||||
self.inner.get_all(key).iter().map(|x| x.to_str().unwrap().to_owned()).collect()
|
||||
}
|
||||
|
||||
// Checks to see if the header set contains a header
|
||||
pub fn has(&self, ctx: Ctx<'_>, key: String) -> Result<bool> {
|
||||
// Process and check the header name is valid
|
||||
let key =
|
||||
HeaderName::from_str(&key).map_err(|e| Exception::throw_type(&ctx, &format!("{e}")))?;
|
||||
// Check if the header entry exists
|
||||
Ok(self.inner.contains_key(&key))
|
||||
}
|
||||
|
||||
// Returns all header keys contained in the header set
|
||||
pub fn keys(&self) -> Vec<String> {
|
||||
// TODO: Incorrect, should return an iterator but iterators are not supported yet by quickjs
|
||||
self.inner.keys().map(|v| v.as_str().to_owned()).collect::<Vec<String>>()
|
||||
}
|
||||
|
||||
// Sets a new value or adds a header to the header set
|
||||
pub fn set(&mut self, ctx: Ctx<'_>, key: String, val: String) -> Result<()> {
|
||||
// Process and check the header name is valid
|
||||
let key = HeaderName::from_str(&key)
|
||||
.map_err(|e| Exception::throw_type(&ctx, &format!("Invalid header name: {e}")))?;
|
||||
// Process and check the header name is valid
|
||||
let val = HeaderValue::from_str(&val)
|
||||
.map_err(|e| Exception::throw_type(&ctx, &format!("Invalid header value: {e}")))?;
|
||||
// Insert and overwrite the header entry
|
||||
self.inner.insert(key, val);
|
||||
// Everything ok
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Returns all header values contained in the header set
|
||||
pub fn values(&self) -> Vec<String> {
|
||||
let mut res = Vec::<String>::with_capacity(self.inner.len());
|
||||
|
||||
let mut pref = None;
|
||||
for (k, v) in self.inner.iter() {
|
||||
if Some(k) == pref {
|
||||
let ent = res.last_mut().unwrap();
|
||||
ent.push_str(", ");
|
||||
ent.push_str(v.to_str().unwrap())
|
||||
} else {
|
||||
pref = Some(k);
|
||||
res.push(v.to_str().unwrap().to_owned());
|
||||
}
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
impl Headers {
|
||||
pub fn from_map(map: HeaderMap) -> Self {
|
||||
Self {
|
||||
inner: map,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_empty() -> Self {
|
||||
Self::from_map(HeaderMap::new())
|
||||
}
|
||||
|
||||
pub fn new_inner<'js>(ctx: &Ctx<'js>, val: Value<'js>) -> Result<Self> {
|
||||
static INVALID_ERROR: &str = "Headers constructor: init was neither sequence<sequence<ByteString>> or record<ByteString, ByteString>";
|
||||
let mut res = Self::new_empty();
|
||||
|
||||
// TODO Set and Map,
|
||||
if let Some(array) = val.as_array() {
|
||||
// a sequence<sequence<String>>;
|
||||
for v in array.iter::<Array>() {
|
||||
let v = match v {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
if e.is_from_js() {
|
||||
return Err(Exception::throw_type(ctx, INVALID_ERROR));
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
let key = match v.get::<Coerced<String>>(0) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
if e.is_from_js() {
|
||||
return Err(Exception::throw_type(ctx, INVALID_ERROR));
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
let value = match v.get::<Coerced<String>>(1) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
if e.is_from_js() {
|
||||
return Err(Exception::throw_type(ctx, INVALID_ERROR));
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
res.append_inner(ctx, &key, &value)?;
|
||||
}
|
||||
} else if let Some(obj) = val.as_object() {
|
||||
// a record<String,String>;
|
||||
for prop in obj.props::<String, Coerced<String>>() {
|
||||
let (key, value) = match prop {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
if e.is_from_js() {
|
||||
return Err(Exception::throw_type(ctx, INVALID_ERROR));
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
res.append_inner(ctx, &key, &value.0)?;
|
||||
}
|
||||
} else {
|
||||
return Err(Exception::throw_type(ctx, INVALID_ERROR));
|
||||
}
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
fn append_inner(&mut self, ctx: &Ctx<'_>, key: &str, val: &str) -> Result<()> {
|
||||
// Unsure what to do exactly here.
|
||||
// Spec dictates normalizing string before adding it as a header value, i.e. removing
|
||||
// any leading and trailing whitespace:
|
||||
// [`https://fetch.spec.whatwg.org/#concept-header-value-normalize`]
|
||||
// But non of the platforms I tested, normalize, instead they throw an error
|
||||
// with `Invalid header value`. I'll chose to just do what the platforms do.
|
||||
|
||||
let key = match HeaderName::from_bytes(key.as_bytes()) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
return Err(Exception::throw_type(
|
||||
ctx,
|
||||
&format!("invalid header name `{key}`: {e}"),
|
||||
))
|
||||
}
|
||||
};
|
||||
let val = match HeaderValue::from_bytes(val.as_bytes()) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
return Err(Exception::throw_type(
|
||||
ctx,
|
||||
&format!("invalid header value `{val}`: {e}"),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
self.inner.append(key, val);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -327,7 +313,7 @@ mod test {
|
|||
});
|
||||
assert.seq(headers.get("f"), "g");
|
||||
assert.seq(headers.get("h"), "j");
|
||||
"#).catch(ctx).unwrap();
|
||||
"#).catch(&ctx).unwrap();
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
|
|
@ -1,18 +1,9 @@
|
|||
//! Request class implementation
|
||||
//!
|
||||
use js::{
|
||||
bind,
|
||||
class::{HasRefs, RefsMarker},
|
||||
prelude::Coerced,
|
||||
Class, Ctx, Exception, FromJs, Object, Persistent, Result, Value,
|
||||
};
|
||||
|
||||
use js::{class::Trace, prelude::Coerced, Class, Ctx, Exception, FromJs, Object, Result, Value};
|
||||
use reqwest::Method;
|
||||
|
||||
use crate::fnc::script::fetch::{
|
||||
body::Body,
|
||||
classes::{BlobClass, HeadersClass},
|
||||
RequestError,
|
||||
};
|
||||
use crate::fnc::script::fetch::{body::Body, RequestError};
|
||||
|
||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||
pub enum RequestMode {
|
||||
|
@ -23,7 +14,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,
|
||||
|
@ -59,7 +50,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,
|
||||
|
@ -96,7 +87,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,
|
||||
|
@ -136,7 +127,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,
|
||||
|
@ -176,7 +167,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,
|
||||
|
@ -213,9 +204,9 @@ impl<'js> FromJs<'js> for ReferrerPolicy {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct RequestInit {
|
||||
pub struct RequestInit<'js> {
|
||||
pub method: Method,
|
||||
pub headers: Persistent<Class<'static, HeadersClass>>,
|
||||
pub headers: Class<'js, Headers>,
|
||||
pub body: Option<Body>,
|
||||
pub referrer: String,
|
||||
pub referrer_policy: ReferrerPolicy,
|
||||
|
@ -227,15 +218,15 @@ pub struct RequestInit {
|
|||
pub keep_alive: bool,
|
||||
}
|
||||
|
||||
impl HasRefs for RequestInit {
|
||||
fn mark_refs(&self, marker: &RefsMarker) {
|
||||
self.headers.mark_refs(marker);
|
||||
impl<'js> Trace<'js> for RequestInit<'js> {
|
||||
fn trace<'a>(&self, tracer: js::class::Tracer<'a, 'js>) {
|
||||
self.headers.trace(tracer);
|
||||
}
|
||||
}
|
||||
|
||||
impl RequestInit {
|
||||
pub fn default(ctx: Ctx<'_>) -> Result<Self> {
|
||||
let headers = Persistent::save(ctx, Class::instance(ctx, HeadersClass::new_empty())?);
|
||||
impl<'js> RequestInit<'js> {
|
||||
pub fn default(ctx: Ctx<'js>) -> Result<Self> {
|
||||
let headers = Class::instance(ctx, Headers::new_empty())?;
|
||||
Ok(RequestInit {
|
||||
method: Method::GET,
|
||||
headers,
|
||||
|
@ -251,9 +242,9 @@ impl RequestInit {
|
|||
})
|
||||
}
|
||||
|
||||
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())?);
|
||||
pub fn clone_js(&self, ctx: Ctx<'js>) -> Result<Self> {
|
||||
let headers = self.headers.clone();
|
||||
let headers = Class::instance(ctx.clone(), headers.borrow().clone())?;
|
||||
|
||||
let body = self.body.as_ref().map(|x| x.clone_js(ctx));
|
||||
|
||||
|
@ -274,7 +265,7 @@ impl RequestInit {
|
|||
}
|
||||
|
||||
// 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")
|
||||
|
@ -309,8 +300,8 @@ fn normalize_method(ctx: Ctx<'_>, m: String) -> Result<Method> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'js> FromJs<'js> for RequestInit {
|
||||
fn from_js(ctx: Ctx<'js>, value: Value<'js>) -> Result<Self> {
|
||||
impl<'js> FromJs<'js> for RequestInit<'js> {
|
||||
fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
|
||||
let object = Object::from_js(ctx, value)?;
|
||||
|
||||
let referrer = object
|
||||
|
@ -346,15 +337,14 @@ impl<'js> FromJs<'js> for RequestInit {
|
|||
}
|
||||
|
||||
let headers = if let Some(hdrs) = object.get::<_, Option<Object>>("headers")? {
|
||||
if let Ok(cls) = Class::<HeadersClass>::from_object(hdrs.clone()) {
|
||||
if let Some(cls) = Class::<Headers>::from_object(hdrs.clone()) {
|
||||
cls
|
||||
} else {
|
||||
Class::instance(ctx, HeadersClass::new_inner(ctx, hdrs.into_value())?)?
|
||||
Class::instance(ctx.clone(), Headers::new_inner(ctx, hdrs.into_value())?)?
|
||||
}
|
||||
} else {
|
||||
Class::instance(ctx, HeadersClass::new_empty())?
|
||||
Class::instance(ctx.clone(), Headers::new_empty())?
|
||||
};
|
||||
let headers = Persistent::save(ctx, headers);
|
||||
|
||||
let body = object.get::<_, Option<Body>>("body")?;
|
||||
|
||||
|
@ -376,192 +366,173 @@ impl<'js> FromJs<'js> for RequestInit {
|
|||
|
||||
pub use request::Request as RequestClass;
|
||||
|
||||
#[bind(object, public)]
|
||||
#[quickjs(bare)]
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(unused_variables)]
|
||||
#[allow(clippy::module_inception)]
|
||||
mod request {
|
||||
pub use super::*;
|
||||
|
||||
pub use super::*;
|
||||
use bytes::Bytes;
|
||||
use js::function::Opt;
|
||||
// TODO: change implementation based on features.
|
||||
use reqwest::{header::HeaderName, Url};
|
||||
|
||||
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};
|
||||
#[allow(dead_code)]
|
||||
#[js::class]
|
||||
#[derive(Trace)]
|
||||
pub struct Request<'js> {
|
||||
#[qjs(skip_trace)]
|
||||
pub(crate) url: Url,
|
||||
pub(crate) init: RequestInit<'js>,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(HasRefs)]
|
||||
#[quickjs(has_refs)]
|
||||
pub struct Request {
|
||||
pub(crate) url: Url,
|
||||
#[quickjs(has_refs)]
|
||||
pub(crate) init: RequestInit,
|
||||
#[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",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl Request {
|
||||
// ------------------------------
|
||||
// Constructor
|
||||
// ------------------------------
|
||||
/// 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)?,
|
||||
})
|
||||
}
|
||||
|
||||
#[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."));
|
||||
// ------------------------------
|
||||
// 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}")))
|
||||
}
|
||||
let init = init.into_inner().map_or_else(|| RequestInit::default(ctx), Ok)?;
|
||||
// HEAD and GET methods can't have a body
|
||||
if init.body.is_some() && init.method == Method::GET || init.method == Method::HEAD
|
||||
{
|
||||
return Err(Exception::throw_type(
|
||||
ctx,
|
||||
&format!("Request with method `{}` cannot have a body", init.method),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
url,
|
||||
init,
|
||||
})
|
||||
} else if let Some(request) = input
|
||||
.into_object()
|
||||
.and_then(|obj| Class::<Self>::from_object(obj).ok().map(|x| x.borrow()))
|
||||
{
|
||||
// existing request object, just return it
|
||||
request.clone_js(ctx, Default::default())
|
||||
} else {
|
||||
Err(Exception::throw_type(
|
||||
ctx,
|
||||
"request `init` paramater must either be a request object or a string",
|
||||
))
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Clone the response, teeing any possible underlying streams.
|
||||
#[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)?,
|
||||
})
|
||||
}
|
||||
// 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()
|
||||
};
|
||||
|
||||
// ------------------------------
|
||||
// Instance properties
|
||||
// ------------------------------
|
||||
#[quickjs(get)]
|
||||
pub fn bodyUsed(&self) -> bool {
|
||||
self.init.body.as_ref().map(Body::used).unwrap_or(true)
|
||||
}
|
||||
let data = self.take_buffer(&ctx).await?;
|
||||
Ok(Blob {
|
||||
mime,
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
#[quickjs(get)]
|
||||
pub fn method(&self) -> String {
|
||||
self.init.method.to_string()
|
||||
}
|
||||
// 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 url(&self) -> String {
|
||||
self.url.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 headers<'js>(&self, ctx: Ctx<'js>) -> Class<'js, HeadersClass> {
|
||||
self.init.headers.clone().restore(ctx).unwrap()
|
||||
}
|
||||
// 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 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())
|
||||
}
|
||||
// Skip UTF-BOM
|
||||
if data.starts_with(&[0xEF, 0xBB, 0xBF]) {
|
||||
Ok(String::from_utf8_lossy(&data[3..]).into_owned())
|
||||
} else {
|
||||
Ok(String::from_utf8_lossy(&data).into_owned())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -644,7 +615,7 @@ mod test {
|
|||
assert.seq(await req_2.text(),"some text");
|
||||
|
||||
})()
|
||||
"#).catch(ctx).unwrap().await.catch(ctx).unwrap();
|
||||
"#).catch(&ctx).unwrap().await.catch(&ctx).unwrap();
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
|
|
@ -1,34 +1,33 @@
|
|||
use std::string::String as StdString;
|
||||
|
||||
use js::{
|
||||
class::{HasRefs, RefsMarker},
|
||||
class::{Trace, Tracer},
|
||||
prelude::*,
|
||||
Class, Ctx, Exception, FromJs, Object, Persistent, Result, Value,
|
||||
Class, Ctx, Exception, FromJs, Object, Result, Value,
|
||||
};
|
||||
|
||||
use crate::fnc::script::fetch::{classes::HeadersClass, util};
|
||||
use crate::fnc::script::fetch::{classes::Headers, util};
|
||||
|
||||
/// Struct containing data from the init argument from the Response constructor.
|
||||
#[derive(Clone)]
|
||||
pub struct ResponseInit {
|
||||
pub struct ResponseInit<'js> {
|
||||
// u16 instead of reqwest::StatusCode since javascript allows non valid status codes in some
|
||||
// circumstances.
|
||||
pub status: u16,
|
||||
pub status_text: StdString,
|
||||
pub headers: Persistent<Class<'static, HeadersClass>>,
|
||||
pub headers: Class<'js, Headers>,
|
||||
}
|
||||
|
||||
impl HasRefs for ResponseInit {
|
||||
fn mark_refs(&self, marker: &RefsMarker) {
|
||||
self.headers.mark_refs(marker);
|
||||
impl<'js> Trace<'js> for ResponseInit<'js> {
|
||||
fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
|
||||
self.headers.trace(tracer);
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponseInit {
|
||||
impl<'js> ResponseInit<'js> {
|
||||
/// Returns a ResponseInit object with all values as the default value.
|
||||
pub fn default(ctx: Ctx<'_>) -> Result<ResponseInit> {
|
||||
let headers = Class::instance(ctx, HeadersClass::new_empty())?;
|
||||
let headers = Persistent::save(ctx, headers);
|
||||
pub fn default(ctx: Ctx<'js>) -> Result<Self> {
|
||||
let headers = Class::instance(ctx, Headers::new_empty())?;
|
||||
Ok(ResponseInit {
|
||||
status: 200,
|
||||
status_text: StdString::new(),
|
||||
|
@ -37,8 +36,8 @@ impl ResponseInit {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'js> FromJs<'js> for ResponseInit {
|
||||
fn from_js(ctx: Ctx<'js>, value: Value<'js>) -> Result<Self> {
|
||||
impl<'js> FromJs<'js> for ResponseInit<'js> {
|
||||
fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
|
||||
let object = Object::from_js(ctx, value)?;
|
||||
|
||||
// Extract status.
|
||||
|
@ -66,12 +65,11 @@ impl<'js> FromJs<'js> for ResponseInit {
|
|||
|
||||
// Extract headers.
|
||||
let headers = if let Some(headers) = object.get::<_, Option<Value>>("headers")? {
|
||||
let headers = HeadersClass::new_inner(ctx, headers)?;
|
||||
Class::instance(ctx, headers)?
|
||||
let headers = Headers::new_inner(ctx, headers)?;
|
||||
Class::instance(ctx.clone(), headers)?
|
||||
} else {
|
||||
Class::instance(ctx, HeadersClass::new_empty())?
|
||||
Class::instance(ctx.clone(), Headers::new_empty())?
|
||||
};
|
||||
let headers = Persistent::save(ctx, headers);
|
||||
|
||||
Ok(ResponseInit {
|
||||
status,
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
//! Response class implementation
|
||||
|
||||
use js::bind;
|
||||
|
||||
mod init;
|
||||
use bytes::Bytes;
|
||||
pub use init::ResponseInit;
|
||||
|
||||
pub use response::Response as ResponseClass;
|
||||
use js::{class::Trace, prelude::Opt, ArrayBuffer, Class, Ctx, Exception, Result, Value};
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Clone, Copy)]
|
||||
|
@ -18,304 +17,284 @@ pub enum ResponseType {
|
|||
OpaqueRedirect,
|
||||
}
|
||||
|
||||
#[bind(object, public)]
|
||||
#[quickjs(bare)]
|
||||
#[allow(non_snake_case)]
|
||||
#[allow(unused_variables)]
|
||||
#[allow(clippy::module_inception)]
|
||||
pub mod response {
|
||||
use reqwest::Url;
|
||||
|
||||
use crate::fnc::script::fetch::{
|
||||
body::{Body, BodyKind},
|
||||
classes::{BlobClass, HeadersClass},
|
||||
util, RequestError,
|
||||
};
|
||||
use crate::fnc::script::fetch::{
|
||||
body::{Body, BodyKind},
|
||||
util, RequestError,
|
||||
};
|
||||
|
||||
use super::{ResponseInit, ResponseType};
|
||||
use bytes::Bytes;
|
||||
use js::{
|
||||
function::{Opt, Rest},
|
||||
ArrayBuffer, Class, Ctx, Exception, HasRefs, Persistent, Result, Value,
|
||||
};
|
||||
use reqwest::Url;
|
||||
use super::{Blob, Headers};
|
||||
|
||||
#[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,
|
||||
#[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,
|
||||
})
|
||||
}
|
||||
|
||||
impl Response {
|
||||
// ------------------------------
|
||||
// Constructor
|
||||
// ------------------------------
|
||||
// ------------------------------
|
||||
// Instance properties
|
||||
// ------------------------------
|
||||
|
||||
#[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
|
||||
),
|
||||
));
|
||||
#[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()
|
||||
}
|
||||
let body = body.unwrap_or_default();
|
||||
})
|
||||
}
|
||||
|
||||
Ok(Response {
|
||||
body,
|
||||
init,
|
||||
url: None,
|
||||
r#type: ResponseType::Default,
|
||||
was_redirected: false,
|
||||
})
|
||||
// ------------------------------
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// 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()
|
||||
#[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}")))
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 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"));
|
||||
}
|
||||
|
||||
// ------------------------------
|
||||
// Instance methods
|
||||
// ------------------------------
|
||||
let headers = Class::instance(ctx, Headers::new_empty())?;
|
||||
|
||||
// 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,
|
||||
})
|
||||
}
|
||||
Ok(Response {
|
||||
url: Some(url),
|
||||
body: Body::new(),
|
||||
init: ResponseInit {
|
||||
status,
|
||||
status_text: String::new(),
|
||||
headers,
|
||||
},
|
||||
r#type: ResponseType::Default,
|
||||
was_redirected: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -388,7 +367,7 @@ mod test {
|
|||
|
||||
|
||||
})()
|
||||
"#).catch(ctx).unwrap().await.catch(ctx).unwrap();
|
||||
"#).catch(&ctx).unwrap().await.catch(&ctx).unwrap();
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
|
|
@ -2,29 +2,28 @@
|
|||
|
||||
use crate::fnc::script::fetch::{
|
||||
body::{Body, BodyData, BodyKind},
|
||||
classes::{
|
||||
self, HeadersClass, RequestClass, RequestInit, ResponseClass, ResponseInit, ResponseType,
|
||||
},
|
||||
classes::{self, Request, RequestInit, Response, ResponseInit, ResponseType},
|
||||
RequestError,
|
||||
};
|
||||
use futures::TryStreamExt;
|
||||
use js::{bind, function::Opt, prelude::*, Class, Ctx, Exception, Persistent, Result, Value};
|
||||
use js::{function::Opt, Class, Ctx, Exception, Result, Value};
|
||||
use reqwest::{
|
||||
header::{HeaderValue, CONTENT_TYPE},
|
||||
redirect, Body as ReqBody,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[bind(object, public)]
|
||||
use super::classes::Headers;
|
||||
|
||||
#[js::function]
|
||||
#[allow(unused_variables)]
|
||||
pub async fn fetch<'js>(
|
||||
ctx: Ctx<'js>,
|
||||
input: Value<'js>,
|
||||
init: Opt<RequestInit>,
|
||||
args: Rest<()>,
|
||||
) -> Result<ResponseClass> {
|
||||
init: Opt<RequestInit<'js>>,
|
||||
) -> Result<Response<'js>> {
|
||||
// Create a request from the input.
|
||||
let js_req = RequestClass::new(ctx, input, init, args)?;
|
||||
let js_req = Request::new(ctx.clone(), input, init)?;
|
||||
|
||||
let url = js_req.url;
|
||||
|
||||
|
@ -32,9 +31,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.restore(ctx).unwrap();
|
||||
let headers = js_req.init.headers;
|
||||
let headers = headers.borrow();
|
||||
let mut headers = headers.inner.borrow().clone();
|
||||
let mut headers = headers.inner.clone();
|
||||
|
||||
let redirect = js_req.init.request_redirect;
|
||||
|
||||
|
@ -55,7 +54,7 @@ pub async fn fetch<'js>(
|
|||
});
|
||||
|
||||
let client = reqwest::Client::builder().redirect(policy).build().map_err(|e| {
|
||||
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.
|
||||
|
@ -70,7 +69,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 => {}
|
||||
|
@ -94,12 +93,11 @@ 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 = HeadersClass::from_map(response.headers().clone());
|
||||
let headers = Headers::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(),
|
||||
|
@ -111,7 +109,7 @@ pub async fn fetch<'js>(
|
|||
BodyKind::Buffer,
|
||||
response.bytes_stream().map_err(Arc::new).map_err(RequestError::Reqwest),
|
||||
);
|
||||
let response = ResponseClass {
|
||||
let response = Response {
|
||||
body,
|
||||
init,
|
||||
url: Some(url),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::{error::Error, fmt, sync::Arc};
|
||||
|
||||
use js::{Ctx, Result};
|
||||
use js::{Class, Ctx, Result};
|
||||
|
||||
mod body;
|
||||
mod classes;
|
||||
|
@ -9,7 +9,7 @@ mod stream;
|
|||
mod util;
|
||||
|
||||
use classes::{Blob, FormData, Headers, Request, Response};
|
||||
use func::Fetch;
|
||||
use func::js_fetch;
|
||||
|
||||
// Anoyingly errors aren't clone,
|
||||
// But with how we implement streams RequestError must be clone.
|
||||
|
@ -30,15 +30,14 @@ 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.init_def::<Fetch>()?;
|
||||
|
||||
globals.init_def::<Response>()?;
|
||||
globals.init_def::<Request>()?;
|
||||
globals.init_def::<Blob>()?;
|
||||
globals.init_def::<FormData>()?;
|
||||
globals.init_def::<Headers>()?;
|
||||
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)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -55,9 +54,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) {
|
||||
|
@ -91,7 +90,7 @@ mod test {
|
|||
}
|
||||
throw new Error(`Code which should throw, didnt: \n${cb}`)
|
||||
}
|
||||
"#).unwrap();
|
||||
").unwrap();
|
||||
|
||||
$($t)*
|
||||
}).await;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
/// The stub implementations for the fetch API when `http` is not enabled.
|
||||
//! stub implementations for the fetch API when `http` is not enabled.
|
||||
|
||||
use js::{bind, prelude::*, Ctx, Exception, Result};
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -5,6 +5,7 @@ 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;
|
||||
|
@ -20,7 +21,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),
|
||||
|
@ -49,14 +50,15 @@ 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, &e).map(|x| x.throw());
|
||||
let (Ok(e) | Err(e)) =
|
||||
Exception::from_message(ctx.clone(), &e).map(|x| x.throw());
|
||||
return Err(e);
|
||||
}
|
||||
// Check to see if this object is a record
|
||||
if (v).instance_of::<classes::record::record::Record>() {
|
||||
let v = v.into_instance::<classes::record::record::Record>().unwrap();
|
||||
if (v).instance_of::<classes::record::Record>() {
|
||||
let v = v.into_class::<classes::record::Record>().unwrap();
|
||||
let borrow = v.borrow();
|
||||
let v: &classes::record::record::Record = &borrow;
|
||||
let v: &classes::record::Record = &borrow;
|
||||
check_nul(&v.value.tb)?;
|
||||
if let Id::String(s) = &v.value.id {
|
||||
check_nul(s)?;
|
||||
|
@ -64,20 +66,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::Duration>() {
|
||||
let v = v.into_instance::<classes::duration::duration::Duration>().unwrap();
|
||||
if (v).instance_of::<classes::duration::Duration>() {
|
||||
let v = v.into_class::<classes::duration::Duration>().unwrap();
|
||||
let borrow = v.borrow();
|
||||
let v: &classes::duration::duration::Duration = &borrow;
|
||||
let v: &classes::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::Uuid>() {
|
||||
let v = v.into_instance::<classes::uuid::uuid::Uuid>().unwrap();
|
||||
if (v).instance_of::<classes::uuid::Uuid>() {
|
||||
let v = v.into_class::<classes::uuid::Uuid>().unwrap();
|
||||
let borrow = v.borrow();
|
||||
let v: &classes::uuid::uuid::Uuid = &borrow;
|
||||
let v: &classes::uuid::Uuid = &borrow;
|
||||
return match &v.value {
|
||||
Some(v) => Ok(v.clone().into()),
|
||||
None => Ok(Value::None),
|
||||
|
@ -87,7 +89,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((js::prelude::This(v),))?;
|
||||
let m: i64 = f.call((This(v),))?;
|
||||
let d = Utc.timestamp_millis_opt(m).unwrap();
|
||||
return Ok(Datetime::from(d).into());
|
||||
}
|
||||
|
|
|
@ -1,32 +1,44 @@
|
|||
#[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(" "));
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
|
|
|
@ -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, *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::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::Number(Number::Decimal(v)) => match decimal_is_integer(&v) {
|
||||
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())),
|
||||
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())),
|
||||
},
|
||||
Value::Datetime(v) => {
|
||||
let date: js::Function = ctx.globals().get("Date")?;
|
||||
let date: js::function::Constructor = ctx.globals().get("Date")?;
|
||||
date.construct((v.0.timestamp_millis(),))
|
||||
}
|
||||
Value::Thing(v) => Ok(Class::<classes::record::record::Record>::instance(
|
||||
ctx,
|
||||
classes::record::record::Record {
|
||||
Value::Thing(v) => Ok(Class::<classes::record::Record>::instance(
|
||||
ctx.clone(),
|
||||
classes::record::Record {
|
||||
value: v.to_owned(),
|
||||
},
|
||||
)?
|
||||
.into_value()),
|
||||
Value::Duration(v) => Ok(Class::<classes::duration::duration::Duration>::instance(
|
||||
ctx,
|
||||
classes::duration::duration::Duration {
|
||||
Value::Duration(v) => Ok(Class::<classes::duration::Duration>::instance(
|
||||
ctx.clone(),
|
||||
classes::duration::Duration {
|
||||
value: Some(v.to_owned()),
|
||||
},
|
||||
)?
|
||||
.into_value()),
|
||||
Value::Uuid(v) => Ok(Class::<classes::uuid::uuid::Uuid>::instance(
|
||||
ctx,
|
||||
classes::uuid::uuid::Uuid {
|
||||
Value::Uuid(v) => Ok(Class::<classes::uuid::Uuid>::instance(
|
||||
ctx.clone(),
|
||||
classes::uuid::Uuid {
|
||||
value: Some(v.to_owned()),
|
||||
},
|
||||
)?
|
||||
.into_value()),
|
||||
Value::Array(v) => {
|
||||
let x = Array::new(ctx)?;
|
||||
let x = Array::new(ctx.clone())?;
|
||||
for (i, v) in v.iter().enumerate() {
|
||||
x.set(i, v)?;
|
||||
}
|
||||
x.into_js(ctx)
|
||||
}
|
||||
Value::Object(v) => {
|
||||
let x = Object::new(ctx)?;
|
||||
let x = Object::new(ctx.clone())?;
|
||||
for (k, v) in v.iter() {
|
||||
x.set(k, v)?;
|
||||
}
|
||||
|
|
|
@ -50,23 +50,24 @@ pub async fn run(
|
|||
|
||||
// Attempt to execute the script
|
||||
async_with!(ctx => |ctx|{
|
||||
let res = async move {
|
||||
let res = async{
|
||||
// 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, "surrealdb")?
|
||||
Module::evaluate_def::<modules::surrealdb::Package, _>(ctx.clone(), "surrealdb")?
|
||||
.get::<_, js::Value>("default")?,
|
||||
)?;
|
||||
fetch::register(ctx)?;
|
||||
fetch::register(&ctx)?;
|
||||
let console = globals::console::console(&ctx)?;
|
||||
// Register the console function to the globals
|
||||
global.init_def::<globals::console::Console>()?;
|
||||
global.set("console",console)?;
|
||||
// Register the special SurrealDB types as classes
|
||||
classes::init(ctx)?;
|
||||
classes::init(&ctx)?;
|
||||
// Attempt to compile the script
|
||||
let res = ctx.compile("script", src)?;
|
||||
let res = ctx.clone().compile("script", src)?;
|
||||
// Attempt to fetch the main export
|
||||
let fnc = res.get::<_, Function>("default")?;
|
||||
// Extract the doc if any
|
||||
|
@ -76,7 +77,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)?;
|
||||
fn evaluate<'js>(ctx: &js::Ctx<'js>, exports: &mut js::module::Exports<'js>) -> js::Result<()> {
|
||||
let default = js::Object::new(ctx.clone())?;
|
||||
$(
|
||||
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,13 +1,31 @@
|
|||
#[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()
|
||||
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(())
|
||||
}
|
||||
/// Get the target operating system
|
||||
pub fn platform() -> &'static str {
|
||||
crate::env::os()
|
||||
|
||||
fn evaluate<'js>(_ctx: &js::Ctx<'js>, exports: &mut Exports<'js>) -> Result<()> {
|
||||
exports.export("arch", js_arch)?;
|
||||
exports.export("platform", js_platform)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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, name)?.get::<_, js::Value>("default")
|
||||
Module::evaluate_def::<D, _>(ctx.clone(), name)?.get::<_, js::Value>("default")
|
||||
}
|
||||
|
|
|
@ -2938,7 +2938,7 @@ mod tests {
|
|||
assert_eq!(24, std::mem::size_of::<crate::sql::table::Table>());
|
||||
assert_eq!(56, std::mem::size_of::<crate::sql::thing::Thing>());
|
||||
assert_eq!(40, std::mem::size_of::<crate::sql::model::Model>());
|
||||
assert_eq!(16, std::mem::size_of::<crate::sql::regex::Regex>());
|
||||
assert_eq!(32, std::mem::size_of::<crate::sql::regex::Regex>());
|
||||
assert_eq!(8, std::mem::size_of::<Box<crate::sql::range::Range>>());
|
||||
assert_eq!(8, std::mem::size_of::<Box<crate::sql::edges::Edges>>());
|
||||
assert_eq!(8, std::mem::size_of::<Box<crate::sql::function::Function>>());
|
||||
|
|
Loading…
Reference in a new issue