use napi::bindgen_prelude::{BigInt, Buffer, JsValue, Null, Object, ToNapiValue, Unknown};
use napi::{Env, Error, Status};
use vitaminc_aead::{
Encrypt, FixedType, Float, Frame, Int, Logical, MapSink, Physical, SeqSink, Sink,
};
use crate::error::JsFault;
use crate::js::{
define_own_properties, depth_error, eager_capacity, forbidden_key, forbidden_key_error,
new_array, own_property, MAX_DEPTH,
};
use crate::scalar;
use crate::wrappers::{Wrapper, Wrappers};
mod out {
use napi::bindgen_prelude::Unknown;
pub trait Out<'e> {
type Ok;
fn put(self, value: Unknown<'e>) -> Self::Ok;
}
pub struct Root;
impl<'e> Out<'e> for Root {
type Ok = Unknown<'e>;
fn put(self, value: Unknown<'e>) -> Unknown<'e> {
value
}
}
pub struct Slot<'s, 'e>(pub(super) &'s mut Vec<Unknown<'e>>);
impl<'e> Out<'e> for Slot<'_, 'e> {
type Ok = ();
fn put(self, value: Unknown<'e>) {
self.0.push(value);
}
}
pub struct Entry<'s, 'e> {
pub(super) entries: &'s mut Vec<(String, Unknown<'e>)>,
pub(super) key: String,
}
impl<'e> Out<'e> for Entry<'_, 'e> {
type Ok = ();
fn put(self, value: Unknown<'e>) {
self.entries.push((self.key, value));
}
}
}
use out::{Entry, Out, Root, Slot};
pub struct JsBuilder<'e, O = Root> {
env: &'e Env,
depth: usize,
out: O,
}
impl<'e> JsBuilder<'e> {
pub fn new(env: &'e Env) -> Self {
Self::at_depth(env, 0)
}
pub(crate) fn at_depth(env: &'e Env, depth: usize) -> Self {
Self {
env,
depth,
out: Root,
}
}
pub fn build(env: &'e Env, frame: Frame<'_>) -> napi::Result<Unknown<'e>> {
frame.visit(Self::new(env)).map_err(Error::from)
}
}
impl<O> JsBuilder<'_, O> {
fn check_depth(&self) -> napi::Result<()> {
if self.depth > MAX_DEPTH {
return Err(depth_error());
}
Ok(())
}
}
impl<'e, O: Out<'e>> Sink for JsBuilder<'e, O> {
type Ok = O::Ok;
type Error = JsFault;
type Seq = JsSeq<'e, O>;
type Map = JsMap<'e, O>;
fn scalar(self, ty: Option<Logical>, value: Physical<'_>) -> Result<O::Ok, JsFault> {
self.check_depth()?;
let built = scalar_to_js(self.env, ty, value)?;
Ok(self.out.put(built))
}
fn typed_null(self, _: FixedType) -> Result<O::Ok, JsFault> {
self.scalar(None, Physical::Null)
}
fn seq(self, len: Option<usize>) -> Result<JsSeq<'e, O>, JsFault> {
self.check_depth()?;
Ok(JsSeq {
env: self.env,
depth: self.depth,
out: self.out,
items: Vec::with_capacity(capacity(len)),
})
}
fn map(self, len: Option<usize>) -> Result<JsMap<'e, O>, JsFault> {
self.check_depth()?;
Ok(JsMap {
env: self.env,
depth: self.depth,
out: self.out,
entries: Vec::with_capacity(capacity(len)),
})
}
fn passthrough<P: Encrypt + Clone>(self, value: P) -> Result<O::Ok, JsFault> {
self.check_depth()?;
let built = value.visit(JsBuilder::at_depth(self.env, self.depth + 1))?;
Ok(self.out.put(built))
}
}
fn capacity(len: Option<usize>) -> usize {
eager_capacity(u32::try_from(len.unwrap_or(0)).unwrap_or(u32::MAX))
}
pub struct JsSeq<'e, O> {
env: &'e Env,
depth: usize,
out: O,
items: Vec<Unknown<'e>>,
}
impl<'e, O: Out<'e>> SeqSink for JsSeq<'e, O> {
type Ok = O::Ok;
type Error = JsFault;
type Element<'s>
= JsBuilder<'e, Slot<'s, 'e>>
where
Self: 's;
fn element(&mut self) -> Result<Self::Element<'_>, JsFault> {
Ok(JsBuilder {
env: self.env,
depth: self.depth + 1,
out: Slot(&mut self.items),
})
}
fn end(self) -> Result<O::Ok, JsFault> {
let env = self.env;
let mut array = new_array(env, self.items.len())?;
let mut elements = Vec::with_capacity(self.items.len());
for (i, item) in self.items.iter().enumerate() {
elements.push(own_property(env, &i.to_string(), item)?);
}
define_own_properties(&mut array, &elements)?;
Ok(self.out.put(array.to_unknown()))
}
}
pub struct JsMap<'e, O> {
env: &'e Env,
depth: usize,
out: O,
entries: Vec<(String, Unknown<'e>)>,
}
impl<'e, O: Out<'e>> MapSink for JsMap<'e, O> {
type Ok = O::Ok;
type Error = JsFault;
type Entry<'s>
= JsBuilder<'e, Entry<'s, 'e>>
where
Self: 's;
fn entry(&mut self, key: &str) -> Result<Self::Entry<'_>, JsFault> {
if forbidden_key(key) {
return Err(forbidden_key_error(key).into());
}
Ok(JsBuilder {
env: self.env,
depth: self.depth + 1,
out: Entry {
entries: &mut self.entries,
key: key.to_owned(),
},
})
}
fn end(self) -> Result<O::Ok, JsFault> {
let env = self.env;
let mut object = Object::new(env)?;
let mut properties = Vec::with_capacity(self.entries.len());
for (key, value) in &self.entries {
properties.push(own_property(env, key, value)?);
}
define_own_properties(&mut object, &properties)?;
Ok(self.out.put(object.to_unknown()))
}
}
fn malformed(what: &str) -> Error {
Error::new(
Status::GenericFailure,
format!("{what} is not a value this binding can build"),
)
}
fn scalar_to_js<'e>(
env: &'e Env,
ty: Option<Logical>,
value: Physical<'_>,
) -> napi::Result<Unknown<'e>> {
let Some(ty) = ty else {
return match value {
Physical::Null => Null.into_unknown(env),
Physical::Bool(b) => b.into_unknown(env),
Physical::Int(int) => bigint(int).into_unknown(env),
Physical::Float(Float::F32(f)) => f64::from(f).into_unknown(env),
Physical::Float(Float::F64(f)) => f.into_unknown(env),
Physical::Text(s) => s.into_unknown(env),
Physical::Bytes(b) => Buffer::from(b.to_vec()).into_unknown(env),
};
};
let wrap = |wrapper: Wrapper, text: Option<String>| -> napi::Result<Unknown<'e>> {
let text = text.ok_or_else(|| malformed(wrapper.class_name()))?;
Wrappers::get(env)?.construct(wrapper, &text)
};
match (ty, value) {
(Logical::Undefined, Physical::Null) => ().into_unknown(env),
(Logical::Date, Physical::Int(days)) => wrap(Wrapper::Date, scalar::date_text(days)),
(Logical::Timestamp(unit), Physical::Int(count)) => {
match scalar::timestamp_js_millis(unit, count) {
Some(ms) => Ok(env.create_date(ms)?.to_unknown()),
None => wrap(Wrapper::Timestamp, scalar::timestamp_text(unit, count)),
}
}
(Logical::Decimal { scale }, Physical::Int(mantissa)) => {
wrap(Wrapper::Decimal, scalar::decimal_text(scale, mantissa))
}
(Logical::Uuid, Physical::Bytes(bytes)) => wrap(Wrapper::Uuid, scalar::uuid_text(bytes)),
(Logical::Unknown(unknown), _) => Err(Error::new(
Status::GenericFailure,
format!(
"logical type {} is not known to this binding and has no JavaScript value",
unknown.id()
),
)),
(ty, _) => Err(malformed(&ty.to_string())),
}
}
fn bigint(int: Int) -> BigInt {
match int.to_i128() {
Some(v) => BigInt::from(v),
None => BigInt::from(int.to_u128().unwrap_or(u128::MAX)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::js::MAX_DEPTH;
#[test]
fn a_length_hint_buys_a_bounded_reservation() {
assert_eq!(capacity(None), 0);
assert_eq!(capacity(Some(0)), 0);
assert_eq!(capacity(Some(37)), 37);
assert_eq!(capacity(Some(usize::MAX)), eager_capacity(u32::MAX));
assert_eq!(capacity(Some(1 << 40)), eager_capacity(u32::MAX));
}
#[test]
fn every_int_width_becomes_a_bigint_of_its_sign_and_magnitude() {
let big = bigint(Int::I8(-1));
assert!(big.sign_bit, "negative");
assert_eq!(big.words, [1, 0]);
let big = bigint(Int::U64(u64::MAX));
assert!(!big.sign_bit);
assert_eq!(big.words, [u64::MAX, 0]);
let big = bigint(Int::I128(i128::MIN));
assert!(big.sign_bit);
assert_eq!(big.words, [0, 1 << 63]);
let big = bigint(Int::U128(u128::MAX));
assert!(!big.sign_bit);
assert_eq!(big.words, [u64::MAX, u64::MAX]);
}
#[test]
fn the_depth_check_refuses_one_past_the_limit() {
let env = Env::from_raw(std::ptr::null_mut());
assert!(JsBuilder::at_depth(&env, MAX_DEPTH).check_depth().is_ok());
let error = JsBuilder::at_depth(&env, MAX_DEPTH + 1)
.check_depth()
.expect_err("past the limit");
assert_eq!(error.status, Status::InvalidArg);
assert!(error.reason.contains("nested too deeply"), "{error}");
}
#[test]
fn a_sequence_at_the_limit_refuses_its_elements() {
let env = Env::from_raw(std::ptr::null_mut());
let mut seq = JsSeq {
env: &env,
depth: MAX_DEPTH,
out: Root,
items: Vec::new(),
};
let element = seq.element().expect("the element slot itself");
assert_eq!(element.depth, MAX_DEPTH + 1);
element.check_depth().expect_err("one level deeper");
}
#[test]
fn a_map_at_the_limit_refuses_its_entries() {
let env = Env::from_raw(std::ptr::null_mut());
let mut map = JsMap {
env: &env,
depth: MAX_DEPTH,
out: Root,
entries: Vec::new(),
};
let entry = map.entry("k").expect("the entry slot itself");
assert_eq!(entry.depth, MAX_DEPTH + 1);
entry.check_depth().expect_err("one level deeper");
}
#[test]
fn a_passthrough_at_the_limit_builds_its_value_one_level_deeper() {
#[derive(Clone)]
struct Probe;
impl Encrypt for Probe {
fn visit<S: Sink>(self, sink: S) -> Result<S::Ok, S::Error> {
match sink.seq(Some(0)) {
Ok(_) => panic!("a sequence one level past the limit was accepted"),
Err(error) => Err(error),
}
}
}
let env = Env::from_raw(std::ptr::null_mut());
let error = JsBuilder::at_depth(&env, MAX_DEPTH)
.passthrough(Probe)
.err()
.expect("the passthrough's value sits one level deeper");
assert!(error.reason().contains("nested too deeply"), "{error:?}");
}
}