use rong::{function::*, *};
#[allow(clippy::upper_case_acronyms)]
macro_rules! define_error_names {
($($name:ident => $display:literal),* $(,)?) => {
#[allow(clippy::upper_case_acronyms)]
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Copy)]
pub enum DOMExceptionName {
$($name,)*
}
impl DOMExceptionName {
#[inline]
pub fn as_str(&self) -> &'static str {
match self {
$(DOMExceptionName::$name => $display,)*
}
}
#[inline]
pub fn iter() -> impl Iterator<Item = &'static str> {
[$($display),*].into_iter()
}
#[inline]
pub fn find_or_default(s: &str) -> Self {
match s {
$(stringify!($name) | $display => DOMExceptionName::$name,)*
_ => DOMExceptionName::ERROR,
}
}
}
}
}
define_error_names! {
INDEX_SIZE_ERR => "IndexSizeError",
DOMSTRING_SIZE_ERR => "DOMStringSizeError",
HIERARCHY_REQUEST_ERR => "HierarchyRequestError",
INVALID_CHARACTER_ERR => "InvalidCharacterError",
NO_DATA_ALLOWED_ERR => "NoDataAllowedError",
NO_MODIFICATION_ALLOWED_ERR => "NoModificationAllowedError",
NOT_FOUND_ERR => "NotFoundError",
NOT_SUPPORTED_ERR => "NotSupportedError",
INUSE_ATTRIBUTE_ERR => "InUseAttributeError",
INVALID_STATE_ERR => "InvalidStateError",
SYNTAX_ERR => "SyntaxError",
INVALID_MODIFICATION_ERR => "InvalidModificationError",
NAMESPACE_ERR => "NamespaceError",
INVALID_ACCESS_ERR => "InvalidAccessError",
VALIDATION_ERR => "ValidationError",
TYPE_MISMATCH_ERR => "TypeMismatchError",
SECURITY_ERR => "SecurityError",
NETWORK_ERR => "NetworkError",
ABORT_ERR => "AbortError",
URL_MISMATCH_ERR => "URLMismatchError",
QUOTA_EXCEEDED_ERR => "QuotaExceededError",
TIMEOUT_ERR => "TimeoutError",
INVALID_NODE_TYPE_ERR => "InvalidNodeTypeError",
DATA_CLONE_ERR => "DataCloneError",
ERROR => "Error",
}
impl From<&str> for DOMExceptionName {
fn from(s: &str) -> Self {
DOMExceptionName::find_or_default(s)
}
}
impl From<Option<String>> for DOMExceptionName {
fn from(value: Option<String>) -> Self {
match value {
Some(s) => s.as_str().into(),
None => DOMExceptionName::ERROR,
}
}
}
#[js_export]
pub struct DOMException {
name: DOMExceptionName,
message: String,
}
#[js_class]
impl DOMException {
#[js_method(constructor)]
fn new(message: Optional<String>, name: Optional<String>) -> Self {
Self {
message: message.0.unwrap_or_default(),
name: name.0.into(),
}
}
#[js_method(getter)]
fn name(&self) -> String {
self.name.as_str().to_string()
}
#[js_method(getter)]
fn message(&self) -> String {
self.message.clone()
}
#[js_method(getter)]
fn stack(&self) -> String {
"NotImplemented".to_string()
}
#[js_method(gc_mark)]
fn gc_mark_with<F>(&self, _mark_fn: F)
where
F: FnMut(&JSValue),
{
}
pub fn create(ctx: &JSContext, message: &str, name: DOMExceptionName) -> JSResult<JSObject> {
let dom = DOMException {
message: message.to_string(),
name,
};
Ok(Class::lookup::<DOMException>(ctx)?.instance(dom))
}
}
pub fn init(ctx: &JSContext) -> JSResult<()> {
ctx.register_class::<DOMException>()?;
let constructor = Class::lookup::<DOMException>(ctx)?;
for name in DOMExceptionName::iter() {
constructor.define_property(
name,
PropertyDescriptor::from_rust(ctx, name)
.enumerable()
.readonly()
.non_configurable(),
)?;
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use rong_test::*;
#[test]
fn test_dom_exception() {
async_run!(|ctx: JSContext| async move {
init(&ctx)?;
let result = ctx.eval::<JSObject>(Source::from_bytes(
r#"
const ex = new DOMException("Operation failed", "ABORT_ERR");
({
name: ex.name,
message: ex.message
})
"#,
))?;
assert_eq!(result.get::<_, String>("name")?, "AbortError");
assert_eq!(result.get::<_, String>("message")?, "Operation failed");
Ok(())
});
}
}