use std::str;
use crate::{
Args, hold, hold_and_release,
quickjs::{
Ctx, Exception, Function, String as JSString, TypedArray, Value, context::EvalOptions,
},
to_js_error, to_string_lossy,
};
use anyhow::{Error, Result, anyhow, bail};
pub(crate) fn register(this: Ctx<'_>) -> Result<()> {
let globals = this.globals();
globals.set(
"__javy_decodeUtf8BufferToString",
Function::new(this.clone(), |cx, args| {
let (cx, args) = hold_and_release!(cx, args);
decode(hold!(cx.clone(), args)).map_err(|e| to_js_error(cx, e))
}),
)?;
globals.set(
"__javy_encodeStringToUtf8Buffer",
Function::new(this.clone(), |cx, args| {
let (cx, args) = hold_and_release!(cx, args);
encode(hold!(cx.clone(), args)).map_err(|e| to_js_error(cx, e))
}),
)?;
let mut opts = EvalOptions::default();
opts.strict = false;
this.eval_with_options::<(), _>(include_str!("./text-encoding.js"), opts)?;
Ok::<_, Error>(())
}
fn decode(args: Args<'_>) -> Result<Value<'_>> {
let (cx, args) = args.release();
if args.len() != 5 {
bail!(
"Wrong number of arguments. Expected 5 arguments. Got: {}",
args.len()
);
}
let buffer = args[0]
.as_object()
.ok_or_else(|| anyhow!("buffer must be an object"))?
.as_array_buffer()
.ok_or_else(|| anyhow!("buffer must be an ArrayBuffer"))?
.as_bytes()
.ok_or_else(|| anyhow!("Couldn't retrive &[u8] from buffer"))?;
let byte_offset = args[1]
.as_number()
.ok_or_else(|| anyhow!("offset must be a number"))? as usize;
let byte_length = args[2]
.as_number()
.ok_or_else(|| anyhow!("byte_length must be a number"))? as usize;
let fatal = args[3]
.as_bool()
.ok_or_else(|| anyhow!("fatal must be a boolean"))?;
let ignore_bom = args[4]
.as_bool()
.ok_or_else(|| anyhow!("ignore_bom must be a boolean"))?;
let mut view = buffer
.get(byte_offset..(byte_offset + byte_length))
.ok_or_else(|| anyhow!("Provided offset and length is not valid for provided buffer"))?;
if !ignore_bom {
view = match view {
[0xEF, 0xBB, 0xBF, rest @ ..] => rest,
_ => view,
};
}
let js_string = if fatal {
JSString::from_str(
cx.clone(),
str::from_utf8(view)
.map_err(|_| Exception::throw_type(&cx, "The encoded data was not valid utf-8"))?,
)
} else {
let str = String::from_utf8_lossy(view);
JSString::from_str(cx, &str)
};
Ok(Value::from_string(js_string?))
}
fn encode(args: Args<'_>) -> Result<Value<'_>> {
let (cx, args) = args.release();
if args.len() != 1 {
bail!("Wrong number of arguments. Expected 1. Got {}", args.len());
}
let js_string = args[0]
.as_string()
.ok_or_else(|| anyhow!("Argument must be a String"))?;
let encoded = js_string
.to_string()
.unwrap_or_else(|error| to_string_lossy(&cx, js_string, error));
Ok(TypedArray::new(cx, encoded.into_bytes())?
.as_value()
.to_owned())
}
#[cfg(test)]
mod tests {
use crate::{Config, Runtime, quickjs::Value};
use anyhow::{Error, Result};
#[test]
fn test_text_encoder_decoder() -> Result<()> {
let mut config = Config::default();
config.text_encoding(true);
let runtime = Runtime::new(config)?;
runtime.context().with(|this| {
let result: Value<'_> = this.eval(
r#"
let encoder = new TextEncoder();
let decoder = new TextDecoder();
let buffer = encoder.encode('hello');
decoder.decode(buffer) == 'hello';
"#,
)?;
assert!(result.as_bool().unwrap());
Ok::<_, Error>(())
})?;
Ok(())
}
}