use boa_engine::object::builtins::{JsArrayBuffer, JsTypedArray, JsUint8Array};
use boa_engine::realm::Realm;
use boa_engine::value::TryFromJs;
use boa_engine::{
Context, Finalize, JsData, JsObject, JsResult, JsString, JsValue, Trace, boa_class, boa_module,
js_error, js_string,
};
#[cfg(test)]
mod tests;
mod encodings;
#[derive(Debug, Default, Clone, JsData, Trace, Finalize)]
pub enum TextDecoder {
#[default]
Utf8,
Utf16Le,
Utf16Be,
}
#[boa_class]
impl TextDecoder {
#[boa(constructor)]
pub fn constructor(encoding: Option<JsString>, _options: Option<JsObject>) -> JsResult<Self> {
let Some(encoding) = encoding else {
return Ok(Self::default());
};
match encoding.to_std_string_lossy().as_str() {
"utf-8" => Ok(Self::Utf8),
"utf-16" | "utf-16le" => Ok(Self::Utf16Le),
"utf-16be" => Ok(Self::Utf16Be),
e => Err(js_error!(RangeError: "The given encoding '{}' is not supported.", e)),
}
}
#[boa(getter)]
#[must_use]
pub fn encoding(&self) -> JsString {
match self {
Self::Utf8 => js_string!("utf-8"),
Self::Utf16Le => js_string!("utf-16le"),
Self::Utf16Be => js_string!("utf-16be"),
}
}
pub fn decode(&self, buffer: JsValue, context: &mut Context) -> JsResult<JsString> {
let bytes = if let Ok(array_buffer) = JsArrayBuffer::try_from_js(&buffer, context) {
JsUint8Array::from_array_buffer(array_buffer, context)?
} else if let Ok(typed_array) = JsTypedArray::try_from_js(&buffer, context) {
let Some(buffer) = typed_array.buffer(context)?.as_object() else {
return Err(js_error!(TypeError: "Invalid buffer backing TypedArray."));
};
JsUint8Array::from_array_buffer(JsArrayBuffer::from_object(buffer)?, context)?
} else {
return Err(
js_error!(TypeError: "Argument 1 must be an ArrayBuffer, TypedArray or DataView."),
);
};
let buffer = bytes.iter(context).collect::<Vec<u8>>();
Ok(match self {
Self::Utf8 => encodings::utf8::decode(&buffer),
Self::Utf16Le => encodings::utf16le::decode(&buffer),
Self::Utf16Be => encodings::utf16be::decode(buffer),
})
}
}
#[derive(Debug, Default, Clone, JsData, Trace, Finalize)]
pub enum TextEncoder {
#[default]
Utf8,
Utf16Le,
Utf16Be,
}
#[boa_class]
impl TextEncoder {
#[boa(constructor)]
pub fn constructor(encoding: Option<JsString>, _options: Option<JsObject>) -> JsResult<Self> {
let Some(encoding) = encoding else {
return Ok(Self::default());
};
match encoding.to_std_string_lossy().as_str() {
"utf-8" => Ok(Self::Utf8),
"utf-16" | "utf-16le" => Ok(Self::Utf16Le),
"utf-16be" => Ok(Self::Utf16Be),
e => Err(js_error!(RangeError: "The given encoding '{}' is not supported.", e)),
}
}
#[boa(getter)]
#[must_use]
fn encoding(&self) -> JsString {
match self {
Self::Utf8 => js_string!("utf-8"),
Self::Utf16Le => js_string!("utf-16le"),
Self::Utf16Be => js_string!("utf-16be"),
}
}
pub fn encode(&self, text: Option<JsString>, context: &mut Context) -> JsResult<JsUint8Array> {
let Some(text) = text else {
return JsUint8Array::from_iter([], context);
};
let vec = match self {
Self::Utf8 => encodings::utf8::encode(&text),
Self::Utf16Le => encodings::utf16le::encode(&text),
Self::Utf16Be => encodings::utf16be::encode(&text),
};
JsUint8Array::from_iter(vec, context)
}
}
#[boa_module]
pub mod js_module {
type TextDecoder = super::TextDecoder;
type TextEncoder = super::TextEncoder;
}
pub fn register(realm: Option<Realm>, context: &mut Context) -> JsResult<()> {
js_module::boa_register(realm, context)
}