1use napi::bindgen_prelude::{
31 Array, BigInt, FromNapiValue, JsObjectValue, JsValue, Object, Uint8ArraySlice, Unknown,
32};
33use napi::{Env, Error, Status, ValueType};
34use vitaminc_aead::frame::FrameWriteError;
35use vitaminc_aead::plaintext::{Error as _, SourceError};
36use vitaminc_aead::{
37 to_frame, Encrypt, Float, Logical, MapSink, Physical, SeqSink, Sink, TimeUnit,
38};
39use vitaminc_protected::Protected;
40
41use crate::error::JsFault;
42use crate::js::{
43 depth_error, forbidden_key, forbidden_key_error, get_own, lossless_string, not_plain_error,
44 own_enumerable_keys, MAX_DEPTH,
45};
46use crate::scalar::{self, ConversionError};
47use crate::wrappers::{Wrapper, Wrappers};
48
49pub struct JsRef<'e> {
51 value: Unknown<'e>,
52 depth: usize,
53}
54
55impl<'e> JsRef<'e> {
56 pub fn new(value: Unknown<'e>) -> Self {
58 Self { value, depth: 0 }
59 }
60
61 pub(crate) fn at_depth(value: Unknown<'e>, depth: usize) -> Self {
64 Self { value, depth }
65 }
66
67 pub fn to_frame(self) -> napi::Result<Protected<Vec<u8>>> {
71 to_frame(self).map_err(frame_error)
72 }
73}
74
75impl Encrypt for JsRef<'_> {
76 fn visit<S: Sink>(self, sink: S) -> Result<S::Ok, S::Error> {
77 js_walk(self.value, self.depth, sink)
78 }
79}
80
81pub(crate) fn frame_error(error: FrameWriteError) -> Error {
83 match error {
84 FrameWriteError::Source(source) => JsFault::from_source(&source),
85 FrameWriteError::TooDeep => depth_error(),
86 other => Error::new(Status::InvalidArg, other.to_string()),
87 }
88}
89
90fn js_fault<S: Sink>(error: Error) -> S::Error {
92 S::Error::source(SourceError::new(JsFault::from(error)))
93}
94
95fn js_conversion<S: Sink>(env: &Env, error: ConversionError) -> S::Error {
98 let error = match env.throw_type_error(&error.to_string(), Some(error.code())) {
99 Ok(()) => Error::new(Status::PendingException, error.to_string()),
100 Err(error) => error,
101 };
102 js_fault::<S>(error)
103}
104
105fn js_walk<S: Sink>(value: Unknown<'_>, depth: usize, sink: S) -> Result<S::Ok, S::Error> {
106 if depth > MAX_DEPTH {
107 return Err(js_fault::<S>(depth_error()));
108 }
109 match value.get_type().map_err(js_fault::<S>)? {
110 ValueType::Null => sink.scalar(None, Physical::Null),
111 ValueType::Undefined => sink.scalar(Some(Logical::Undefined), Physical::Null),
112 ValueType::Boolean => {
113 let b = bool::from_unknown(value).map_err(js_fault::<S>)?;
114 sink.scalar(None, Physical::Bool(b))
115 }
116 ValueType::Number => {
117 let n = f64::from_unknown(value).map_err(js_fault::<S>)?;
118 sink.scalar(None, Physical::Float(Float::F64(n)))
119 }
120 ValueType::BigInt => {
121 let env = Env::from_raw(value.value().env);
122 let big = BigInt::from_unknown(value).map_err(js_fault::<S>)?;
123 let int = scalar::bigint(big.sign_bit, &big.words)
124 .map_err(|e| js_conversion::<S>(&env, e))?;
125 sink.scalar(None, Physical::Int(int))
126 }
127 ValueType::String => {
128 let text = lossless_string(value, "string").map_err(js_fault::<S>)?;
131 sink.scalar(None, Physical::Text(&text))
132 }
133 ValueType::Object => js_object(value, depth, sink),
134 other => Err(js_fault::<S>(Error::new(
135 Status::InvalidArg,
136 format!("JS type cannot be encrypted: {other}"),
137 ))),
138 }
139}
140
141fn js_object<S: Sink>(value: Unknown<'_>, depth: usize, sink: S) -> Result<S::Ok, S::Error> {
146 if value.is_typedarray().map_err(js_fault::<S>)? {
150 return js_bytes(value, sink);
151 }
152 if value.is_dataview().map_err(js_fault::<S>)? {
153 return Err(js_fault::<S>(not_bytes_error("DataView")));
154 }
155 if value.is_array().map_err(js_fault::<S>)? {
156 return js_array(value, depth, sink);
157 }
158 if value.is_date().map_err(js_fault::<S>)? {
159 return js_date(value, sink);
160 }
161 match js_classify(value).map_err(js_fault::<S>)? {
162 Some(wrapper) => js_wrapped(value, wrapper, sink),
163 None => js_map(value, depth, sink),
164 }
165}
166
167fn js_bytes<S: Sink>(value: Unknown<'_>, sink: S) -> Result<S::Ok, S::Error> {
178 let arr = Uint8ArraySlice::from_unknown(value)
179 .map_err(|_| js_fault::<S>(not_bytes_error("typed array")))?;
180 sink.scalar(None, Physical::Bytes(&arr))
181}
182
183fn not_bytes_error(what: &str) -> Error {
184 Error::new(
185 Status::InvalidArg,
186 format!("only a Buffer or a Uint8Array can be encrypted as bytes, not a {what}"),
187 )
188}
189
190fn js_date<S: Sink>(value: Unknown<'_>, sink: S) -> Result<S::Ok, S::Error> {
191 let env = Env::from_raw(value.value().env);
192 let date = napi::JsDate::from_unknown(value).map_err(js_fault::<S>)?;
193 let ms = date.value_of().map_err(js_fault::<S>)?;
194 let count = scalar::date_millis(ms).map_err(|e| js_conversion::<S>(&env, e))?;
195 sink.scalar(
196 Some(Logical::Timestamp(TimeUnit::Millis)),
197 Physical::Int(count),
198 )
199}
200
201fn js_classify(value: Unknown<'_>) -> napi::Result<Option<Wrapper>> {
204 let env = Env::from_raw(value.value().env);
205 let wrappers = Wrappers::get(&env)?;
206 if wrappers.is_plain(value)? {
209 return Ok(None);
210 }
211 match wrappers.classify(&value)? {
212 Some(wrapper) => Ok(Some(wrapper)),
213 None => Err(not_plain_error()),
214 }
215}
216
217fn js_map<S: Sink>(value: Unknown<'_>, depth: usize, sink: S) -> Result<S::Ok, S::Error> {
218 let obj = Object::from_unknown(value).map_err(js_fault::<S>)?;
219 let keys = own_enumerable_keys(&obj).map_err(js_fault::<S>)?;
220 let mut map = sink.map(Some(keys.len()))?;
221 for key in keys {
222 if forbidden_key(&key.name) {
223 return Err(js_fault::<S>(forbidden_key_error(&key.name)));
224 }
225 let value = get_own(&obj, &key).map_err(js_fault::<S>)?;
228 js_walk(value, depth + 1, map.entry(&key.name)?)?;
229 }
230 map.end()
231}
232
233fn js_array<S: Sink>(value: Unknown<'_>, depth: usize, sink: S) -> Result<S::Ok, S::Error> {
234 let arr = Array::from_unknown(value).map_err(js_fault::<S>)?;
235 let obj = Object::from_unknown(value).map_err(js_fault::<S>)?;
240 let len = arr.len();
241 let mut seq = sink.seq(Some(len as usize))?;
242 for i in 0..len {
243 let sparse = || Error::new(Status::InvalidArg, "sparse arrays are not supported");
247 if !obj
248 .has_own_property(&i.to_string())
249 .map_err(js_fault::<S>)?
250 {
251 return Err(js_fault::<S>(sparse()));
252 }
253 let element: Unknown = arr
254 .get(i)
255 .map_err(js_fault::<S>)?
256 .ok_or_else(|| js_fault::<S>(sparse()))?;
257 js_walk(element, depth + 1, seq.element()?)?;
258 }
259 seq.end()
260}
261
262fn js_wrapped<S: Sink>(value: Unknown<'_>, wrapper: Wrapper, sink: S) -> Result<S::Ok, S::Error> {
266 let env = &Env::from_raw(value.value().env);
267 let wrappers = Wrappers::get(env).map_err(js_fault::<S>)?;
268 let instance = &Object::from_unknown(value).map_err(js_fault::<S>)?;
269 let invalid = match wrapper {
270 Wrapper::Date => ConversionError::InvalidDate,
271 Wrapper::Timestamp => ConversionError::InvalidTimestamp,
272 Wrapper::Decimal => ConversionError::InvalidDecimal,
273 Wrapper::Uuid => ConversionError::InvalidUuid,
274 };
275 let text = match wrappers.text(instance).map_err(js_fault::<S>)? {
276 Some(text) => lossless_string(text, wrapper.class_name()).map_err(js_fault::<S>)?,
277 None => return Err(js_conversion::<S>(env, invalid)),
278 };
279 let convert = |e| js_conversion::<S>(env, e);
280 match wrapper {
281 Wrapper::Date => {
282 let days = scalar::date(&text).map_err(convert)?;
283 sink.scalar(Some(Logical::Date), Physical::Int(days))
284 }
285 Wrapper::Timestamp => {
286 let nanos = scalar::timestamp(&text).map_err(convert)?;
287 sink.scalar(
288 Some(Logical::Timestamp(TimeUnit::Nanos)),
289 Physical::Int(nanos),
290 )
291 }
292 Wrapper::Decimal => {
293 let (scale, mantissa) = scalar::decimal(&text).map_err(convert)?;
294 sink.scalar(Some(Logical::Decimal { scale }), Physical::Int(mantissa))
295 }
296 Wrapper::Uuid => {
297 let bytes = scalar::uuid(&text).map_err(convert)?;
298 sink.scalar(Some(Logical::Uuid), Physical::Bytes(&bytes))
299 }
300 }
301}
302
303#[cfg(test)]
304mod tests {
305 use super::*;
306
307 #[test]
308 fn a_frame_writers_refusal_becomes_the_napi_error_the_host_expects() {
309 let too_deep = frame_error(FrameWriteError::TooDeep);
310 assert_eq!(too_deep.status, Status::InvalidArg);
311 assert!(too_deep.reason.contains("nested too deeply"), "{too_deep}");
312
313 let too_long = frame_error(FrameWriteError::TooLong);
314 assert_eq!(too_long.status, Status::InvalidArg);
315 assert_eq!(too_long.reason, FrameWriteError::TooLong.to_string());
316
317 let thrown = Error::new(Status::PendingException, "the getter threw");
319 let source = FrameWriteError::Source(SourceError::new(JsFault::from(thrown)));
320 let back = frame_error(source);
321 assert_eq!(back.status, Status::PendingException);
322 assert_eq!(back.reason, "the getter threw");
323 }
324}