feat: init commit

This commit is contained in:
2020-11-28 13:27:17 +08:00
parent c13575871c
commit 3a6b5b2485
54 changed files with 91423 additions and 22 deletions

107
src/value/bigint.rs Normal file
View 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
View 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);
}
}