feat: init commit
This commit is contained in:
107
src/value/bigint.rs
Normal file
107
src/value/bigint.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
use num_traits::cast::ToPrimitive;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum BigIntOrI64 {
|
||||
Int(i64),
|
||||
BigInt(num_bigint::BigInt),
|
||||
}
|
||||
|
||||
impl PartialEq for BigIntOrI64 {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
use BigIntOrI64::*;
|
||||
match (&self, &other) {
|
||||
(Int(i), Int(j)) => i == j,
|
||||
(Int(i), BigInt(b)) | (BigInt(b), Int(i)) => b == &num_bigint::BigInt::from(*i),
|
||||
(BigInt(a), BigInt(b)) => a == b,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for BigIntOrI64 {}
|
||||
|
||||
/// A value holding JavaScript
|
||||
/// [BigInt](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt) type
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct BigInt {
|
||||
pub(crate) inner: BigIntOrI64,
|
||||
}
|
||||
|
||||
impl BigInt {
|
||||
/// Return `Some` if value fits into `i64` and `None` otherwise
|
||||
pub fn as_i64(&self) -> Option<i64> {
|
||||
match &self.inner {
|
||||
BigIntOrI64::Int(int) => Some(*int),
|
||||
BigIntOrI64::BigInt(bigint) => bigint.to_i64(),
|
||||
}
|
||||
}
|
||||
/// Convert value into `num_bigint::BigInt`
|
||||
pub fn into_bigint(self) -> num_bigint::BigInt {
|
||||
match self.inner {
|
||||
BigIntOrI64::Int(int) => int.into(),
|
||||
BigIntOrI64::BigInt(bigint) => bigint,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for BigInt {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
match self.inner {
|
||||
BigIntOrI64::Int(i) => write!(f, "{}", i),
|
||||
BigIntOrI64::BigInt(ref i) => write!(f, "{}", i),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for BigInt {
|
||||
fn from(int: i64) -> Self {
|
||||
BigInt {
|
||||
inner: BigIntOrI64::Int(int),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<num_bigint::BigInt> for BigInt {
|
||||
fn from(bigint: num_bigint::BigInt) -> Self {
|
||||
BigInt {
|
||||
inner: BigIntOrI64::BigInt(bigint),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[allow(unused_imports)]
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_bigint_as_i64() {
|
||||
let value = BigInt {
|
||||
inner: BigIntOrI64::Int(1234i64),
|
||||
};
|
||||
assert_eq!(value.as_i64(), Some(1234i64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bigint_as_i64_overflow() {
|
||||
let value = BigInt {
|
||||
inner: BigIntOrI64::BigInt(num_bigint::BigInt::from(std::i128::MAX)),
|
||||
};
|
||||
assert_eq!(value.as_i64(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bigint_into_bigint() {
|
||||
for i in vec![
|
||||
0 as i128,
|
||||
std::i64::MAX as i128,
|
||||
std::i64::MIN as i128,
|
||||
std::i128::MAX,
|
||||
std::i128::MIN,
|
||||
] {
|
||||
let value = BigInt {
|
||||
inner: BigIntOrI64::BigInt(num_bigint::BigInt::from(i)),
|
||||
};
|
||||
assert_eq!(value.into_bigint(), num_bigint::BigInt::from(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
320
src/value/mod.rs
Normal file
320
src/value/mod.rs
Normal file
@@ -0,0 +1,320 @@
|
||||
#[cfg(feature = "bigint")]
|
||||
pub(crate) mod bigint;
|
||||
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::{collections::HashMap, error, fmt};
|
||||
|
||||
#[cfg(feature = "bigint")]
|
||||
pub use bigint::BigInt;
|
||||
|
||||
/// A value that can be (de)serialized to/from the quickjs runtime.
|
||||
#[derive(PartialEq, Clone, Debug)]
|
||||
#[allow(missing_docs)]
|
||||
pub enum JsValue {
|
||||
Null,
|
||||
Bool(bool),
|
||||
Int(i32),
|
||||
Float(f64),
|
||||
String(String),
|
||||
Array(Vec<JsValue>),
|
||||
Object(HashMap<String, JsValue>),
|
||||
/// chrono::Datetime<Utc> / JS Date integration.
|
||||
/// Only available with the optional `chrono` feature.
|
||||
#[cfg(feature = "chrono")]
|
||||
Date(chrono::DateTime<chrono::Utc>),
|
||||
/// num_bigint::BigInt / JS BigInt integration
|
||||
/// Only available with the optional `bigint` feature
|
||||
#[cfg(feature = "bigint")]
|
||||
BigInt(crate::BigInt),
|
||||
#[doc(hidden)]
|
||||
__NonExhaustive,
|
||||
}
|
||||
|
||||
impl JsValue {
|
||||
/// Cast value to a str.
|
||||
///
|
||||
/// Returns `Some(&str)` if value is a `JsValue::String`, None otherwise.
|
||||
pub fn as_str(&self) -> Option<&str> {
|
||||
match self {
|
||||
JsValue::String(ref s) => Some(s.as_str()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert to `String`.
|
||||
pub fn into_string(self) -> Option<String> {
|
||||
match self {
|
||||
JsValue::String(s) => Some(s),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! value_impl_from {
|
||||
(
|
||||
(
|
||||
$( $t1:ty => $var1:ident, )*
|
||||
)
|
||||
(
|
||||
$( $t2:ty => |$exprname:ident| $expr:expr => $var2:ident, )*
|
||||
)
|
||||
) => {
|
||||
$(
|
||||
impl From<$t1> for JsValue {
|
||||
fn from(value: $t1) -> Self {
|
||||
JsValue::$var1(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<JsValue> for $t1 {
|
||||
type Error = ValueError;
|
||||
|
||||
fn try_from(value: JsValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
JsValue::$var1(inner) => Ok(inner),
|
||||
_ => Err(ValueError::UnexpectedType)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
)*
|
||||
$(
|
||||
impl From<$t2> for JsValue {
|
||||
fn from(value: $t2) -> Self {
|
||||
let $exprname = value;
|
||||
let inner = $expr;
|
||||
JsValue::$var2(inner)
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
value_impl_from! {
|
||||
(
|
||||
bool => Bool,
|
||||
i32 => Int,
|
||||
f64 => Float,
|
||||
String => String,
|
||||
)
|
||||
(
|
||||
i8 => |x| i32::from(x) => Int,
|
||||
i16 => |x| i32::from(x) => Int,
|
||||
u8 => |x| i32::from(x) => Int,
|
||||
u16 => |x| i32::from(x) => Int,
|
||||
u32 => |x| f64::from(x) => Float,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "bigint")]
|
||||
value_impl_from! {
|
||||
()
|
||||
(
|
||||
i64 => |x| x.into() => BigInt,
|
||||
u64 => |x| num_bigint::BigInt::from(x).into() => BigInt,
|
||||
i128 => |x| num_bigint::BigInt::from(x).into() => BigInt,
|
||||
u128 => |x| num_bigint::BigInt::from(x).into() => BigInt,
|
||||
num_bigint::BigInt => |x| x.into() => BigInt,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "bigint")]
|
||||
impl std::convert::TryFrom<JsValue> for i64 {
|
||||
type Error = ValueError;
|
||||
|
||||
fn try_from(value: JsValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
JsValue::Int(int) => Ok(int as i64),
|
||||
JsValue::BigInt(bigint) => bigint.as_i64().ok_or(ValueError::UnexpectedType),
|
||||
_ => Err(ValueError::UnexpectedType),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bigint")]
|
||||
macro_rules! value_bigint_impl_tryfrom {
|
||||
(
|
||||
($($t:ty => $to_type:ident, )*)
|
||||
) => {
|
||||
$(
|
||||
impl std::convert::TryFrom<JsValue> for $t {
|
||||
type Error = ValueError;
|
||||
|
||||
fn try_from(value: JsValue) -> Result<Self, Self::Error> {
|
||||
use num_traits::ToPrimitive;
|
||||
|
||||
match value {
|
||||
JsValue::Int(int) => Ok(int as $t),
|
||||
JsValue::BigInt(bigint) => bigint
|
||||
.into_bigint()
|
||||
.$to_type()
|
||||
.ok_or(ValueError::UnexpectedType),
|
||||
_ => Err(ValueError::UnexpectedType),
|
||||
}
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bigint")]
|
||||
value_bigint_impl_tryfrom! {
|
||||
(
|
||||
u64 => to_u64,
|
||||
i128 => to_i128,
|
||||
u128 => to_u128,
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(feature = "bigint")]
|
||||
impl std::convert::TryFrom<JsValue> for num_bigint::BigInt {
|
||||
type Error = ValueError;
|
||||
|
||||
fn try_from(value: JsValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
JsValue::Int(int) => Ok(num_bigint::BigInt::from(int)),
|
||||
JsValue::BigInt(bigint) => Ok(bigint.into_bigint()),
|
||||
_ => Err(ValueError::UnexpectedType),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Vec<T>> for JsValue
|
||||
where
|
||||
T: Into<JsValue>,
|
||||
{
|
||||
fn from(values: Vec<T>) -> Self {
|
||||
let items = values.into_iter().map(|x| x.into()).collect();
|
||||
JsValue::Array(items)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for JsValue {
|
||||
fn from(val: &'a str) -> Self {
|
||||
JsValue::String(val.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Option<T>> for JsValue
|
||||
where
|
||||
T: Into<JsValue>,
|
||||
{
|
||||
fn from(opt: Option<T>) -> Self {
|
||||
if let Some(value) = opt {
|
||||
value.into()
|
||||
} else {
|
||||
JsValue::Null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V> From<HashMap<K, V>> for JsValue
|
||||
where
|
||||
K: Into<String>,
|
||||
V: Into<JsValue>,
|
||||
{
|
||||
fn from(map: HashMap<K, V>) -> Self {
|
||||
let new_map = map.into_iter().map(|(k, v)| (k.into(), v.into())).collect();
|
||||
JsValue::Object(new_map)
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> TryFrom<JsValue> for HashMap<String, V>
|
||||
where
|
||||
V: TryFrom<JsValue>,
|
||||
{
|
||||
type Error = ValueError;
|
||||
|
||||
fn try_from(value: JsValue) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
JsValue::Object(object) => object
|
||||
.into_iter()
|
||||
.map(|(k, v)| match v.try_into() {
|
||||
Ok(v) => Ok((k, v)),
|
||||
Err(_) => Err(ValueError::UnexpectedType),
|
||||
})
|
||||
.collect(),
|
||||
_ => Err(ValueError::UnexpectedType),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Error during value conversion.
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub enum ValueError {
|
||||
/// Invalid non-utf8 string.
|
||||
InvalidString(std::str::Utf8Error),
|
||||
/// Encountered string with \0 bytes.
|
||||
StringWithZeroBytes(std::ffi::NulError),
|
||||
/// Internal error.
|
||||
Internal(String),
|
||||
/// Received an unexpected type that could not be converted.
|
||||
UnexpectedType,
|
||||
#[doc(hidden)]
|
||||
__NonExhaustive,
|
||||
}
|
||||
|
||||
// TODO: remove this once either the Never type get's stabilized or the compiler
|
||||
// can properly handle Infallible.
|
||||
impl From<std::convert::Infallible> for ValueError {
|
||||
fn from(_: std::convert::Infallible) -> Self {
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ValueError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
use ValueError::*;
|
||||
match self {
|
||||
InvalidString(e) => write!(
|
||||
f,
|
||||
"Value conversion failed - invalid non-utf8 string: {}",
|
||||
e
|
||||
),
|
||||
StringWithZeroBytes(_) => write!(f, "String contains \\0 bytes",),
|
||||
Internal(e) => write!(f, "Value conversion failed - internal error: {}", e),
|
||||
UnexpectedType => write!(f, "Could not convert - received unexpected type"),
|
||||
__NonExhaustive => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for ValueError {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[allow(unused_imports)]
|
||||
use super::*;
|
||||
|
||||
#[cfg(feature = "bigint")]
|
||||
#[test]
|
||||
fn test_bigint_from_i64() {
|
||||
let int = 1234i64;
|
||||
let value = JsValue::from(int);
|
||||
if let JsValue::BigInt(value) = value {
|
||||
assert_eq!(value.as_i64(), Some(int));
|
||||
} else {
|
||||
panic!("Expected JsValue::BigInt");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bigint")]
|
||||
#[test]
|
||||
fn test_bigint_from_bigint() {
|
||||
let bigint = num_bigint::BigInt::from(std::i128::MAX);
|
||||
let value = JsValue::from(bigint.clone());
|
||||
if let JsValue::BigInt(value) = value {
|
||||
assert_eq!(value.into_bigint(), bigint);
|
||||
} else {
|
||||
panic!("Expected JsValue::BigInt");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bigint")]
|
||||
#[test]
|
||||
fn test_bigint_i64_bigint_eq() {
|
||||
let value_i64 = JsValue::BigInt(1234i64.into());
|
||||
let value_bigint = JsValue::BigInt(num_bigint::BigInt::from(1234i64).into());
|
||||
assert_eq!(value_i64, value_bigint);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user