cred: isolate scripts in WASI and move RSA signing to AWS-LC
This commit is contained in:
@@ -0,0 +1,298 @@
|
||||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use rquickjs::{
|
||||
class::{Trace, Tracer},
|
||||
Ctx, FromJs, IntoJs, JsLifetime, Result, Value,
|
||||
};
|
||||
|
||||
macro_rules! define_any_of {
|
||||
($name:ident, $($variant:ident),+) => {
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum $name<$($variant),+> {
|
||||
$(
|
||||
$variant($variant),
|
||||
)+
|
||||
}
|
||||
|
||||
define_any_of_from_js!($name, $($variant),+);
|
||||
|
||||
impl<'js, $($variant: IntoJs<'js>),+> IntoJs<'js> for $name<$($variant),+> {
|
||||
fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
|
||||
match self {
|
||||
$(
|
||||
Self::$variant(val) => val.into_js(ctx),
|
||||
)+
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'js, $($variant: JsLifetime<'js>),+> JsLifetime<'js> for $name<$($variant),+> {
|
||||
type Changed<'to> = $name<$($variant::Changed<'to>),+>;
|
||||
}
|
||||
|
||||
impl<'js, $($variant: Trace<'js>),+> Trace<'js> for $name<$($variant),+> {
|
||||
fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
|
||||
match self {
|
||||
$(
|
||||
Self::$variant(val) => val.trace(tracer),
|
||||
)+
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
define_any_of_methods!($name, $($variant),+);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! define_any_of_from_js {
|
||||
($name:ident, $first:ident, $($rest:ident),+) => {
|
||||
impl<'js, $first: FromJs<'js>, $($rest: FromJs<'js>),+> FromJs<'js> for $name<$first, $($rest),+> {
|
||||
fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
|
||||
define_any_of_from_js_impl!($name, ctx, value, $first, $($rest),+)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! define_any_of_from_js_impl {
|
||||
($name:ident, $ctx:ident, $value:ident, $first:ident) => {
|
||||
$first::from_js($ctx, $value).map($name::$first)
|
||||
};
|
||||
|
||||
($name:ident, $ctx:ident, $value:ident, $first:ident, $($rest:ident),+) => {
|
||||
$first::from_js($ctx, $value.clone()).map($name::$first).or_else(|error| {
|
||||
if error.is_from_js() {
|
||||
define_any_of_from_js_impl!($name, $ctx, $value, $($rest),+)
|
||||
} else {
|
||||
Err(error)
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! define_any_of_variant_methods {
|
||||
($variant:ident, $is_fn:ident, $as_fn:ident, $as_mut_fn:ident, $into_fn:ident) => {
|
||||
#[allow(dead_code)]
|
||||
pub fn $is_fn(&self) -> bool {
|
||||
matches!(self, Self::$variant(_))
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn $as_fn(&self) -> Option<&$variant> {
|
||||
match self {
|
||||
Self::$variant(val) => Some(val),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn $as_mut_fn(&mut self) -> Option<&mut $variant> {
|
||||
match self {
|
||||
Self::$variant(val) => Some(val),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn $into_fn(self) -> std::result::Result<$variant, Self> {
|
||||
match self {
|
||||
Self::$variant(val) => Ok(val),
|
||||
other => Err(other),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
(A) => {
|
||||
define_any_of_variant_methods!(A, is_a, as_a, as_a_mut, into_a);
|
||||
};
|
||||
(B) => {
|
||||
define_any_of_variant_methods!(B, is_b, as_b, as_b_mut, into_b);
|
||||
};
|
||||
(C) => {
|
||||
define_any_of_variant_methods!(C, is_c, as_c, as_c_mut, into_c);
|
||||
};
|
||||
(D) => {
|
||||
define_any_of_variant_methods!(D, is_d, as_d, as_d_mut, into_d);
|
||||
};
|
||||
(E) => {
|
||||
define_any_of_variant_methods!(E, is_e, as_e, as_e_mut, into_e);
|
||||
};
|
||||
(F) => {
|
||||
define_any_of_variant_methods!(F, is_f, as_f, as_f_mut, into_f);
|
||||
};
|
||||
(G) => {
|
||||
define_any_of_variant_methods!(G, is_g, as_g, as_g_mut, into_g);
|
||||
};
|
||||
(H) => {
|
||||
define_any_of_variant_methods!(H, is_h, as_h, as_h_mut, into_h);
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! define_any_of_methods {
|
||||
($name:ident, $($variant:ident),+) => {
|
||||
impl<$($variant),+> $name<$($variant),+> {
|
||||
$(
|
||||
define_any_of_variant_methods!($variant);
|
||||
)+
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_any_of!(AnyOf2, A, B);
|
||||
define_any_of!(AnyOf3, A, B, C);
|
||||
define_any_of!(AnyOf4, A, B, C, D);
|
||||
define_any_of!(AnyOf5, A, B, C, D, E);
|
||||
define_any_of!(AnyOf6, A, B, C, D, E, F);
|
||||
define_any_of!(AnyOf7, A, B, C, D, E, F, G);
|
||||
define_any_of!(AnyOf8, A, B, C, D, E, F, G, H);
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rquickjs::{Context, Runtime};
|
||||
|
||||
#[test]
|
||||
fn test_any_of_string_number() {
|
||||
let rt = Runtime::new().unwrap();
|
||||
let ctx = Context::full(&rt).unwrap();
|
||||
|
||||
ctx.with(|ctx| {
|
||||
// Test string conversion
|
||||
let val: Value = ctx.eval("'hello'").unwrap();
|
||||
let any: AnyOf2<String, i32> = AnyOf2::from_js(&ctx, val).unwrap();
|
||||
assert!(any.is_a());
|
||||
assert_eq!(any.as_a().unwrap(), "hello");
|
||||
assert!(!any.is_b());
|
||||
assert!(any.as_b().is_none());
|
||||
|
||||
// Test number conversion
|
||||
let val: Value = ctx.eval("42").unwrap();
|
||||
let any: AnyOf2<String, i32> = AnyOf2::from_js(&ctx, val).unwrap();
|
||||
assert!(!any.is_a());
|
||||
assert!(any.is_b());
|
||||
assert_eq!(*any.as_b().unwrap(), 42);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_any_of_fallback() {
|
||||
let rt = Runtime::new().unwrap();
|
||||
let ctx = Context::full(&rt).unwrap();
|
||||
|
||||
ctx.with(|ctx| {
|
||||
// Test that it tries in order
|
||||
let val: Value = ctx.eval("true").unwrap();
|
||||
let any: AnyOf3<String, i32, bool> = AnyOf3::from_js(&ctx, val).unwrap();
|
||||
assert!(any.is_c());
|
||||
assert!(*any.as_c().unwrap());
|
||||
assert!(!any.is_a());
|
||||
assert!(!any.is_b());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_any_of_into_js() {
|
||||
let rt = Runtime::new().unwrap();
|
||||
let ctx = Context::full(&rt).unwrap();
|
||||
|
||||
ctx.with(|ctx| {
|
||||
let any: AnyOf2<String, i32> = AnyOf2::A("test".to_string());
|
||||
let val: Value = any.into_js(&ctx).unwrap();
|
||||
let result: String = val.get().unwrap();
|
||||
assert_eq!(result, "test");
|
||||
|
||||
let any: AnyOf2<String, i32> = AnyOf2::B(99);
|
||||
let val: Value = any.into_js(&ctx).unwrap();
|
||||
let result: i32 = val.get().unwrap();
|
||||
assert_eq!(result, 99);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_any_of_methods() {
|
||||
let rt = Runtime::new().unwrap();
|
||||
let ctx = Context::full(&rt).unwrap();
|
||||
|
||||
ctx.with(|ctx| {
|
||||
// Test all methods for variant A
|
||||
let val: Value = ctx.eval("'test'").unwrap();
|
||||
let any: AnyOf3<String, i32, bool> = AnyOf3::from_js(&ctx, val).unwrap();
|
||||
assert!(any.is_a());
|
||||
assert_eq!(any.as_a().unwrap(), "test");
|
||||
assert_eq!(any.into_a().unwrap(), "test");
|
||||
|
||||
// Test all methods for variant B
|
||||
let val: Value = ctx.eval("42").unwrap();
|
||||
let any: AnyOf3<String, i32, bool> = AnyOf3::from_js(&ctx, val).unwrap();
|
||||
assert!(any.is_b());
|
||||
assert_eq!(*any.as_b().unwrap(), 42);
|
||||
assert_eq!(any.into_b().unwrap(), 42);
|
||||
|
||||
// Test all methods for variant C
|
||||
let val: Value = ctx.eval("true").unwrap();
|
||||
let any: AnyOf3<String, i32, bool> = AnyOf3::from_js(&ctx, val).unwrap();
|
||||
assert!(any.is_c());
|
||||
assert!(*any.as_c().unwrap());
|
||||
assert!(any.into_c().unwrap());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_any_of_mutable_methods() {
|
||||
let rt = Runtime::new().unwrap();
|
||||
let ctx = Context::full(&rt).unwrap();
|
||||
|
||||
ctx.with(|ctx| {
|
||||
let val: Value = ctx.eval("42").unwrap();
|
||||
let mut any: AnyOf4<String, i32, bool, f64> = AnyOf4::from_js(&ctx, val).unwrap();
|
||||
|
||||
if let Some(n) = any.as_b_mut() {
|
||||
*n = 100;
|
||||
}
|
||||
|
||||
assert_eq!(any.into_b().unwrap(), 100);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_any_of_error_propagation() {
|
||||
use rquickjs::{Array, Object};
|
||||
|
||||
let rt = Runtime::new().unwrap();
|
||||
let ctx = Context::full(&rt).unwrap();
|
||||
|
||||
ctx.with(|ctx| {
|
||||
// Test that conversion errors cause fallback to next type
|
||||
let val: Value = ctx.eval("42").unwrap();
|
||||
let any: AnyOf2<String, i32> = AnyOf2::from_js(&ctx, val).unwrap();
|
||||
assert!(any.is_b());
|
||||
|
||||
// Test that all types fail results in an error
|
||||
let val: Value = ctx.eval("null").unwrap();
|
||||
let result: Result<AnyOf2<Object, Array>> = AnyOf2::from_js(&ctx, val);
|
||||
assert!(result.is_err());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_any_of_conversion_order() {
|
||||
let rt = Runtime::new().unwrap();
|
||||
let ctx = Context::full(&rt).unwrap();
|
||||
|
||||
ctx.with(|ctx| {
|
||||
// Test that conversion happens in order A, B, C, D, E
|
||||
// Since 42 can be converted to f64, i32, etc., but String comes first and fails,
|
||||
// it should try the next successful conversion
|
||||
let val: Value = ctx.eval("42").unwrap();
|
||||
|
||||
// String should fail, so it tries i32 which succeeds
|
||||
let any: AnyOf3<String, i32, f64> = AnyOf3::from_js(&ctx, val).unwrap();
|
||||
assert!(any.is_b());
|
||||
|
||||
// If we flip the order, f64 would be tried first (but both work)
|
||||
let val: Value = ctx.eval("3.14").unwrap();
|
||||
let any: AnyOf3<String, f64, i32> = AnyOf3::from_js(&ctx, val).unwrap();
|
||||
assert!(any.is_b()); // f64 should succeed first
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use std::{
|
||||
cmp::min,
|
||||
collections::VecDeque,
|
||||
sync::{
|
||||
atomic::{AtomicBool, AtomicUsize, Ordering},
|
||||
Arc, Mutex,
|
||||
},
|
||||
};
|
||||
|
||||
use tokio::sync::{Notify, Semaphore};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BytearrayBuffer {
|
||||
inner: Arc<Mutex<VecDeque<u8>>>,
|
||||
max_capacity: Arc<AtomicUsize>,
|
||||
len: Arc<AtomicUsize>,
|
||||
notify: Arc<Notify>,
|
||||
closed: Arc<AtomicBool>,
|
||||
write_semaphore: Arc<Semaphore>,
|
||||
}
|
||||
|
||||
impl BytearrayBuffer {
|
||||
pub fn new(capacity: usize) -> Self {
|
||||
let queue = VecDeque::with_capacity(capacity);
|
||||
let capacity = queue.capacity();
|
||||
Self {
|
||||
inner: Arc::new(Mutex::new(queue)),
|
||||
len: Arc::new(AtomicUsize::new(0)),
|
||||
max_capacity: Arc::new(AtomicUsize::new(capacity)),
|
||||
notify: Arc::new(Notify::new()),
|
||||
closed: Arc::new(AtomicBool::new(false)),
|
||||
write_semaphore: Arc::new(Semaphore::new(1)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.len.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn write_forced(&self, item: &[u8]) {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
inner.extend(item);
|
||||
let capacity = inner.capacity();
|
||||
self.len.fetch_add(item.len(), Ordering::Relaxed);
|
||||
self.max_capacity.store(capacity, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub async fn write(&self, item: &mut [u8]) -> usize {
|
||||
let _ = self.write_semaphore.acquire().await.unwrap();
|
||||
let mut slice_index = 0;
|
||||
loop {
|
||||
let max_capacity = self.max_capacity.load(Ordering::Relaxed);
|
||||
if self.closed.load(Ordering::Relaxed) {
|
||||
return max_capacity;
|
||||
}
|
||||
|
||||
let len = self.len.load(Ordering::Relaxed);
|
||||
|
||||
let available = max_capacity - len;
|
||||
|
||||
if available > 0 {
|
||||
let end_index = min(item.len() - 1, slice_index + available - 1);
|
||||
let sub_slice = &item[slice_index..=end_index];
|
||||
let slice_length = sub_slice.len();
|
||||
slice_index += slice_length;
|
||||
|
||||
self.inner.lock().unwrap().extend(sub_slice);
|
||||
self.len.fetch_add(slice_length, Ordering::Relaxed);
|
||||
|
||||
if slice_index == item.len() {
|
||||
return max_capacity;
|
||||
}
|
||||
}
|
||||
self.notify.notified().await;
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn is_closed(&self) -> bool {
|
||||
self.closed.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub async fn close(&self) {
|
||||
self.closed.store(true, Ordering::Relaxed);
|
||||
self.notify.notify_one();
|
||||
//wait for write to finish
|
||||
let _ = self.write_semaphore.acquire().await.unwrap();
|
||||
}
|
||||
|
||||
pub async fn clear(&self) {
|
||||
self.closed.store(false, Ordering::Relaxed);
|
||||
self.notify.notify_one();
|
||||
//wait for write to finish
|
||||
let _ = self.write_semaphore.acquire().await.unwrap();
|
||||
self.len.store(0, Ordering::Relaxed);
|
||||
self.inner.lock().unwrap().clear();
|
||||
self.closed.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn read(&self, desired_size: Option<usize>) -> Option<Vec<u8>> {
|
||||
let mut inner = self.inner.lock().unwrap();
|
||||
let done = self.closed.load(Ordering::Relaxed);
|
||||
|
||||
let items = if done {
|
||||
Some(inner.drain(0..).collect())
|
||||
} else if let Some(desired_len) = desired_size {
|
||||
let max_capacity = self.max_capacity.load(Ordering::Relaxed);
|
||||
if desired_len > max_capacity {
|
||||
let diff = desired_len - max_capacity;
|
||||
inner.reserve(diff - 1);
|
||||
let mut max_capacity = inner.capacity();
|
||||
if desired_len > max_capacity {
|
||||
inner.reserve(desired_len - max_capacity);
|
||||
max_capacity = inner.capacity();
|
||||
}
|
||||
drop(inner);
|
||||
self.max_capacity.store(max_capacity, Ordering::Relaxed);
|
||||
self.notify.notify_one();
|
||||
return None;
|
||||
}
|
||||
|
||||
let len = self.len.load(Ordering::Relaxed);
|
||||
if desired_len > len {
|
||||
self.notify.notify_one();
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(inner.drain(0..desired_len).collect())
|
||||
} else {
|
||||
Some(inner.drain(0..).collect())
|
||||
};
|
||||
self.len.store(inner.len(), Ordering::Relaxed);
|
||||
drop(inner);
|
||||
self.notify.notify_one();
|
||||
items
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::BytearrayBuffer;
|
||||
|
||||
#[tokio::test]
|
||||
async fn clear_while_writing() {
|
||||
let queue = BytearrayBuffer::new(8);
|
||||
let queue2 = queue.clone();
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
let mut vec: Vec<u8> = (0..=255).collect();
|
||||
queue.write(&mut vec).await;
|
||||
});
|
||||
|
||||
queue2.clear().await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn write_one_at_a_time() {
|
||||
let queue = BytearrayBuffer::new(8);
|
||||
let queue2 = queue.clone();
|
||||
let queue3 = queue.clone();
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
let mut vec: Vec<u8> = (0..=127).collect();
|
||||
queue.write(&mut vec).await;
|
||||
});
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
let mut vec: Vec<u8> = (128..=255).collect();
|
||||
queue2.write(&mut vec).await;
|
||||
});
|
||||
|
||||
let mut data = Vec::<u8>::new();
|
||||
|
||||
loop {
|
||||
tokio::task::yield_now().await;
|
||||
if let Some(bytes) = queue3.read(Some(256)) {
|
||||
data.extend(bytes);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//assert that data in vec is increment from 0 to 255
|
||||
for i in 0..=255 {
|
||||
assert_eq!(data[i as usize], i);
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn queue() {
|
||||
let queue = BytearrayBuffer::new(8);
|
||||
let queue2 = queue.clone();
|
||||
|
||||
let write_task = tokio::task::spawn(async move {
|
||||
for _ in 0..=255 {
|
||||
let mut vec: Vec<u8> = (0..=255).collect();
|
||||
queue.write(&mut vec).await;
|
||||
}
|
||||
queue.close().await;
|
||||
});
|
||||
|
||||
let mut data = Vec::<u8>::new();
|
||||
|
||||
loop {
|
||||
let done = queue2.is_closed();
|
||||
|
||||
tokio::task::yield_now().await;
|
||||
if let Some(bytes) = queue2.read(Some(9)) {
|
||||
data.extend(bytes);
|
||||
}
|
||||
if done {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let _ = write_task.await;
|
||||
|
||||
assert_eq!(data.len(), 256 * 256)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use std::rc::Rc;
|
||||
|
||||
use rquickjs::{
|
||||
atom::PredefinedAtom,
|
||||
class::{Trace, Tracer},
|
||||
function::Constructor,
|
||||
ArrayBuffer, Coerced, Ctx, Exception, FromJs, IntoJs, JsLifetime, Object, Result, TypedArray,
|
||||
Value,
|
||||
};
|
||||
|
||||
use crate::{error_messages::ERROR_MSG_ARRAY_BUFFER_DETACHED, result::ResultExt};
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
pub enum ObjectBytes<'js> {
|
||||
U8Array(TypedArray<'js, u8>),
|
||||
I8Array(TypedArray<'js, i8>),
|
||||
U16Array(TypedArray<'js, u16>),
|
||||
I16Array(TypedArray<'js, i16>),
|
||||
U32Array(TypedArray<'js, u32>),
|
||||
I32Array(TypedArray<'js, i32>),
|
||||
U64Array(TypedArray<'js, u64>),
|
||||
I64Array(TypedArray<'js, i64>),
|
||||
F32Array(TypedArray<'js, f32>),
|
||||
F64Array(TypedArray<'js, f64>),
|
||||
DataView(ArrayBuffer<'js>),
|
||||
Vec(Vec<u8>),
|
||||
}
|
||||
|
||||
// Requires manual implementation because rquickjs hasn't implemented JsLifetime for f32 or f64
|
||||
unsafe impl<'js> JsLifetime<'js> for ObjectBytes<'js> {
|
||||
type Changed<'to> = ObjectBytes<'to>;
|
||||
}
|
||||
|
||||
impl<'js> Trace<'js> for ObjectBytes<'js> {
|
||||
fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
|
||||
match self {
|
||||
ObjectBytes::U8Array(a) => a.trace(tracer),
|
||||
ObjectBytes::I8Array(a) => a.trace(tracer),
|
||||
ObjectBytes::U16Array(a) => a.trace(tracer),
|
||||
ObjectBytes::I16Array(a) => a.trace(tracer),
|
||||
ObjectBytes::U32Array(a) => a.trace(tracer),
|
||||
ObjectBytes::I32Array(a) => a.trace(tracer),
|
||||
ObjectBytes::U64Array(a) => a.trace(tracer),
|
||||
ObjectBytes::I64Array(a) => a.trace(tracer),
|
||||
ObjectBytes::F32Array(a) => a.trace(tracer),
|
||||
ObjectBytes::F64Array(a) => a.trace(tracer),
|
||||
ObjectBytes::DataView(d) => d.trace(tracer),
|
||||
ObjectBytes::Vec(v) => v.trace(tracer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'js> IntoJs<'js> for ObjectBytes<'js> {
|
||||
fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
|
||||
match self {
|
||||
ObjectBytes::U8Array(a) => a.into_js(ctx),
|
||||
ObjectBytes::I8Array(a) => a.into_js(ctx),
|
||||
ObjectBytes::U16Array(a) => a.into_js(ctx),
|
||||
ObjectBytes::I16Array(a) => a.into_js(ctx),
|
||||
ObjectBytes::U32Array(a) => a.into_js(ctx),
|
||||
ObjectBytes::I32Array(a) => a.into_js(ctx),
|
||||
ObjectBytes::U64Array(a) => a.into_js(ctx),
|
||||
ObjectBytes::I64Array(a) => a.into_js(ctx),
|
||||
ObjectBytes::F32Array(a) => a.into_js(ctx),
|
||||
ObjectBytes::F64Array(a) => a.into_js(ctx),
|
||||
ObjectBytes::DataView(d) => {
|
||||
let ctor: Constructor = ctx.globals().get(PredefinedAtom::DataView)?;
|
||||
ctor.construct((d,))
|
||||
},
|
||||
ObjectBytes::Vec(v) => v.into_js(ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'js> TryFrom<ObjectBytes<'js>> for Vec<u8> {
|
||||
type Error = Rc<str>;
|
||||
fn try_from(value: ObjectBytes<'js>) -> std::result::Result<Self, Self::Error> {
|
||||
value.into_bytes_inner()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'js> TryFrom<&'a ObjectBytes<'js>> for &'a [u8] {
|
||||
type Error = Rc<str>;
|
||||
fn try_from(value: &'a ObjectBytes<'js>) -> std::result::Result<Self, Self::Error> {
|
||||
value.as_bytes_inner()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'js> FromJs<'js> for ObjectBytes<'js> {
|
||||
fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
|
||||
Self::from_offset(ctx, &value, 0, None)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'js> ObjectBytes<'js> {
|
||||
pub fn from(ctx: &Ctx<'js>, value: &Value<'js>) -> Result<Self> {
|
||||
Self::from_offset(ctx, value, 0, None)
|
||||
}
|
||||
|
||||
pub fn from_offset(
|
||||
ctx: &Ctx<'js>,
|
||||
value: &Value<'js>,
|
||||
offset: usize,
|
||||
length: Option<usize>,
|
||||
) -> Result<Self> {
|
||||
if value.is_undefined() {
|
||||
return Ok(ObjectBytes::Vec(vec![]));
|
||||
}
|
||||
if let Some(bytes) = get_string_bytes(value, offset, length)? {
|
||||
return Ok(ObjectBytes::Vec(bytes));
|
||||
}
|
||||
if let Some(bytes) = get_array_bytes(value, offset, length)? {
|
||||
return Ok(ObjectBytes::Vec(bytes));
|
||||
}
|
||||
|
||||
if let Some(obj) = value.as_object() {
|
||||
if let Some(bytes) = Self::from_array_buffer(obj)? {
|
||||
return Ok(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(bytes) = get_coerced_string_bytes(value, offset, length) {
|
||||
return Ok(ObjectBytes::Vec(bytes));
|
||||
}
|
||||
|
||||
Err(Exception::throw_message(
|
||||
ctx,
|
||||
"value must be typed DataView, Buffer, ArrayBuffer, Uint8Array or interpretable as string",
|
||||
))
|
||||
}
|
||||
|
||||
pub fn as_bytes(&self, ctx: &Ctx<'js>) -> Result<&[u8]> {
|
||||
self.as_bytes_inner().or_throw(ctx)
|
||||
}
|
||||
|
||||
fn as_bytes_inner(&self) -> std::result::Result<&[u8], Rc<str>> {
|
||||
match self {
|
||||
ObjectBytes::U8Array(array) => array.as_bytes(),
|
||||
ObjectBytes::I8Array(array) => array.as_bytes(),
|
||||
ObjectBytes::U16Array(array) => array.as_bytes(),
|
||||
ObjectBytes::I16Array(array) => array.as_bytes(),
|
||||
ObjectBytes::U32Array(array) => array.as_bytes(),
|
||||
ObjectBytes::I32Array(array) => array.as_bytes(),
|
||||
ObjectBytes::U64Array(array) => array.as_bytes(),
|
||||
ObjectBytes::I64Array(array) => array.as_bytes(),
|
||||
ObjectBytes::F32Array(array) => array.as_bytes(),
|
||||
ObjectBytes::F64Array(array) => array.as_bytes(),
|
||||
ObjectBytes::DataView(array_buffer) => array_buffer.as_bytes(),
|
||||
ObjectBytes::Vec(bytes) => Some(bytes.as_ref()),
|
||||
}
|
||||
.ok_or(ERROR_MSG_ARRAY_BUFFER_DETACHED.into())
|
||||
}
|
||||
|
||||
pub fn into_bytes(self, ctx: &Ctx<'_>) -> Result<Vec<u8>> {
|
||||
self.into_bytes_inner().or_throw(ctx)
|
||||
}
|
||||
|
||||
fn into_bytes_inner(self) -> std::result::Result<Vec<u8>, Rc<str>> {
|
||||
if let ObjectBytes::Vec(bytes) = self {
|
||||
return Ok(bytes);
|
||||
}
|
||||
Ok(self.as_bytes_inner()?.to_vec())
|
||||
}
|
||||
|
||||
pub fn from_array_buffer(obj: &Object<'js>) -> Result<Option<ObjectBytes<'js>>> {
|
||||
//most common
|
||||
if let Ok(typed_array) = TypedArray::<u8>::from_object(obj.clone()) {
|
||||
return Ok(Some(ObjectBytes::U8Array(typed_array)));
|
||||
}
|
||||
//second most common
|
||||
if let Some(array_buffer) = ArrayBuffer::from_object(obj.clone()) {
|
||||
return Ok(Some(ObjectBytes::DataView(array_buffer)));
|
||||
}
|
||||
|
||||
if let Ok(typed_array) = TypedArray::<i8>::from_object(obj.clone()) {
|
||||
return Ok(Some(ObjectBytes::I8Array(typed_array)));
|
||||
}
|
||||
|
||||
if let Ok(typed_array) = TypedArray::<u16>::from_object(obj.clone()) {
|
||||
return Ok(Some(ObjectBytes::U16Array(typed_array)));
|
||||
}
|
||||
|
||||
if let Ok(typed_array) = TypedArray::<i16>::from_object(obj.clone()) {
|
||||
return Ok(Some(ObjectBytes::I16Array(typed_array)));
|
||||
}
|
||||
|
||||
if let Ok(typed_array) = TypedArray::<u32>::from_object(obj.clone()) {
|
||||
return Ok(Some(ObjectBytes::U32Array(typed_array)));
|
||||
}
|
||||
|
||||
if let Ok(typed_array) = TypedArray::<i32>::from_object(obj.clone()) {
|
||||
return Ok(Some(ObjectBytes::I32Array(typed_array)));
|
||||
}
|
||||
|
||||
if let Ok(typed_array) = TypedArray::<u64>::from_object(obj.clone()) {
|
||||
return Ok(Some(ObjectBytes::U64Array(typed_array)));
|
||||
}
|
||||
|
||||
if let Ok(typed_array) = TypedArray::<i64>::from_object(obj.clone()) {
|
||||
return Ok(Some(ObjectBytes::I64Array(typed_array)));
|
||||
}
|
||||
|
||||
if let Ok(typed_array) = TypedArray::<f32>::from_object(obj.clone()) {
|
||||
return Ok(Some(ObjectBytes::F32Array(typed_array)));
|
||||
}
|
||||
|
||||
if let Ok(typed_array) = TypedArray::<f64>::from_object(obj.clone()) {
|
||||
return Ok(Some(ObjectBytes::F64Array(typed_array)));
|
||||
}
|
||||
|
||||
if let Ok(array_buffer) = obj.get::<_, ArrayBuffer>("buffer") {
|
||||
return Ok(Some(ObjectBytes::DataView(array_buffer)));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn get_array_buffer(&self) -> Result<Option<(ArrayBuffer<'js>, usize, usize)>> {
|
||||
let buffer = match self {
|
||||
ObjectBytes::DataView(array_buffer) => (array_buffer.clone(), array_buffer.len(), 0),
|
||||
ObjectBytes::U8Array(typed_array) => {
|
||||
let byte_length = typed_array.len();
|
||||
(
|
||||
typed_array.arraybuffer()?,
|
||||
byte_length,
|
||||
typed_array.get("byteOffset")?,
|
||||
)
|
||||
},
|
||||
ObjectBytes::I8Array(typed_array) => {
|
||||
let byte_length = typed_array.len();
|
||||
(
|
||||
typed_array.arraybuffer()?,
|
||||
byte_length,
|
||||
typed_array.get("byteOffset")?,
|
||||
)
|
||||
},
|
||||
ObjectBytes::U16Array(typed_array) => {
|
||||
let byte_length = typed_array.len() * 2;
|
||||
(
|
||||
typed_array.arraybuffer()?,
|
||||
byte_length,
|
||||
typed_array.get("byteOffset")?,
|
||||
)
|
||||
},
|
||||
ObjectBytes::I16Array(typed_array) => {
|
||||
let byte_length = typed_array.len() * 2;
|
||||
(
|
||||
typed_array.arraybuffer()?,
|
||||
byte_length,
|
||||
typed_array.get("byteOffset")?,
|
||||
)
|
||||
},
|
||||
ObjectBytes::U32Array(typed_array) => {
|
||||
let byte_length = typed_array.len() * 4;
|
||||
(
|
||||
typed_array.arraybuffer()?,
|
||||
byte_length,
|
||||
typed_array.get("byteOffset")?,
|
||||
)
|
||||
},
|
||||
ObjectBytes::I32Array(typed_array) => {
|
||||
let byte_length = typed_array.len() * 4;
|
||||
(
|
||||
typed_array.arraybuffer()?,
|
||||
byte_length,
|
||||
typed_array.get("byteOffset")?,
|
||||
)
|
||||
},
|
||||
ObjectBytes::U64Array(typed_array) => {
|
||||
let byte_length = typed_array.len() * 8;
|
||||
(
|
||||
typed_array.arraybuffer()?,
|
||||
byte_length,
|
||||
typed_array.get("byteOffset")?,
|
||||
)
|
||||
},
|
||||
ObjectBytes::I64Array(typed_array) => {
|
||||
let byte_length = typed_array.len() * 8;
|
||||
(
|
||||
typed_array.arraybuffer()?,
|
||||
byte_length,
|
||||
typed_array.get("byteOffset")?,
|
||||
)
|
||||
},
|
||||
ObjectBytes::F32Array(typed_array) => {
|
||||
let byte_length = typed_array.len() * 4;
|
||||
(
|
||||
typed_array.arraybuffer()?,
|
||||
byte_length,
|
||||
typed_array.get("byteOffset")?,
|
||||
)
|
||||
},
|
||||
ObjectBytes::F64Array(typed_array) => {
|
||||
let byte_length = typed_array.len() * 8;
|
||||
(
|
||||
typed_array.arraybuffer()?,
|
||||
byte_length,
|
||||
typed_array.get("byteOffset")?,
|
||||
)
|
||||
},
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
Ok(Some(buffer))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_start_end_indexes(
|
||||
source_len: usize,
|
||||
target_len: Option<usize>,
|
||||
offset: usize,
|
||||
) -> (usize, usize) {
|
||||
if offset > source_len {
|
||||
return (0, 0);
|
||||
}
|
||||
|
||||
let target_len = target_len.unwrap_or(source_len - offset);
|
||||
|
||||
if offset + target_len > source_len {
|
||||
return (offset, source_len);
|
||||
}
|
||||
|
||||
(offset, target_len + offset)
|
||||
}
|
||||
|
||||
pub fn get_array_bytes(
|
||||
value: &Value<'_>,
|
||||
offset: usize,
|
||||
length: Option<usize>,
|
||||
) -> Result<Option<Vec<u8>>> {
|
||||
if value.is_array() {
|
||||
let array = value.as_array().unwrap();
|
||||
let (start, end) = get_start_end_indexes(array.len(), length, offset);
|
||||
let size = end - start;
|
||||
let mut bytes: Vec<u8> = Vec::with_capacity(size);
|
||||
|
||||
for val in array.iter::<u8>().skip(start).take(size) {
|
||||
let val: u8 = val?;
|
||||
bytes.push(val);
|
||||
}
|
||||
|
||||
return Ok(Some(bytes));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn get_coerced_string_bytes(
|
||||
value: &Value<'_>,
|
||||
offset: usize,
|
||||
length: Option<usize>,
|
||||
) -> Option<Vec<u8>> {
|
||||
if let Ok(val) = value.get::<Coerced<String>>() {
|
||||
return Some(bytes_from_js_string(val.0, offset, length));
|
||||
};
|
||||
None
|
||||
}
|
||||
|
||||
fn bytes_from_js_string(string: String, offset: usize, length: Option<usize>) -> Vec<u8> {
|
||||
let (start, end) = get_start_end_indexes(string.len(), length, offset);
|
||||
string.as_bytes()[start..end].to_vec()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_string_bytes(
|
||||
value: &Value<'_>,
|
||||
offset: usize,
|
||||
length: Option<usize>,
|
||||
) -> Result<Option<Vec<u8>>> {
|
||||
if let Some(val) = value.as_string() {
|
||||
let string = val.to_string()?;
|
||||
return Ok(Some(bytes_from_js_string(string, offset, length)));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn bytes_to_typed_array<'js>(ctx: Ctx<'js>, bytes: &[u8]) -> Result<Value<'js>> {
|
||||
TypedArray::<u8>::new(ctx.clone(), bytes).into_js(&ctx)
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use rquickjs::{
|
||||
atom::PredefinedAtom, class::JsClass, object::Accessor, prelude::This, Array, Class, Ctx,
|
||||
Function, Object, Result, Value,
|
||||
};
|
||||
|
||||
use crate::primordials::{BasePrimordials, Primordial};
|
||||
|
||||
use super::{object::ObjectExt, result::OptionExt};
|
||||
|
||||
pub static CUSTOM_INSPECT_SYMBOL_DESCRIPTION: &str = "llrt.inspect.custom";
|
||||
|
||||
pub trait IteratorDef<'js>
|
||||
where
|
||||
Self: 'js + JsClass<'js> + Sized,
|
||||
{
|
||||
fn js_entries(&self, ctx: Ctx<'js>) -> Result<Array<'js>>;
|
||||
|
||||
fn js_iterator(&self, ctx: Ctx<'js>) -> Result<Value<'js>> {
|
||||
let value = self.js_entries(ctx)?;
|
||||
let obj = value.as_object();
|
||||
let values_fn: Function = obj.get(PredefinedAtom::Values)?;
|
||||
values_fn.call((This(value),))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_class_name(value: &Value) -> Result<Option<String>> {
|
||||
value
|
||||
.get_optional::<_, Object>(PredefinedAtom::Constructor)?
|
||||
.and_then_ok(|ctor| ctor.get_optional::<_, String>(PredefinedAtom::Name))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub fn get_class<'js, C>(provided: &Value<'js>) -> Result<Option<Class<'js, C>>>
|
||||
where
|
||||
C: JsClass<'js>,
|
||||
{
|
||||
if provided
|
||||
.as_object()
|
||||
.map(|p| p.instance_of::<C>())
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return Ok(Some(Class::<C>::from_value(provided)?));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub trait CustomInspectExtension<'js> {
|
||||
fn define_with_custom_inspect(globals: &Object<'js>) -> Result<()>;
|
||||
}
|
||||
|
||||
pub trait CustomInspect<'js>
|
||||
where
|
||||
Self: JsClass<'js>,
|
||||
{
|
||||
fn custom_inspect(&self, ctx: Ctx<'js>) -> Result<Object<'js>>;
|
||||
}
|
||||
|
||||
impl<'js, C> CustomInspectExtension<'js> for Class<'js, C>
|
||||
where
|
||||
C: JsClass<'js> + CustomInspect<'js> + 'js,
|
||||
{
|
||||
fn define_with_custom_inspect(globals: &Object<'js>) -> Result<()> {
|
||||
Self::define(globals)?;
|
||||
let custom_inspect_symbol = BasePrimordials::get(globals.ctx())?
|
||||
.symbol_custom_inspect
|
||||
.clone();
|
||||
if let Some(proto) = Class::<C>::prototype(globals.ctx())? {
|
||||
proto.prop(
|
||||
custom_inspect_symbol,
|
||||
Accessor::from(|this: This<Class<'js, C>>, ctx| this.borrow().custom_inspect(ctx)),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,487 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use rquickjs::{
|
||||
atom::PredefinedAtom,
|
||||
function::{Constructor, Opt, This},
|
||||
Array, ArrayBuffer, Ctx, Exception, Function, IntoJs, Null, Object, Result, Type, Value,
|
||||
};
|
||||
|
||||
use super::{
|
||||
hash,
|
||||
object::ObjectExt,
|
||||
primordials::{BasePrimordials, Primordial},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum StackItem<'js> {
|
||||
Value(usize, Value<'js>, Option<String>, Option<usize>),
|
||||
ObjectEnd,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ObjectType {
|
||||
Set,
|
||||
Map,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum TapeValue<'js> {
|
||||
Array(Array<'js>),
|
||||
Object(Object<'js>),
|
||||
Value(Value<'js>),
|
||||
Collection(Option<Value<'js>>, ObjectType),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TapeItem<'js> {
|
||||
parent: usize,
|
||||
object_key: Option<String>,
|
||||
array_index: Option<usize>,
|
||||
value: TapeValue<'js>,
|
||||
}
|
||||
|
||||
pub fn structured_clone<'js>(
|
||||
ctx: &Ctx<'js>,
|
||||
value: Value<'js>,
|
||||
options: Opt<Object<'js>>,
|
||||
) -> Result<Value<'js>> {
|
||||
let primordials = BasePrimordials::get(ctx)?;
|
||||
let mut transfer_set = None;
|
||||
|
||||
if let Some(options) = options.0 {
|
||||
if let Some(transfer_array) = options.get_optional::<_, Array>("transfer")? {
|
||||
let mut set = HashSet::with_capacity(transfer_array.len());
|
||||
|
||||
for item in transfer_array.iter::<Value>() {
|
||||
set.insert(item?);
|
||||
}
|
||||
transfer_set = Some(set);
|
||||
}
|
||||
}
|
||||
|
||||
let mut tape = Vec::<TapeItem>::with_capacity(10);
|
||||
let mut stack = Vec::with_capacity(10);
|
||||
let mut visited = Vec::<(usize, usize)>::with_capacity(10);
|
||||
let mut index = 0usize;
|
||||
|
||||
stack.push(StackItem::Value(0, value, None, None));
|
||||
|
||||
while let Some(item) = stack.pop() {
|
||||
match item {
|
||||
StackItem::Value(parent, value, mut object_key, array_index) => {
|
||||
if let Some(set) = &transfer_set {
|
||||
if let Some(value) = set.get(&value) {
|
||||
append_transfer_value(&mut tape, value, parent, object_key, array_index)?;
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
match value.type_of() {
|
||||
Type::Proxy => {
|
||||
return Err(Exception::throw_type(
|
||||
ctx,
|
||||
"A Proxy value could not be cloned",
|
||||
));
|
||||
},
|
||||
Type::Object => {
|
||||
if check_circular(
|
||||
&mut tape,
|
||||
&mut visited,
|
||||
&value,
|
||||
parent,
|
||||
&mut object_key,
|
||||
array_index,
|
||||
index,
|
||||
) {
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
//unsafe OK since we're guaranteed to be object by the match
|
||||
let object = unsafe { value.as_object().unwrap_unchecked() };
|
||||
|
||||
if object.is_instance_of(&primordials.constructor_date) {
|
||||
append_ctor_value(
|
||||
&mut tape,
|
||||
object,
|
||||
&primordials.constructor_date,
|
||||
parent,
|
||||
object_key,
|
||||
array_index,
|
||||
)?;
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if object.is_instance_of(&primordials.constructor_regexp) {
|
||||
append_ctor_value(
|
||||
&mut tape,
|
||||
object,
|
||||
&primordials.constructor_regexp,
|
||||
parent,
|
||||
object_key,
|
||||
array_index,
|
||||
)?;
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
let is_collection = if object.is_instance_of(&primordials.constructor_set) {
|
||||
Some(ObjectType::Set)
|
||||
} else if object.is_instance_of(&primordials.constructor_map) {
|
||||
Some(ObjectType::Map)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if let Some(collection_type) = is_collection {
|
||||
append_collection(
|
||||
&mut tape,
|
||||
&primordials.function_array_from,
|
||||
object,
|
||||
parent,
|
||||
object_key,
|
||||
array_index,
|
||||
collection_type,
|
||||
&mut stack,
|
||||
index,
|
||||
)?;
|
||||
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if primordials
|
||||
.function_array_buffer_is_view
|
||||
.call::<_, bool>((value.clone(),))?
|
||||
{
|
||||
append_buffer(&mut tape, object, parent, object_key, array_index)?;
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
let new: Object<'_> =
|
||||
if object.is_instance_of(&primordials.constructor_error) {
|
||||
primordials.constructor_error.construct(("",))
|
||||
} else {
|
||||
Object::new(ctx.clone())
|
||||
}?;
|
||||
|
||||
tape.push(TapeItem {
|
||||
parent,
|
||||
object_key,
|
||||
array_index,
|
||||
value: TapeValue::Object(new),
|
||||
});
|
||||
stack.push(StackItem::ObjectEnd);
|
||||
|
||||
for key in object.keys::<String>() {
|
||||
let key = key?;
|
||||
let value = object.get(&key)?;
|
||||
stack.push(StackItem::Value(index, value, Some(key), None));
|
||||
}
|
||||
},
|
||||
Type::Array => {
|
||||
if check_circular(
|
||||
&mut tape,
|
||||
&mut visited,
|
||||
&value,
|
||||
parent,
|
||||
&mut object_key,
|
||||
array_index,
|
||||
index,
|
||||
) {
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
let new = Array::new(ctx.clone())?;
|
||||
tape.push(TapeItem {
|
||||
parent,
|
||||
object_key,
|
||||
array_index,
|
||||
value: TapeValue::Array(new),
|
||||
});
|
||||
stack.push(StackItem::ObjectEnd);
|
||||
//unsafe OK since we're guaranteed to be object by the match
|
||||
let array = unsafe { value.as_array().unwrap_unchecked() };
|
||||
|
||||
//reverse for loop of items in array
|
||||
for array_index in (0usize..array.len()).rev() {
|
||||
stack.push(StackItem::Value(
|
||||
index,
|
||||
array.get(array_index)?,
|
||||
None,
|
||||
Some(array_index),
|
||||
));
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
tape.push(TapeItem {
|
||||
parent,
|
||||
object_key,
|
||||
array_index,
|
||||
value: TapeValue::Value(value),
|
||||
});
|
||||
},
|
||||
}
|
||||
index += 1;
|
||||
},
|
||||
StackItem::ObjectEnd => {
|
||||
visited.pop();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(item) = tape.pop() {
|
||||
let value = match item.value {
|
||||
TapeValue::Array(array) => array.into_value(),
|
||||
TapeValue::Object(object) => object.into_value(),
|
||||
TapeValue::Value(value) => value,
|
||||
TapeValue::Collection(mut value, _) => value.take().unwrap(),
|
||||
};
|
||||
if tape.is_empty() {
|
||||
return Ok(value);
|
||||
}
|
||||
let parent = &mut tape[item.parent];
|
||||
let array_index = item.array_index;
|
||||
let object_key = item.object_key;
|
||||
match &mut parent.value {
|
||||
TapeValue::Array(array) => {
|
||||
array.set(array_index.unwrap(), value)?;
|
||||
},
|
||||
TapeValue::Object(object) => {
|
||||
let string = object_key.unwrap();
|
||||
object.set(string, value)?;
|
||||
},
|
||||
TapeValue::Collection(collection_value, collection_type) => {
|
||||
match collection_type {
|
||||
ObjectType::Set => {
|
||||
collection_value.replace(primordials.constructor_set.construct((value,))?);
|
||||
},
|
||||
ObjectType::Map => {
|
||||
collection_value.replace(primordials.constructor_map.construct((value,))?);
|
||||
},
|
||||
};
|
||||
},
|
||||
_ => {},
|
||||
};
|
||||
}
|
||||
|
||||
Null.into_js(ctx)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[cold]
|
||||
fn append_buffer<'js>(
|
||||
tape: &mut Vec<TapeItem<'js>>,
|
||||
object: &Object<'js>,
|
||||
parent: usize,
|
||||
object_key: Option<String>,
|
||||
array_index: Option<usize>,
|
||||
) -> Result<()> {
|
||||
let ctor: Constructor = object.get(PredefinedAtom::Constructor)?;
|
||||
let slice: Function = object.get("slice")?;
|
||||
let clone: Value = slice.call((This(object.clone()),))?;
|
||||
let new = ctor.construct((clone,))?;
|
||||
tape.push(TapeItem {
|
||||
parent,
|
||||
object_key,
|
||||
array_index,
|
||||
value: TapeValue::Value(new),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[cold]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn append_collection<'js>(
|
||||
tape: &mut Vec<TapeItem<'js>>,
|
||||
array_from: &Function<'js>,
|
||||
object: &Object<'js>,
|
||||
parent: usize,
|
||||
object_key: Option<String>,
|
||||
array_index: Option<usize>,
|
||||
collection_type: ObjectType,
|
||||
stack: &mut Vec<StackItem<'js>>,
|
||||
index: usize,
|
||||
) -> Result<()> {
|
||||
let array: Array = array_from.call((object.clone(),))?;
|
||||
tape.push(TapeItem {
|
||||
parent,
|
||||
object_key,
|
||||
array_index,
|
||||
value: TapeValue::Collection(None, collection_type),
|
||||
});
|
||||
stack.push(StackItem::ObjectEnd);
|
||||
stack.push(StackItem::Value(index, array.into(), None, None));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn check_circular(
|
||||
tape: &mut Vec<TapeItem>,
|
||||
visited: &mut Vec<(usize, usize)>,
|
||||
value: &Value<'_>,
|
||||
parent: usize,
|
||||
object_key: &mut Option<String>,
|
||||
array_index: Option<usize>,
|
||||
index: usize,
|
||||
) -> bool {
|
||||
let hash = hash::default_hash(value);
|
||||
if let Some(visited) = visited.iter().find(|v| v.0 == hash) {
|
||||
append_circular(tape, visited, object_key, parent, array_index);
|
||||
return true;
|
||||
}
|
||||
visited.push((hash, index));
|
||||
false
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[cold]
|
||||
fn append_transfer_value<'js>(
|
||||
tape: &mut Vec<TapeItem<'js>>,
|
||||
value: &Value<'js>,
|
||||
parent: usize,
|
||||
object_key: Option<String>,
|
||||
array_index: Option<usize>,
|
||||
) -> Result<()> {
|
||||
let value = if let Some(ab) = ArrayBuffer::from_value(value.clone()) {
|
||||
ab.get::<_, Function>("transfer")?.call((This(ab),))?
|
||||
} else {
|
||||
value.clone()
|
||||
};
|
||||
|
||||
tape.push(TapeItem {
|
||||
parent,
|
||||
object_key,
|
||||
array_index,
|
||||
value: TapeValue::Value(value),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[cold]
|
||||
fn append_circular(
|
||||
tape: &mut Vec<TapeItem<'_>>,
|
||||
visited: &(usize, usize),
|
||||
object_key: &mut Option<String>,
|
||||
parent: usize,
|
||||
array_index: Option<usize>,
|
||||
) {
|
||||
let value = match &tape[visited.1].value {
|
||||
TapeValue::Array(array) => array.clone().into_value(),
|
||||
TapeValue::Object(object) => object.clone().into_value(),
|
||||
TapeValue::Value(value) => value.clone(),
|
||||
TapeValue::Collection(value, _) => value.clone().unwrap(),
|
||||
};
|
||||
|
||||
let object_key = object_key.take();
|
||||
|
||||
tape.push(TapeItem {
|
||||
parent,
|
||||
object_key,
|
||||
array_index,
|
||||
value: TapeValue::Value(value),
|
||||
});
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
#[cold]
|
||||
fn append_ctor_value<'js>(
|
||||
tape: &mut Vec<TapeItem<'js>>,
|
||||
object: &Object<'js>,
|
||||
ctor: &Constructor<'js>,
|
||||
parent: usize,
|
||||
object_key: Option<String>,
|
||||
array_index: Option<usize>,
|
||||
) -> Result<()> {
|
||||
let clone: Value = ctor.construct((object.clone(),))?;
|
||||
tape.push(TapeItem {
|
||||
parent,
|
||||
object_key,
|
||||
array_index,
|
||||
value: TapeValue::Value(clone),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use llrt_test::test_sync_with;
|
||||
use rquickjs::{function::Opt, Object, Value};
|
||||
|
||||
use crate::primordials::{BasePrimordials, Primordial};
|
||||
|
||||
use super::structured_clone;
|
||||
|
||||
#[tokio::test]
|
||||
async fn clone() {
|
||||
test_sync_with(|ctx| {
|
||||
BasePrimordials::init(&ctx)?;
|
||||
let value: Object = ctx.eval(
|
||||
r#"
|
||||
const a = {
|
||||
"foo":{
|
||||
"bar":"baz"
|
||||
},
|
||||
"foo1":{
|
||||
"bar1":"baz1",
|
||||
"bar11":"baz11"
|
||||
}
|
||||
};
|
||||
a
|
||||
"#,
|
||||
)?;
|
||||
|
||||
let cloned = structured_clone(&ctx, value.clone().into_value(), Opt(None))?
|
||||
.into_object()
|
||||
.unwrap();
|
||||
|
||||
let json = ctx
|
||||
.json_stringify(value.clone())?
|
||||
.unwrap()
|
||||
.to_string()?
|
||||
.to_string();
|
||||
|
||||
let clone_json = ctx
|
||||
.json_stringify(cloned.clone())?
|
||||
.unwrap()
|
||||
.to_string()?
|
||||
.to_string();
|
||||
|
||||
assert_eq!(json, clone_json);
|
||||
|
||||
assert_ne!(
|
||||
value.get::<_, Value>("foo")?,
|
||||
cloned.get::<_, Value>("foo")?
|
||||
);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn clone_circular() {
|
||||
test_sync_with(|ctx| {
|
||||
BasePrimordials::init(&ctx)?;
|
||||
let _value: Object = ctx.eval(
|
||||
r#"
|
||||
const originalObject = { foo: { bar: "baz",arr: [1,2,3] } };
|
||||
originalObject.foo.circularRef = originalObject;
|
||||
originalObject.foo.circularRef2 = originalObject;
|
||||
originalObject.foo.circularRef3 = originalObject.foo;
|
||||
originalObject.ref2 = originalObject;
|
||||
"#,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use rquickjs::{Ctx, Result};
|
||||
|
||||
pub trait CtxExt {
|
||||
fn get_script_or_module_name(&self) -> Result<String>;
|
||||
}
|
||||
|
||||
impl CtxExt for Ctx<'_> {
|
||||
fn get_script_or_module_name(&self) -> Result<String> {
|
||||
if let Some(name) = self.script_or_module_name(0) {
|
||||
name.to_string()
|
||||
} else {
|
||||
Ok(String::from("."))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use rquickjs::{CatchResultExt, CaughtError, Ctx, Error, IntoJs, Result, Value};
|
||||
|
||||
pub trait ErrorExtensions<'js> {
|
||||
fn into_value(self, ctx: &Ctx<'js>) -> Result<Value<'js>>;
|
||||
}
|
||||
|
||||
impl<'js> ErrorExtensions<'js> for Error {
|
||||
fn into_value(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
|
||||
Err::<(), _>(self).catch(ctx).unwrap_err().into_value(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'js> ErrorExtensions<'js> for CaughtError<'js> {
|
||||
fn into_value(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
|
||||
Ok(match self {
|
||||
CaughtError::Error(err) => err.to_string().into_js(ctx)?,
|
||||
CaughtError::Exception(ex) => ex.into_value(),
|
||||
CaughtError::Value(val) => val,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
pub const ERROR_MSG_NOT_ARRAY_BUFFER: &str = "Not an ArrayBuffer";
|
||||
pub const ERROR_MSG_ARRAY_BUFFER_DETACHED: &str = "ArrayBuffer is detached";
|
||||
pub const ERROR_MSG_BROADCAST_LAGGED: &str = "Lagged too much behind";
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use std::{fs::Metadata, io, path::PathBuf};
|
||||
|
||||
use tokio::fs::{self};
|
||||
|
||||
pub struct DirectoryWalker<T>
|
||||
where
|
||||
T: Fn(&str) -> bool,
|
||||
{
|
||||
stack: Vec<(PathBuf, Option<Metadata>)>,
|
||||
filter: T,
|
||||
recursive: bool,
|
||||
eat_root: bool,
|
||||
}
|
||||
|
||||
impl<T> DirectoryWalker<T>
|
||||
where
|
||||
T: Fn(&str) -> bool,
|
||||
{
|
||||
pub fn new(root: PathBuf, filter: T) -> Self {
|
||||
Self {
|
||||
stack: vec![(root, None)],
|
||||
filter,
|
||||
recursive: false,
|
||||
eat_root: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_recursive(&mut self, recursive: bool) {
|
||||
self.recursive = recursive;
|
||||
}
|
||||
|
||||
pub async fn walk(&mut self) -> io::Result<Option<(PathBuf, Metadata)>> {
|
||||
if self.eat_root {
|
||||
self.eat_root = false;
|
||||
let (dir, _) = self.stack.pop().unwrap();
|
||||
self.append_stack(&dir).await?;
|
||||
}
|
||||
if let Some((entry, metadata)) = self.stack.pop() {
|
||||
let metadata = metadata.unwrap();
|
||||
if self.recursive && metadata.is_dir() {
|
||||
self.append_stack(&entry).await?;
|
||||
}
|
||||
|
||||
Ok(Some((entry, metadata)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk_sync(&mut self) -> io::Result<Option<(PathBuf, Metadata)>> {
|
||||
if self.eat_root {
|
||||
self.eat_root = false;
|
||||
let (dir, _) = self.stack.pop().unwrap();
|
||||
self.append_stack_sync(&dir)?;
|
||||
}
|
||||
if let Some((entry, metadata)) = self.stack.pop() {
|
||||
let metadata = metadata.unwrap();
|
||||
if self.recursive && metadata.is_dir() {
|
||||
self.append_stack_sync(&entry)?;
|
||||
}
|
||||
|
||||
Ok(Some((entry, metadata)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
async fn append_stack(&mut self, dir: &PathBuf) -> io::Result<()> {
|
||||
let mut stream = fs::read_dir(dir).await?;
|
||||
|
||||
while let Some(entry) = stream.next_entry().await? {
|
||||
let name = entry.file_name();
|
||||
let name = name.to_string_lossy();
|
||||
if !(self.filter)(name.as_ref()) {
|
||||
continue;
|
||||
}
|
||||
let entry_path = entry.path();
|
||||
let metadata = fs::symlink_metadata(&entry_path).await?;
|
||||
|
||||
self.stack.push((entry_path, Some(metadata)));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn append_stack_sync(&mut self, dir: &PathBuf) -> io::Result<()> {
|
||||
let dir = std::fs::read_dir(dir)?;
|
||||
|
||||
for entry in dir.flatten() {
|
||||
let name = entry.file_name();
|
||||
let name = name.to_string_lossy();
|
||||
if !(self.filter)(name.as_ref()) {
|
||||
continue;
|
||||
}
|
||||
let entry_path = entry.path();
|
||||
let metadata = entry_path.symlink_metadata()?;
|
||||
self.stack.push((entry_path, Some(metadata)))
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
use std::hash::{DefaultHasher, Hash, Hasher};
|
||||
|
||||
#[inline]
|
||||
pub fn default_hash<T: Hash + ?Sized>(v: &T) -> usize {
|
||||
let mut state = DefaultHasher::default();
|
||||
v.hash(&mut state);
|
||||
state.finish() as usize
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
macro_rules! define_extension {
|
||||
($base:ident, $file:ident, $ext:expr) => {
|
||||
#[allow(dead_code)]
|
||||
pub const $base: &str = $ext;
|
||||
#[allow(dead_code)]
|
||||
pub const $file: &str = concat!(".", $ext);
|
||||
};
|
||||
}
|
||||
|
||||
define_extension!(BYTECODE_EXT, BYTECODE_FILE_EXT, "lrt");
|
||||
|
||||
macro_rules! define_supported_extensions {
|
||||
// Accepts a list of supported extensions and a single additional constant extension
|
||||
($constant_ext:ident, $($ext:literal),*) => {
|
||||
// Define the array of extensions as a constant
|
||||
pub const SUPPORTED_EXTENSIONS: &[&str] = &[$($ext),*, $constant_ext];
|
||||
|
||||
pub const JS_EXTENSIONS: &[&str] = &[$($ext),*];
|
||||
|
||||
// Define the function `is_supported_ext` using a match statement
|
||||
pub fn is_supported_ext(ext: &str) -> bool {
|
||||
matches!(ext, $($ext)|* | $constant_ext)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_supported_extensions!(BYTECODE_FILE_EXT, ".js", ".mjs", ".cjs");
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use tokio::sync::Notify;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Latch {
|
||||
count: AtomicUsize,
|
||||
notify: Notify,
|
||||
}
|
||||
|
||||
impl Latch {
|
||||
pub fn increment(&self) {
|
||||
self.count.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
pub fn decrement(&self) {
|
||||
let previous = self.count.fetch_sub(1, Ordering::Relaxed);
|
||||
if previous == 1 {
|
||||
self.notify.notify_waiters();
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn wait(&self) {
|
||||
if self.count.load(Ordering::Relaxed) > 0 {
|
||||
self.notify.notified().await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pub mod any_of;
|
||||
#[cfg(feature = "bytearray-buffer")]
|
||||
pub mod bytearray_buffer;
|
||||
pub mod bytes;
|
||||
pub mod class;
|
||||
pub mod clone;
|
||||
pub mod ctx;
|
||||
pub mod error;
|
||||
pub mod error_messages;
|
||||
#[cfg(feature = "fs")]
|
||||
pub mod fs;
|
||||
pub mod hash;
|
||||
pub mod io;
|
||||
pub mod latch;
|
||||
pub mod macros;
|
||||
pub mod mc_oneshot;
|
||||
pub mod module;
|
||||
pub mod object;
|
||||
pub mod option;
|
||||
pub mod primordials;
|
||||
pub mod provider;
|
||||
pub mod result;
|
||||
pub mod reuse_list;
|
||||
pub mod string;
|
||||
pub mod sysinfo;
|
||||
pub mod time;
|
||||
|
||||
#[cfg(any(unix, windows))]
|
||||
pub mod signals;
|
||||
|
||||
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#[macro_export]
|
||||
macro_rules! count_members {
|
||||
() => (0);
|
||||
($head:tt $(,$tail:tt)*) => (1 + count_members!($($tail),*));
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! iterable_enum {
|
||||
($name:ident, $($variant:ident),*) => {
|
||||
impl $name {
|
||||
const VARIANTS: &'static [$name] = &[$($name::$variant,)*];
|
||||
pub fn iter() -> std::slice::Iter<'static, $name> {
|
||||
Self::VARIANTS.iter()
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn _ensure_all_variants(s: Self) {
|
||||
match s {
|
||||
$($name::$variant => {},)*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! str_enum {
|
||||
($name:ident, $($variant:ident => $str:expr),*) => {
|
||||
impl $name {
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
$($name::$variant => $str,)*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for $name {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&str> for $name {
|
||||
type Error = String;
|
||||
fn try_from(s: &str) -> std::result::Result<Self, Self::Error> {
|
||||
match s {
|
||||
$($str => Ok($name::$variant),)*
|
||||
_ => Err(["'", s, "' not available"].concat())
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
use std::sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc, RwLock,
|
||||
};
|
||||
|
||||
use rquickjs::{
|
||||
class::{Trace, Tracer},
|
||||
Value,
|
||||
};
|
||||
use std::ops::Deref;
|
||||
use tokio::sync::Notify;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Shared<T> {
|
||||
is_sent: AtomicBool,
|
||||
value: RwLock<Option<T>>,
|
||||
notify: Notify,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Sender<T: Clone>(Arc<Shared<T>>);
|
||||
|
||||
impl<T: Clone> Deref for Sender<T> {
|
||||
type Target = Arc<Shared<T>>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'js> Trace<'js> for Sender<Value<'js>> {
|
||||
fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
|
||||
if let Ok(v) = self.value.read() {
|
||||
if let Some(v) = v.as_ref() {
|
||||
tracer.mark(v)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Sender<T> {
|
||||
pub fn send(&self, value: T) {
|
||||
if !self.is_sent.load(Ordering::Relaxed) {
|
||||
self.value.write().unwrap().replace(value);
|
||||
self.is_sent.store(true, Ordering::Release);
|
||||
self.notify.notify_waiters();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subscribe(&self) -> Receiver<T> {
|
||||
Receiver(Arc::clone(&self.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Receiver<T: Clone>(Arc<Shared<T>>);
|
||||
|
||||
impl<T: Clone> Deref for Receiver<T> {
|
||||
type Target = Arc<Shared<T>>;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Receiver<T> {
|
||||
pub async fn recv(&self) -> T {
|
||||
if !self.is_sent.load(Ordering::Acquire) {
|
||||
self.notify.notified().await;
|
||||
}
|
||||
self.value.read().unwrap().clone().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn channel<T: Clone>() -> (Sender<T>, Receiver<T>) {
|
||||
let shared = Arc::new(Shared {
|
||||
is_sent: AtomicBool::new(false),
|
||||
value: RwLock::new(None),
|
||||
notify: Notify::new(),
|
||||
});
|
||||
|
||||
(Sender(Arc::clone(&shared)), Receiver(shared))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use tokio::join;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test() {
|
||||
let (tx, rx1) = super::channel::<bool>();
|
||||
|
||||
let rx2 = tx.subscribe();
|
||||
let rx3 = tx.subscribe();
|
||||
|
||||
let a = tokio::spawn(async move {
|
||||
let val = rx1.recv().await; //wait for value to become false
|
||||
assert!(val)
|
||||
});
|
||||
|
||||
let b = tokio::spawn(async move {
|
||||
let val = rx2.recv().await; //wait for value to become false
|
||||
assert!(val)
|
||||
});
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_millis(10)).await;
|
||||
|
||||
tx.send(true);
|
||||
|
||||
let val = rx3.recv().await;
|
||||
assert!(val);
|
||||
|
||||
let (a, b) = join!(a, b);
|
||||
a.unwrap();
|
||||
b.unwrap();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use rquickjs::{
|
||||
module::{Exports, ModuleDef},
|
||||
Ctx, Object, Result, Value,
|
||||
};
|
||||
|
||||
pub struct ModuleInfo<T: ModuleDef> {
|
||||
pub name: &'static str,
|
||||
pub module: T,
|
||||
}
|
||||
|
||||
pub fn export_default<'js, F>(ctx: &Ctx<'js>, exports: &Exports<'js>, f: F) -> Result<()>
|
||||
where
|
||||
F: FnOnce(&Object<'js>) -> Result<()>,
|
||||
{
|
||||
let default = Object::new(ctx.clone())?;
|
||||
f(&default)?;
|
||||
|
||||
for name in default.keys::<String>() {
|
||||
let name = name?;
|
||||
let value: Value = default.get(&name)?;
|
||||
exports.export(name, value)?;
|
||||
}
|
||||
|
||||
exports.export("default", default)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use rquickjs::{
|
||||
atom::PredefinedAtom, function::IntoJsFunc, prelude::Func, Array, Coerced, Ctx, Error,
|
||||
Exception, FromJs, IntoAtom, IntoJs, Object, Result, Symbol, Undefined, Value,
|
||||
};
|
||||
|
||||
use crate::primordials::{BasePrimordials, Primordial};
|
||||
|
||||
pub trait ObjectExt<'js> {
|
||||
fn get_optional<K: IntoAtom<'js> + Clone, V: FromJs<'js>>(&self, k: K) -> Result<Option<V>>;
|
||||
fn get_required<K: AsRef<str>, V: FromJs<'js>>(
|
||||
&self,
|
||||
k: K,
|
||||
object_name: &'static str,
|
||||
) -> Result<V>;
|
||||
fn into_object_or_throw(self, ctx: &Ctx<'js>, object_name: &'static str)
|
||||
-> Result<Object<'js>>;
|
||||
}
|
||||
|
||||
impl<'js> ObjectExt<'js> for Object<'js> {
|
||||
fn get_optional<K: IntoAtom<'js> + Clone, V: FromJs<'js> + Sized>(
|
||||
&self,
|
||||
k: K,
|
||||
) -> Result<Option<V>> {
|
||||
self.get::<K, Option<V>>(k)
|
||||
}
|
||||
|
||||
fn get_required<K: AsRef<str>, V: FromJs<'js>>(
|
||||
&self,
|
||||
k: K,
|
||||
object_name: &'static str,
|
||||
) -> Result<V> {
|
||||
let k = k.as_ref();
|
||||
self.get::<&str, Option<V>>(k)?.ok_or_else(|| {
|
||||
Exception::throw_type(
|
||||
self.ctx(),
|
||||
&[object_name, " '", k, "' property required"].concat(),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn into_object_or_throw(self, _: &Ctx<'js>, _: &'static str) -> Result<Object<'js>> {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'js> ObjectExt<'js> for Value<'js> {
|
||||
fn get_optional<K: IntoAtom<'js> + Clone, V: FromJs<'js>>(&self, k: K) -> Result<Option<V>> {
|
||||
if let Some(obj) = self.as_object() {
|
||||
return obj.get_optional(k);
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn get_required<K: AsRef<str>, V: FromJs<'js>>(
|
||||
&self,
|
||||
k: K,
|
||||
object_name: &'static str,
|
||||
) -> Result<V> {
|
||||
self.as_object()
|
||||
.ok_or_else(|| not_a_object_error(self.ctx(), object_name))?
|
||||
.get_required(k, object_name)
|
||||
}
|
||||
|
||||
fn into_object_or_throw(
|
||||
self,
|
||||
ctx: &Ctx<'js>,
|
||||
object_name: &'static str,
|
||||
) -> Result<Object<'js>> {
|
||||
self.into_object()
|
||||
.ok_or_else(|| not_a_object_error(ctx, object_name))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn not_a_object_error(ctx: &Ctx<'_>, object_name: &str) -> Error {
|
||||
Exception::throw_type(ctx, &[object_name, " is not an object"].concat())
|
||||
}
|
||||
|
||||
pub trait CreateSymbol<'js> {
|
||||
fn for_description(ctx: &Ctx<'js>, description: &str) -> Result<Symbol<'js>>;
|
||||
}
|
||||
|
||||
impl<'js> CreateSymbol<'js> for Symbol<'js> {
|
||||
fn for_description(ctx: &Ctx<'js>, description: &str) -> Result<Symbol<'js>> {
|
||||
BasePrimordials::get(ctx)?
|
||||
.function_symbol_for
|
||||
.call((description,))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Proxy<'js> {
|
||||
target: Value<'js>,
|
||||
options: Object<'js>,
|
||||
}
|
||||
|
||||
impl<'js> IntoJs<'js> for Proxy<'js> {
|
||||
fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
|
||||
BasePrimordials::get(ctx)?
|
||||
.constructor_proxy
|
||||
.construct::<_, Value>((self.target, self.options))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'js> Proxy<'js> {
|
||||
pub fn new(ctx: Ctx<'js>) -> Result<Self> {
|
||||
let options = Object::new(ctx.clone())?;
|
||||
Ok(Self {
|
||||
target: Undefined.into_value(ctx),
|
||||
options,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn with_target(ctx: Ctx<'js>, target: Value<'js>) -> Result<Self> {
|
||||
let options = Object::new(ctx)?;
|
||||
Ok(Self { target, options })
|
||||
}
|
||||
|
||||
pub fn setter<T, P>(&self, setter: Func<T, P>) -> Result<()>
|
||||
where
|
||||
T: IntoJsFunc<'js, P> + 'js,
|
||||
{
|
||||
self.options.set(PredefinedAtom::Setter, setter)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn getter<T, P>(&self, getter: Func<T, P>) -> Result<()>
|
||||
where
|
||||
T: IntoJsFunc<'js, P> + 'js,
|
||||
{
|
||||
self.options.set(PredefinedAtom::Getter, getter)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn map_to_entries<'js, K, V, M>(ctx: &Ctx<'js>, map: M) -> Result<Array<'js>>
|
||||
where
|
||||
M: IntoIterator<Item = (K, V)>,
|
||||
K: IntoJs<'js>,
|
||||
V: IntoJs<'js>,
|
||||
{
|
||||
let array = Array::new(ctx.clone())?;
|
||||
for (idx, (key, value)) in map.into_iter().enumerate() {
|
||||
let entry = Array::new(ctx.clone())?;
|
||||
entry.set(0, key)?;
|
||||
entry.set(1, value)?;
|
||||
array.set(idx, entry)?;
|
||||
}
|
||||
|
||||
Ok(array)
|
||||
}
|
||||
|
||||
pub fn array_to_btree_map<'js>(
|
||||
ctx: &Ctx<'js>,
|
||||
array: Array<'js>,
|
||||
) -> Result<BTreeMap<String, Coerced<String>>> {
|
||||
let value = object_from_entries(ctx, array)?;
|
||||
let value = value.into_value();
|
||||
BTreeMap::from_js(ctx, value)
|
||||
}
|
||||
|
||||
pub fn object_from_entries<'js>(ctx: &Ctx<'js>, array: Array<'js>) -> Result<Object<'js>> {
|
||||
let obj = Object::new(ctx.clone())?;
|
||||
for value in array.into_iter().flatten() {
|
||||
if let Some(entry) = value.as_array() {
|
||||
if let Ok(key) = entry.get::<Value>(0) {
|
||||
if let Ok(value) = entry.get::<Value>(1) {
|
||||
let _ = obj.set(key, value); //ignore result of failed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(obj)
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
use rquickjs::{
|
||||
class::{Trace, Tracer},
|
||||
function::{FromParam, ParamRequirement, ParamsAccessor},
|
||||
Ctx, FromJs, IntoJs, JsLifetime, Result, Type, Value,
|
||||
};
|
||||
|
||||
/// Helper type for treating an undefined value as None, without treating null as None
|
||||
#[derive(Clone)]
|
||||
pub struct Undefined<T>(pub Option<T>);
|
||||
|
||||
impl<'js, T: FromJs<'js>> FromJs<'js> for Undefined<T> {
|
||||
fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
|
||||
if value.type_of() == Type::Undefined {
|
||||
Ok(Self(None))
|
||||
} else {
|
||||
Ok(Self(Some(FromJs::from_js(ctx, value)?)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'js, T: IntoJs<'js>> IntoJs<'js> for Undefined<T> {
|
||||
fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
|
||||
match self.0 {
|
||||
None => Ok(Value::new_undefined(ctx.clone())),
|
||||
Some(val) => val.into_js(ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for Undefined<T> {
|
||||
fn default() -> Self {
|
||||
Self(None)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'js, T: JsLifetime<'js>> JsLifetime<'js> for Undefined<T> {
|
||||
type Changed<'to> = Undefined<T::Changed<'to>>;
|
||||
}
|
||||
|
||||
impl<'js, T: Trace<'js>> Trace<'js> for Undefined<T> {
|
||||
fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
|
||||
self.0.trace(tracer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper type for converting an None into null instead of undefined.
|
||||
/// Needed while rquickjs::function::Null has no IntoJs implementation
|
||||
#[derive(Clone)]
|
||||
pub struct Null<T>(pub Option<T>);
|
||||
|
||||
impl<'js, T: FromJs<'js>> FromJs<'js> for Null<T> {
|
||||
fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
|
||||
if value.type_of() == Type::Null {
|
||||
Ok(Self(None))
|
||||
} else {
|
||||
Ok(Self(Some(FromJs::from_js(ctx, value)?)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'js, T: IntoJs<'js>> IntoJs<'js> for Null<T> {
|
||||
fn into_js(self, ctx: &Ctx<'js>) -> Result<Value<'js>> {
|
||||
match self.0 {
|
||||
None => Ok(Value::new_null(ctx.clone())),
|
||||
Some(val) => val.into_js(ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'js, T: JsLifetime<'js>> JsLifetime<'js> for Null<T> {
|
||||
type Changed<'to> = Null<T::Changed<'to>>;
|
||||
}
|
||||
|
||||
impl<'js, T: Trace<'js>> Trace<'js> for Null<T> {
|
||||
fn trace<'a>(&self, tracer: Tracer<'a, 'js>) {
|
||||
self.0.trace(tracer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper type for accepting no value, or null, but considering undefined as a value
|
||||
pub struct NullableOpt<T>(pub Option<T>);
|
||||
|
||||
impl<'js, T: FromJs<'js>> FromParam<'js> for NullableOpt<T> {
|
||||
fn param_requirement() -> ParamRequirement {
|
||||
ParamRequirement::optional()
|
||||
}
|
||||
|
||||
fn from_param<'a>(params: &mut ParamsAccessor<'a, 'js>) -> Result<Self> {
|
||||
if !params.is_empty() {
|
||||
let arg = params.arg();
|
||||
if arg.is_null() {
|
||||
Ok(NullableOpt(None))
|
||||
} else {
|
||||
let ctx = params.ctx().clone();
|
||||
Ok(NullableOpt(Some(T::from_js(&ctx, arg)?)))
|
||||
}
|
||||
} else {
|
||||
Ok(NullableOpt(None))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
use std::any::type_name;
|
||||
|
||||
use rquickjs::{
|
||||
atom::PredefinedAtom, function::Constructor, runtime::UserDataGuard, Ctx, Function, JsLifetime,
|
||||
Object, Result, Symbol,
|
||||
};
|
||||
|
||||
use crate::{class::CUSTOM_INSPECT_SYMBOL_DESCRIPTION, result::ResultExt};
|
||||
|
||||
#[derive(JsLifetime)]
|
||||
pub struct BasePrimordials<'js> {
|
||||
// Constructors
|
||||
pub constructor_map: Constructor<'js>,
|
||||
pub constructor_set: Constructor<'js>,
|
||||
pub constructor_date: Constructor<'js>,
|
||||
pub constructor_error: Constructor<'js>,
|
||||
pub constructor_type_error: Constructor<'js>,
|
||||
pub constructor_range_error: Constructor<'js>,
|
||||
pub constructor_regexp: Constructor<'js>,
|
||||
pub constructor_uint8array: Constructor<'js>,
|
||||
pub constructor_array_buffer: Constructor<'js>,
|
||||
pub constructor_proxy: Constructor<'js>,
|
||||
pub constructor_object: Constructor<'js>,
|
||||
pub constructor_bool: Constructor<'js>,
|
||||
pub constructor_number: Constructor<'js>,
|
||||
pub constructor_string: Constructor<'js>,
|
||||
|
||||
// Prototypes
|
||||
pub prototype_object: Object<'js>,
|
||||
pub prototype_date: Object<'js>,
|
||||
pub prototype_regexp: Object<'js>,
|
||||
pub prototype_set: Object<'js>,
|
||||
pub prototype_map: Object<'js>,
|
||||
pub prototype_error: Object<'js>,
|
||||
|
||||
// Functions
|
||||
pub function_array_from: Function<'js>,
|
||||
pub function_array_buffer_is_view: Function<'js>,
|
||||
pub function_get_own_property_descriptor: Function<'js>,
|
||||
pub function_parse_int: Function<'js>,
|
||||
pub function_parse_float: Function<'js>,
|
||||
pub function_symbol_for: Function<'js>,
|
||||
|
||||
// Symbols
|
||||
pub symbol_custom_inspect: Symbol<'js>,
|
||||
}
|
||||
|
||||
pub trait Primordial<'js>
|
||||
where
|
||||
Self: Sized + JsLifetime<'js>,
|
||||
{
|
||||
fn get<'a>(ctx: &'a Ctx<'js>) -> Result<UserDataGuard<'a, Self>> {
|
||||
let userdata = ctx.userdata::<Self>().or_throw_msg(
|
||||
ctx,
|
||||
&[
|
||||
"Userdata of ",
|
||||
type_name::<Self>(),
|
||||
" not initialized. Call init(&ctx) on this type.",
|
||||
]
|
||||
.concat(),
|
||||
)?;
|
||||
|
||||
Ok(userdata)
|
||||
}
|
||||
|
||||
fn init<'a>(ctx: &'a Ctx<'js>) -> Result<()> {
|
||||
if ctx.userdata::<Self>().is_none() {
|
||||
let primoridals = Self::new(ctx)?;
|
||||
let _ = ctx.store_userdata(primoridals);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn new(ctx: &Ctx<'js>) -> Result<Self>;
|
||||
}
|
||||
|
||||
impl<'js> Primordial<'js> for BasePrimordials<'js> {
|
||||
fn new(ctx: &Ctx<'js>) -> Result<Self> {
|
||||
let globals = ctx.globals();
|
||||
|
||||
let constructor_object: Constructor = globals.get(PredefinedAtom::Object)?;
|
||||
let prototype_object: Object = constructor_object.get(PredefinedAtom::Prototype)?;
|
||||
|
||||
let constructor_proxy: Constructor = globals.get(PredefinedAtom::Proxy)?;
|
||||
|
||||
let function_get_own_property_descriptor: Function =
|
||||
constructor_object.get(PredefinedAtom::GetOwnPropertyDescriptor)?;
|
||||
|
||||
let constructor_date: Constructor = globals.get(PredefinedAtom::Date)?;
|
||||
let prototype_date: Object = constructor_date.get(PredefinedAtom::Prototype)?;
|
||||
|
||||
let constructor_map: Constructor = globals.get(PredefinedAtom::Map)?;
|
||||
let prototype_map: Object = constructor_map.get(PredefinedAtom::Prototype)?;
|
||||
|
||||
let constructor_set: Constructor = globals.get(PredefinedAtom::Set)?;
|
||||
let prototype_set: Object = constructor_set.get(PredefinedAtom::Prototype)?;
|
||||
|
||||
let constructor_regexp: Constructor = globals.get(PredefinedAtom::RegExp)?;
|
||||
let prototype_regexp: Object = constructor_regexp.get(PredefinedAtom::Prototype)?;
|
||||
|
||||
let constructor_uint8array: Constructor = globals.get(PredefinedAtom::Uint8Array)?;
|
||||
let constructor_arraybuffer: Constructor = globals.get(PredefinedAtom::ArrayBuffer)?;
|
||||
|
||||
let constructor_error: Constructor = globals.get(PredefinedAtom::Error)?;
|
||||
let constructor_type_error: Constructor = ctx.globals().get(PredefinedAtom::TypeError)?;
|
||||
let constructor_range_error: Constructor = ctx.globals().get(PredefinedAtom::RangeError)?;
|
||||
let prototype_error: Object = constructor_error.get(PredefinedAtom::Prototype)?;
|
||||
|
||||
let constructor_array: Object = globals.get(PredefinedAtom::Array)?;
|
||||
let function_array_from: Function = constructor_array.get(PredefinedAtom::From)?;
|
||||
|
||||
let constructor_array_buffer: Object = globals.get(PredefinedAtom::ArrayBuffer)?;
|
||||
let function_array_buffer_is_view: Function = constructor_array_buffer.get("isView")?;
|
||||
|
||||
let constructor_bool: Constructor = globals.get(PredefinedAtom::Boolean)?;
|
||||
|
||||
let constructor_number: Constructor = globals.get(PredefinedAtom::Number)?;
|
||||
let function_parse_float: Function = constructor_number.get("parseFloat")?;
|
||||
let function_parse_int: Function = constructor_number.get("parseInt")?;
|
||||
|
||||
let constructor_string: Constructor = globals.get(PredefinedAtom::String)?;
|
||||
|
||||
let constructor_symbol: Constructor = globals.get(PredefinedAtom::Symbol)?;
|
||||
let function_symbol_for: Function = constructor_symbol.get(PredefinedAtom::For)?;
|
||||
|
||||
let symbol_custom_inspect: Symbol<'js> =
|
||||
function_symbol_for.call((CUSTOM_INSPECT_SYMBOL_DESCRIPTION,))?;
|
||||
|
||||
Ok(Self {
|
||||
constructor_map,
|
||||
constructor_set,
|
||||
constructor_date,
|
||||
constructor_proxy,
|
||||
constructor_error,
|
||||
constructor_type_error,
|
||||
constructor_range_error,
|
||||
constructor_regexp,
|
||||
constructor_uint8array,
|
||||
constructor_array_buffer: constructor_arraybuffer,
|
||||
constructor_object,
|
||||
constructor_bool,
|
||||
constructor_number,
|
||||
constructor_string,
|
||||
prototype_object,
|
||||
prototype_date,
|
||||
prototype_regexp,
|
||||
prototype_set,
|
||||
prototype_map,
|
||||
prototype_error,
|
||||
function_array_from,
|
||||
function_array_buffer_is_view,
|
||||
function_get_own_property_descriptor,
|
||||
function_parse_float,
|
||||
function_parse_int,
|
||||
function_symbol_for,
|
||||
symbol_custom_inspect,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#[derive(PartialEq)]
|
||||
pub enum ProviderType {
|
||||
None,
|
||||
Resource(String), // Custom asynchronous resource
|
||||
// Userland provider types
|
||||
Immediate, // [Immediate] Processing by setImmediate()
|
||||
Interval, // [Interval] Timer by setInterval()
|
||||
MessagePort, // [MessagePort] Port for worker_threads
|
||||
Microtask, // [Microtask] Processing by queueMicrotask()
|
||||
TickObject, // [TickObject] Processing by process.nextTick()
|
||||
Timeout, // [Timeout] Timer by setTimeout()
|
||||
// Internal provider types
|
||||
FsReqCallback, // [FSREQCALLBACK] Callback for file system operations
|
||||
GetAddrInfoReqWrap, // [GETADDRINFOREQWRAP] When resolving DNS (dns.lookup(), etc.)
|
||||
GetNameInfoReqWrap, // [GETNAMEINFOREQWRAP] DNS reverse lookup
|
||||
PipeWrap, // [PIPEWRAP] Pipe connection
|
||||
StatWatcher, // [STATWACHER] File monitoring such as fs.watch()
|
||||
TcpWrap, // [TCPWRAP] TCP socket wrap (net.Socket, etc.)
|
||||
TimerWrap, // [TIMERWRAP] Internal timer wrap (low level)
|
||||
TlsWrap, // [TLSWRAP] TLS socket (HTTPS, etc.)
|
||||
UdpWrap, // [UDPWRAP] UDP socket wrap (dgram module)
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#![allow(clippy::uninlined_format_args)]
|
||||
|
||||
use std::{fmt::Write, result::Result as StdResult};
|
||||
|
||||
use rquickjs::{Ctx, Exception, Result};
|
||||
|
||||
pub trait ResultExt<T> {
|
||||
fn or_throw_msg(self, ctx: &Ctx, msg: &str) -> Result<T>;
|
||||
fn or_throw_range(self, ctx: &Ctx, msg: &str) -> Result<T>;
|
||||
fn or_throw_type(self, ctx: &Ctx, msg: &str) -> Result<T>;
|
||||
fn or_throw(self, ctx: &Ctx) -> Result<T>;
|
||||
}
|
||||
|
||||
pub trait OptionExt<T> {
|
||||
fn and_then_ok<U, E, F>(self, f: F) -> StdResult<Option<U>, E>
|
||||
where
|
||||
F: FnOnce(T) -> StdResult<Option<U>, E>;
|
||||
|
||||
fn unwrap_or_else_ok<E, F>(self, f: F) -> StdResult<T, E>
|
||||
where
|
||||
F: FnOnce() -> StdResult<T, E>;
|
||||
}
|
||||
|
||||
impl<T, E: std::fmt::Display> ResultExt<T> for StdResult<T, E> {
|
||||
fn or_throw_msg(self, ctx: &Ctx, msg: &str) -> Result<T> {
|
||||
self.map_err(|e| {
|
||||
let mut message = String::with_capacity(100);
|
||||
message.push_str(msg);
|
||||
message.push_str(". ");
|
||||
write!(message, "{}", e).unwrap();
|
||||
Exception::throw_message(ctx, &message)
|
||||
})
|
||||
}
|
||||
|
||||
fn or_throw_range(self, ctx: &Ctx, msg: &str) -> Result<T> {
|
||||
self.map_err(|e| {
|
||||
let mut message = String::with_capacity(100);
|
||||
if !message.is_empty() {
|
||||
message.push_str(msg);
|
||||
message.push_str(". ");
|
||||
}
|
||||
write!(message, "{}", e).unwrap();
|
||||
Exception::throw_range(ctx, &message)
|
||||
})
|
||||
}
|
||||
|
||||
fn or_throw_type(self, ctx: &Ctx, msg: &str) -> Result<T> {
|
||||
self.map_err(|e| {
|
||||
let mut message = String::with_capacity(100);
|
||||
if !msg.is_empty() {
|
||||
message.push_str(msg);
|
||||
message.push_str(". ");
|
||||
}
|
||||
write!(message, "{}", e).unwrap();
|
||||
Exception::throw_type(ctx, &message)
|
||||
})
|
||||
}
|
||||
|
||||
fn or_throw(self, ctx: &Ctx) -> Result<T> {
|
||||
self.map_err(|err| Exception::throw_message(ctx, &err.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ResultExt<T> for Option<T> {
|
||||
fn or_throw_msg(self, ctx: &Ctx, msg: &str) -> Result<T> {
|
||||
self.ok_or_else(|| Exception::throw_message(ctx, msg))
|
||||
}
|
||||
|
||||
fn or_throw_range(self, ctx: &Ctx, msg: &str) -> Result<T> {
|
||||
self.ok_or_else(|| Exception::throw_range(ctx, msg))
|
||||
}
|
||||
|
||||
fn or_throw_type(self, ctx: &Ctx, msg: &str) -> Result<T> {
|
||||
self.ok_or_else(|| Exception::throw_type(ctx, msg))
|
||||
}
|
||||
|
||||
fn or_throw(self, ctx: &Ctx) -> Result<T> {
|
||||
self.ok_or_else(|| Exception::throw_message(ctx, "Value is not present"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> OptionExt<T> for Option<T> {
|
||||
fn and_then_ok<U, E, F>(self, f: F) -> StdResult<Option<U>, E>
|
||||
where
|
||||
F: FnOnce(T) -> StdResult<Option<U>, E>,
|
||||
{
|
||||
match self {
|
||||
Some(v) => f(v),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
fn unwrap_or_else_ok<E, F>(self, f: F) -> StdResult<T, E>
|
||||
where
|
||||
F: FnOnce() -> StdResult<T, E>,
|
||||
{
|
||||
match self {
|
||||
Some(v) => Ok(v),
|
||||
None => f(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,337 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct ReuseList<T> {
|
||||
items: Vec<Option<T>>,
|
||||
slots: Vec<usize>,
|
||||
last_slot_idx: usize,
|
||||
len: usize,
|
||||
slot_size: usize,
|
||||
}
|
||||
|
||||
impl<T: Debug> Debug for ReuseList<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("ReuseList")
|
||||
.field("items", &self.items)
|
||||
.field("slots", &self.slots)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ReuseList<T> {
|
||||
pub fn new() -> Self {
|
||||
Self::with_capacity(0)
|
||||
}
|
||||
|
||||
//is empty
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len == 0
|
||||
}
|
||||
|
||||
//create a with capacity
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
items: Vec::with_capacity(capacity),
|
||||
slots: Vec::with_capacity(capacity >> 2),
|
||||
last_slot_idx: 0,
|
||||
len: 0,
|
||||
slot_size: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn append(&mut self, item: T) -> usize {
|
||||
if self.slot_size > 0 {
|
||||
//reuse empty slot if valid
|
||||
let slot = self.slots[self.last_slot_idx - 1];
|
||||
if slot > 0 {
|
||||
self.items[slot - 1] = Some(item);
|
||||
self.slots[self.last_slot_idx - 1] = 0;
|
||||
if self.last_slot_idx > 1 {
|
||||
self.last_slot_idx -= 1;
|
||||
}
|
||||
|
||||
self.len += 1;
|
||||
return slot - 1;
|
||||
}
|
||||
}
|
||||
//no valid empty slots, append to end
|
||||
self.items.push(Some(item));
|
||||
self.len += 1;
|
||||
self.items.len() - 1
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, index: usize) -> Option<T> {
|
||||
if index >= self.items.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let item = self.items[index].take();
|
||||
if item.is_some() {
|
||||
if self.slot_size > 0 && self.slots[self.last_slot_idx - 1] == 0 {
|
||||
self.slots[self.last_slot_idx - 1] = index + 1;
|
||||
} else {
|
||||
self.slots.push(index + 1);
|
||||
self.last_slot_idx += 1;
|
||||
self.slot_size += 1;
|
||||
}
|
||||
self.len -= 1;
|
||||
}
|
||||
item
|
||||
}
|
||||
|
||||
pub fn get(&self, index: usize) -> Option<&T> {
|
||||
if index >= self.items.len() {
|
||||
None
|
||||
} else {
|
||||
self.items[index].as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, index: usize) -> Option<&mut T> {
|
||||
if index >= self.items.len() {
|
||||
None
|
||||
} else {
|
||||
self.items[index].as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn capacity(&self) -> usize {
|
||||
self.items.capacity()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &T> {
|
||||
self.items.iter().filter_map(|x| x.as_ref())
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
|
||||
self.items.iter_mut().filter_map(|x| x.as_mut())
|
||||
}
|
||||
|
||||
//implement clear
|
||||
pub fn clear(&mut self) {
|
||||
self.items.clear();
|
||||
self.slots.clear();
|
||||
self.last_slot_idx = 0;
|
||||
self.len = 0;
|
||||
self.slot_size = 0;
|
||||
}
|
||||
|
||||
pub fn optimize(&mut self) {
|
||||
let mut new_items = Vec::with_capacity(self.len);
|
||||
|
||||
for item in self.items.iter_mut() {
|
||||
let a = item.take();
|
||||
if a.is_some() {
|
||||
new_items.push(a);
|
||||
}
|
||||
}
|
||||
self.items = new_items;
|
||||
self.slots.clear();
|
||||
self.last_slot_idx = 0;
|
||||
self.slot_size = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_new() {
|
||||
let list: ReuseList<i32> = ReuseList::new();
|
||||
assert_eq!(list.len(), 0);
|
||||
assert_eq!(list.capacity(), 0);
|
||||
assert_eq!(list.items.len(), 0);
|
||||
assert_eq!(list.slots.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_capacity() {
|
||||
let list: ReuseList<i32> = ReuseList::with_capacity(10);
|
||||
assert_eq!(list.len(), 0);
|
||||
assert_eq!(list.capacity(), 10);
|
||||
assert_eq!(list.items.len(), 0);
|
||||
assert_eq!(list.slots.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_append() {
|
||||
let mut list = ReuseList::new();
|
||||
assert_eq!(list.append(1), 0);
|
||||
assert_eq!(list.append(2), 1);
|
||||
assert_eq!(list.append(3), 2);
|
||||
assert_eq!(list.len(), 3);
|
||||
|
||||
let items: Vec<i32> = list.iter().cloned().collect();
|
||||
assert_eq!(items, vec![1, 2, 3]);
|
||||
assert_eq!(list.items, vec![Some(1), Some(2), Some(3)]);
|
||||
assert_eq!(list.slots, vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove() {
|
||||
let mut list = ReuseList::new();
|
||||
list.append(1);
|
||||
list.append(2);
|
||||
list.append(3);
|
||||
|
||||
assert_eq!(list.remove(1), Some(2));
|
||||
assert_eq!(list.len(), 2);
|
||||
|
||||
let items: Vec<i32> = list.iter().cloned().collect();
|
||||
assert_eq!(items, vec![1, 3]);
|
||||
assert_eq!(list.items, vec![Some(1), None, Some(3)]);
|
||||
assert_eq!(list.slots, vec![2]);
|
||||
|
||||
assert_eq!(list.remove(5), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reuse_slots() {
|
||||
let mut list = ReuseList::new();
|
||||
list.append(1);
|
||||
list.append(2);
|
||||
list.append(3);
|
||||
|
||||
list.remove(1); // Remove 2
|
||||
assert_eq!(list.append(4), 1); // Should reuse index 1
|
||||
|
||||
let items: Vec<i32> = list.iter().cloned().collect();
|
||||
assert_eq!(items, vec![1, 4, 3]);
|
||||
assert_eq!(list.items, vec![Some(1), Some(4), Some(3)]);
|
||||
assert_eq!(list.slots, vec![0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get() {
|
||||
let mut list = ReuseList::new();
|
||||
list.append(1);
|
||||
list.append(2);
|
||||
|
||||
assert_eq!(list.get(0), Some(&1));
|
||||
assert_eq!(list.get(1), Some(&2));
|
||||
assert_eq!(list.get(2), None);
|
||||
assert_eq!(list.items, vec![Some(1), Some(2)]);
|
||||
assert_eq!(list.slots, vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_mut() {
|
||||
let mut list = ReuseList::new();
|
||||
list.append(1);
|
||||
list.append(2);
|
||||
|
||||
if let Some(value) = list.get_mut(0) {
|
||||
*value = 10;
|
||||
}
|
||||
|
||||
assert_eq!(list.get(0), Some(&10));
|
||||
assert_eq!(list.items, vec![Some(10), Some(2)]);
|
||||
assert_eq!(list.slots, vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iter() {
|
||||
let mut list = ReuseList::new();
|
||||
list.append(1);
|
||||
list.append(2);
|
||||
list.append(3);
|
||||
list.remove(1);
|
||||
|
||||
let items: Vec<i32> = list.iter().cloned().collect();
|
||||
assert_eq!(items, vec![1, 3]);
|
||||
assert_eq!(list.items, vec![Some(1), None, Some(3)]);
|
||||
assert_eq!(list.slots, vec![2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_iter_mut() {
|
||||
let mut list = ReuseList::new();
|
||||
list.append(1);
|
||||
list.append(2);
|
||||
list.append(3);
|
||||
|
||||
for item in list.iter_mut() {
|
||||
*item *= 2;
|
||||
}
|
||||
|
||||
let items: Vec<i32> = list.iter().cloned().collect();
|
||||
assert_eq!(items, vec![2, 4, 6]);
|
||||
assert_eq!(list.items, vec![Some(2), Some(4), Some(6)]);
|
||||
assert_eq!(list.slots, vec![]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_removes() {
|
||||
let mut list = ReuseList::new();
|
||||
for i in 0..5 {
|
||||
list.append(i);
|
||||
}
|
||||
|
||||
list.remove(1);
|
||||
list.remove(3);
|
||||
|
||||
let items: Vec<i32> = list.iter().cloned().collect();
|
||||
assert_eq!(items, vec![0, 2, 4]);
|
||||
assert_eq!(list.items, vec![Some(0), None, Some(2), None, Some(4)]);
|
||||
assert_eq!(list.slots, vec![2, 4]);
|
||||
|
||||
// Test reuse of both slots
|
||||
list.append(10);
|
||||
list.append(11);
|
||||
|
||||
let items: Vec<i32> = list.iter().cloned().collect();
|
||||
assert_eq!(items, vec![0, 11, 2, 10, 4]);
|
||||
assert_eq!(
|
||||
list.items,
|
||||
vec![Some(0), Some(11), Some(2), Some(10), Some(4)]
|
||||
);
|
||||
assert_eq!(list.slots, vec![0, 0]);
|
||||
|
||||
list.remove(0);
|
||||
|
||||
let items: Vec<i32> = list.iter().cloned().collect();
|
||||
assert_eq!(items, vec![11, 2, 10, 4]);
|
||||
assert_eq!(list.items, vec![None, Some(11), Some(2), Some(10), Some(4)]);
|
||||
assert_eq!(list.slots, vec![1, 0]);
|
||||
|
||||
list.append(20);
|
||||
|
||||
let items: Vec<i32> = list.iter().cloned().collect();
|
||||
assert_eq!(items, vec![20, 11, 2, 10, 4]);
|
||||
assert_eq!(
|
||||
list.items,
|
||||
vec![Some(20), Some(11), Some(2), Some(10), Some(4)]
|
||||
);
|
||||
assert_eq!(list.slots, vec![0, 0]);
|
||||
|
||||
//remove all items
|
||||
list.clear();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_optimize() {
|
||||
let mut list = ReuseList::new();
|
||||
list.append(1);
|
||||
list.append(2);
|
||||
list.append(3);
|
||||
list.remove(1);
|
||||
|
||||
assert_eq!(list.items, vec![Some(1), None, Some(3)]);
|
||||
assert_eq!(list.slots, vec![2]);
|
||||
|
||||
list.optimize();
|
||||
|
||||
assert_eq!(list.items, vec![Some(1), Some(3)]);
|
||||
assert_eq!(list.slots, vec![]);
|
||||
assert_eq!(list.last_slot_idx, 0);
|
||||
assert_eq!(list.slot_size, 0);
|
||||
|
||||
let items: Vec<i32> = list.iter().cloned().collect();
|
||||
assert_eq!(items, vec![1, 3]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
use rquickjs::{prelude::Opt, Ctx, Exception, Result, Value};
|
||||
|
||||
use crate::result::ResultExt;
|
||||
use std::io;
|
||||
|
||||
#[cfg(unix)]
|
||||
macro_rules! generate_signal_from_str_fn {
|
||||
($($signal:path),*) => {
|
||||
pub fn signal_from_str(signal: &str) -> Option<i32> {
|
||||
let signal = ["libc::", signal].concat();
|
||||
match signal.as_str() {
|
||||
$(stringify!($signal) => Some($signal),)*
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn signal_str_from_i32(signal: i32) -> Option<&'static str> {
|
||||
$(if signal == $signal {
|
||||
return Some(&stringify!($signal)[6..]);
|
||||
})*
|
||||
None
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
generate_signal_from_str_fn!(
|
||||
libc::SIGHUP,
|
||||
libc::SIGINT,
|
||||
libc::SIGQUIT,
|
||||
libc::SIGILL,
|
||||
libc::SIGABRT,
|
||||
libc::SIGFPE,
|
||||
libc::SIGKILL,
|
||||
libc::SIGSEGV,
|
||||
libc::SIGPIPE,
|
||||
libc::SIGALRM,
|
||||
libc::SIGTERM
|
||||
);
|
||||
|
||||
#[cfg(not(unix))]
|
||||
static WINDOWS_SIGTERM: i32 = -1;
|
||||
|
||||
pub fn parse_signal(signal: Option<Value<'_>>) -> Result<i32> {
|
||||
let Some(val) = signal else {
|
||||
#[cfg(unix)]
|
||||
return Ok(libc::SIGTERM);
|
||||
#[cfg(not(unix))]
|
||||
return Ok(WINDOWS_SIGTERM);
|
||||
};
|
||||
|
||||
if let Some(num) = val.as_number() {
|
||||
let sig = num as i32;
|
||||
#[cfg(unix)]
|
||||
return Ok(sig);
|
||||
// On Windows: 0 checks existence, anything else kills
|
||||
#[cfg(not(unix))]
|
||||
return Ok(if sig == 0 { 0 } else { WINDOWS_SIGTERM });
|
||||
}
|
||||
|
||||
if let Some(str_val) = val.as_string() {
|
||||
let s = str_val.to_string()?;
|
||||
|
||||
#[cfg(unix)]
|
||||
let mapped_sig = signal_from_str(&s);
|
||||
|
||||
#[cfg(not(unix))]
|
||||
let mapped_sig = match s.as_str() {
|
||||
"SIGINT" | "SIGTERM" | "SIGKILL" | "SIGQUIT" | "SIGHUP" | "SIGUSR1" => {
|
||||
Some(WINDOWS_SIGTERM)
|
||||
},
|
||||
_ => None,
|
||||
};
|
||||
|
||||
return match mapped_sig {
|
||||
Some(sig) => Ok(sig),
|
||||
None => Err(Exception::throw_type(
|
||||
val.ctx(),
|
||||
&format!("Unknown signal: {}", s),
|
||||
)),
|
||||
};
|
||||
}
|
||||
|
||||
Err(Exception::throw_type(val.ctx(), "Invalid signal type"))
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub fn kill_process_raw(pid: u32, signal: i32) -> io::Result<()> {
|
||||
// libc::kill returns 0 on success, -1 on error
|
||||
// SAFETY: kill is a safe system call as long as the signal value is valid, which is ensured by parse_signal
|
||||
if unsafe { libc::kill(pid as i32, signal) } == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(io::Error::last_os_error())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
pub fn kill_process_raw(pid: u32, signal: i32) -> io::Result<()> {
|
||||
use windows_sys::Win32::Foundation::CloseHandle;
|
||||
use windows_sys::Win32::System::Threading::{OpenProcess, TerminateProcess, PROCESS_TERMINATE};
|
||||
|
||||
// SAFETY: OpenProcess is safe to call with valid parameters, and PROCESS_TERMINATE is a valid access right
|
||||
let handle = unsafe { OpenProcess(PROCESS_TERMINATE, 0, pid) };
|
||||
if handle == std::ptr::null_mut() {
|
||||
return Err(io::Error::last_os_error());
|
||||
}
|
||||
|
||||
let result = if signal == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
// SAFETY: TerminateProcess is safe to call with a valid process handle obtained from OpenProcess
|
||||
if unsafe { TerminateProcess(handle, 1) } != 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(io::Error::last_os_error())
|
||||
}
|
||||
};
|
||||
|
||||
// SAFETY: CloseHandle is safe to call with a valid handle obtained from OpenProcess
|
||||
unsafe { CloseHandle(handle) };
|
||||
result
|
||||
}
|
||||
|
||||
pub fn kill(ctx: &Ctx<'_>, pid: u32, signal: Opt<Value<'_>>) -> Result<bool> {
|
||||
let signal = parse_signal(signal.0)?;
|
||||
|
||||
kill_process_raw(pid, signal)
|
||||
.map(|_| true)
|
||||
.or_else(|e| {
|
||||
// Handle "Process Not Found" / "Existence Check" logic
|
||||
// If signal is 0 (check existence) and we hit a specific error, return Ok(false).
|
||||
|
||||
#[cfg(unix)]
|
||||
let is_not_found = e.raw_os_error() == Some(libc::ESRCH); // Error 3
|
||||
|
||||
#[cfg(windows)]
|
||||
let is_not_found = true; // On Windows, any OpenProcess failure during check implies "not found" (or not accessible)
|
||||
|
||||
if signal == 0 && is_not_found {
|
||||
Ok(false)
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
})
|
||||
.or_throw(ctx)
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
use rquickjs::{Coerced, Result, Value};
|
||||
|
||||
#[inline]
|
||||
pub fn get_string(value: &Value<'_>) -> Result<Option<String>> {
|
||||
if let Some(val) = value.as_string() {
|
||||
let string = val.to_string()?;
|
||||
return Ok(Some(string));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub fn get_coerced_string(value: &Value<'_>) -> Option<String> {
|
||||
if let Ok(val) = value.get::<Coerced<String>>() {
|
||||
return Some(val.0);
|
||||
};
|
||||
None
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
#[cfg(target_os = "macos")]
|
||||
pub const PLATFORM: &str = "darwin";
|
||||
#[cfg(target_os = "windows")]
|
||||
pub const PLATFORM: &str = "win32";
|
||||
#[cfg(not(any(target_os = "macos", target_os = "windows")))]
|
||||
pub const PLATFORM: &str = std::env::consts::OS;
|
||||
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
|
||||
pub const ARCH: &str = "x64";
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub const ARCH: &str = "arm64";
|
||||
#[cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")))]
|
||||
pub const ARCH: &str = std::env::consts::ARCH;
|
||||
@@ -0,0 +1,47 @@
|
||||
use std::{
|
||||
sync::atomic::{AtomicU64, Ordering},
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
static TIME_ORIGIN: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
/// Get the current time in nanoseconds.
|
||||
///
|
||||
/// # Safety
|
||||
/// - Good until the year 2554
|
||||
/// - Always use a checked substraction since this can return 0
|
||||
pub fn now_nanos() -> u64 {
|
||||
SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_nanos() as u64
|
||||
}
|
||||
|
||||
/// Get the current time in millis.
|
||||
///
|
||||
/// # Safety
|
||||
/// - Good until the year 2554
|
||||
/// - Always use a checked substraction since this can return 0
|
||||
pub fn now_millis() -> i64 {
|
||||
SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap_or_default()
|
||||
.as_millis() as i64
|
||||
}
|
||||
|
||||
/// Get the origin time in nanoseconds.
|
||||
///
|
||||
/// # Safety
|
||||
/// - Good until the year 2554
|
||||
/// - Always use a checked substraction since this can return 0
|
||||
pub fn origin_nanos() -> u64 {
|
||||
TIME_ORIGIN.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
// For accuracy reasons, this function should be executed when the vm is initialized
|
||||
pub fn init() {
|
||||
if TIME_ORIGIN.load(Ordering::Relaxed) == 0 {
|
||||
let time_origin = now_nanos();
|
||||
TIME_ORIGIN.store(time_origin, Ordering::Relaxed)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user