mirror of
https://github.com/lexmount/moli.git
synced 2026-10-04 08:00:45 +00:00
2666 lines
95 KiB
Rust
2666 lines
95 KiB
Rust
use std::collections::HashMap;
|
|
|
|
use quote::{format_ident, quote};
|
|
use syn::spanned::Spanned;
|
|
use syn::{Data, DeriveInput, Error, Field, Fields, GenericParam, Ident, Lit, LitStr, Type};
|
|
|
|
use crate::attrs::{
|
|
ConstructorAttr, ConstructorDefaultAttr, FunctionTemplateAttrs, ObjectAttrs, RenameRule,
|
|
ValueInitAttr, parse_field_attrs, parse_function_template_attrs, parse_interface_attrs,
|
|
parse_object_attrs,
|
|
};
|
|
|
|
struct WebApiFieldKey {
|
|
display_name: proc_macro2::TokenStream,
|
|
property_key: proc_macro2::TokenStream,
|
|
function_name: Option<proc_macro2::TokenStream>,
|
|
}
|
|
|
|
pub(crate) fn expand_webapi_interface(
|
|
input: DeriveInput,
|
|
) -> Result<proc_macro2::TokenStream, Error> {
|
|
let struct_name = input.ident;
|
|
let generics = input.generics;
|
|
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
|
let attrs = parse_interface_attrs(&input.attrs)?;
|
|
let interface_name = attrs
|
|
.name
|
|
.unwrap_or_else(|| LitStr::new(&struct_name.to_string(), struct_name.span()));
|
|
let parent = match attrs.parent {
|
|
Some(parent) => quote!(::std::option::Option::Some(#parent)),
|
|
None => quote!(::std::option::Option::None),
|
|
};
|
|
let constructor_callback = match attrs
|
|
.constructor
|
|
.clone()
|
|
.unwrap_or(ConstructorAttr::Illegal)
|
|
{
|
|
ConstructorAttr::Illegal => {
|
|
quote!(::moli_webapi_declare::illegal_constructor_callback)
|
|
}
|
|
ConstructorAttr::Callback(callback) => quote!(#callback),
|
|
};
|
|
let constructor_length = attrs.constructor_length.unwrap_or(0);
|
|
let fields = named_fields(&input.data)?;
|
|
let methods = fields
|
|
.iter()
|
|
.filter_map(|field| expand_interface_field(field, attrs.rename_all))
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
|
|
let body = quote! {
|
|
const INTERFACE_NAME: &'static str = #interface_name;
|
|
const PARENT_INTERFACE: ::std::option::Option<&'static str> = #parent;
|
|
|
|
let prototype = ::moli_webapi_declare::v8::Object::new(scope);
|
|
if let ::std::option::Option::Some(parent) = PARENT_INTERFACE {
|
|
if let ::std::option::Option::Some(parent_prototype) =
|
|
::moli_webapi_declare::__private::global_constructor_prototype(scope, parent)
|
|
{
|
|
let _ = prototype.set_prototype(scope, parent_prototype.into());
|
|
}
|
|
}
|
|
#(#methods)*
|
|
let constructor = ::moli_webapi_declare::v8::Function::builder(#constructor_callback)
|
|
.length(#constructor_length)
|
|
.build(scope)
|
|
.ok_or_else(|| {
|
|
::moli_webapi_declare::BindError::new(
|
|
::std::format!("failed to build `{}` constructor", INTERFACE_NAME)
|
|
)
|
|
})?;
|
|
constructor.set_name(::moli_webapi_declare::__private::v8str(scope, INTERFACE_NAME));
|
|
::moli_webapi_declare::define_interface_prototype_property(
|
|
scope,
|
|
constructor,
|
|
prototype,
|
|
)?;
|
|
::moli_webapi_declare::define_interface_constructor_property(
|
|
scope,
|
|
prototype,
|
|
constructor,
|
|
)?;
|
|
::moli_webapi_declare::define_to_string_tag(scope, prototype, INTERFACE_NAME);
|
|
global
|
|
.define_own_property(
|
|
scope,
|
|
::moli_webapi_declare::__private::v8str(scope, INTERFACE_NAME).into(),
|
|
constructor.into(),
|
|
::moli_webapi_declare::v8::PropertyAttribute::DONT_ENUM,
|
|
)
|
|
.unwrap_or(false)
|
|
.then_some(())
|
|
.ok_or_else(|| {
|
|
::moli_webapi_declare::BindError::new(
|
|
::std::format!("failed to define `{}` constructor", INTERFACE_NAME)
|
|
)
|
|
})?;
|
|
::std::result::Result::Ok(constructor)
|
|
};
|
|
|
|
Ok(quote! {
|
|
impl #impl_generics #struct_name #ty_generics #where_clause {
|
|
pub fn bind<'s>(
|
|
&self,
|
|
scope: &mut ::moli_webapi_declare::v8::PinScope<'s, '_>,
|
|
global: ::moli_webapi_declare::v8::Local<'s, ::moli_webapi_declare::v8::Object>,
|
|
) -> ::std::result::Result<
|
|
::moli_webapi_declare::v8::Local<'s, ::moli_webapi_declare::v8::Function>,
|
|
::moli_webapi_declare::BindError,
|
|
> {
|
|
#body
|
|
}
|
|
}
|
|
|
|
impl #impl_generics ::moli_webapi_declare::WebApiInterfaceDeclaration for #struct_name #ty_generics #where_clause {
|
|
const NAME: &'static str = #interface_name;
|
|
const PARENT: ::std::option::Option<&'static str> = #parent;
|
|
|
|
fn bind<'s>(
|
|
&self,
|
|
scope: &mut ::moli_webapi_declare::v8::PinScope<'s, '_>,
|
|
global: ::moli_webapi_declare::v8::Local<'s, ::moli_webapi_declare::v8::Object>,
|
|
) -> ::std::result::Result<
|
|
::moli_webapi_declare::v8::Local<'s, ::moli_webapi_declare::v8::Function>,
|
|
::moli_webapi_declare::BindError,
|
|
> {
|
|
self.bind(scope, global)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
pub(crate) fn expand_webapi_function_template(
|
|
input: DeriveInput,
|
|
) -> Result<proc_macro2::TokenStream, Error> {
|
|
let struct_name = input.ident;
|
|
let generics = input.generics;
|
|
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
|
let attrs = parse_function_template_attrs(&input.attrs)?;
|
|
let template_name = attrs
|
|
.name
|
|
.clone()
|
|
.unwrap_or_else(|| LitStr::new(&struct_name.to_string(), struct_name.span()));
|
|
let constructor_callback = match attrs
|
|
.constructor
|
|
.clone()
|
|
.unwrap_or(ConstructorAttr::Illegal)
|
|
{
|
|
ConstructorAttr::Illegal => {
|
|
quote!(::moli_webapi_declare::illegal_constructor_callback)
|
|
}
|
|
ConstructorAttr::Callback(callback) => quote!(#callback),
|
|
};
|
|
let constructor_length = attrs.constructor_length.unwrap_or(0);
|
|
let initialize_intrinsic_prototype_parent =
|
|
attrs.intrinsic_prototype_parent.as_ref().map(|intrinsic| {
|
|
quote! {
|
|
let intrinsic_parent =
|
|
::moli_webapi_declare::v8::FunctionTemplate::builder(
|
|
::moli_webapi_declare::illegal_constructor_callback,
|
|
)
|
|
.constructor_behavior(
|
|
::moli_webapi_declare::v8::ConstructorBehavior::Throw,
|
|
)
|
|
.build(scope);
|
|
intrinsic_parent.remove_prototype();
|
|
intrinsic_parent.set_intrinsic_data_property(
|
|
::moli_webapi_declare::__private::v8str(scope, "prototype").into(),
|
|
#intrinsic,
|
|
::moli_webapi_declare::v8::PropertyAttribute::NONE,
|
|
);
|
|
template.inherit(intrinsic_parent);
|
|
}
|
|
});
|
|
let initialize_readonly_prototype = attrs.readonly_prototype.then(|| {
|
|
quote! {
|
|
template.read_only_prototype();
|
|
}
|
|
});
|
|
let initialize_prototype_to_string_tag = attrs.prototype_to_string_tag.as_ref().map(|tag| {
|
|
quote! {
|
|
prototype.set_with_attr(
|
|
::moli_webapi_declare::v8::Symbol::get_to_string_tag(scope).into(),
|
|
::moli_webapi_declare::__private::v8str(scope, #tag).into(),
|
|
::moli_webapi_declare::v8::PropertyAttribute::READ_ONLY
|
|
| ::moli_webapi_declare::v8::PropertyAttribute::DONT_ENUM,
|
|
);
|
|
}
|
|
});
|
|
let fields = named_fields(&input.data)?;
|
|
let declaration_field_reads = fields.iter().filter_map(|field| {
|
|
field.ident.as_ref().map(|ident| {
|
|
quote! {
|
|
let _ = &self.#ident;
|
|
}
|
|
})
|
|
});
|
|
let template_fields = expand_function_template_fields(&fields, &attrs, &template_name)?;
|
|
let FunctionTemplateFieldExpansions {
|
|
template_methods,
|
|
prototype_methods,
|
|
} = template_fields;
|
|
|
|
Ok(quote! {
|
|
impl #impl_generics #struct_name #ty_generics #where_clause {
|
|
fn __webapi_mark_declaration_fields_used(&self) {
|
|
#(#declaration_field_reads)*
|
|
}
|
|
|
|
pub fn build<'s>(
|
|
scope: &mut ::moli_webapi_declare::v8::PinScope<'s, '_, ()>,
|
|
) -> ::moli_webapi_declare::v8::Local<'s, ::moli_webapi_declare::v8::FunctionTemplate> {
|
|
let _ = Self::__webapi_mark_declaration_fields_used;
|
|
let template = ::moli_webapi_declare::v8::FunctionTemplate::builder(#constructor_callback)
|
|
.length(#constructor_length)
|
|
.build(scope);
|
|
template.set_class_name(::moli_webapi_declare::__private::v8str(scope, #template_name));
|
|
Self::initialize_template(scope, template);
|
|
let prototype = template.prototype_template(scope);
|
|
Self::initialize_prototype_template(scope, prototype);
|
|
template
|
|
}
|
|
|
|
pub fn initialize_template<'s>(
|
|
scope: &mut ::moli_webapi_declare::v8::PinScope<'s, '_, ()>,
|
|
template: ::moli_webapi_declare::v8::Local<'s, ::moli_webapi_declare::v8::FunctionTemplate>,
|
|
) {
|
|
#initialize_intrinsic_prototype_parent
|
|
#initialize_readonly_prototype
|
|
#(#template_methods)*
|
|
}
|
|
|
|
pub fn initialize_prototype_template<'s>(
|
|
scope: &mut ::moli_webapi_declare::v8::PinScope<'s, '_, ()>,
|
|
prototype: ::moli_webapi_declare::v8::Local<'s, ::moli_webapi_declare::v8::ObjectTemplate>,
|
|
) {
|
|
#initialize_prototype_to_string_tag
|
|
#(#prototype_methods)*
|
|
}
|
|
}
|
|
|
|
impl #impl_generics ::moli_webapi_declare::WebApiFunctionTemplateDeclaration for #struct_name #ty_generics #where_clause {
|
|
const NAME: &'static str = #template_name;
|
|
|
|
fn build<'s>(
|
|
scope: &mut ::moli_webapi_declare::v8::PinScope<'s, '_, ()>,
|
|
) -> ::moli_webapi_declare::v8::Local<'s, ::moli_webapi_declare::v8::FunctionTemplate> {
|
|
<#struct_name #ty_generics>::build(scope)
|
|
}
|
|
|
|
fn initialize_template<'s>(
|
|
scope: &mut ::moli_webapi_declare::v8::PinScope<'s, '_, ()>,
|
|
template: ::moli_webapi_declare::v8::Local<'s, ::moli_webapi_declare::v8::FunctionTemplate>,
|
|
) {
|
|
<#struct_name #ty_generics>::initialize_template(scope, template)
|
|
}
|
|
|
|
fn initialize_prototype_template<'s>(
|
|
scope: &mut ::moli_webapi_declare::v8::PinScope<'s, '_, ()>,
|
|
prototype: ::moli_webapi_declare::v8::Local<'s, ::moli_webapi_declare::v8::ObjectTemplate>,
|
|
) {
|
|
<#struct_name #ty_generics>::initialize_prototype_template(scope, prototype)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
pub(crate) fn expand_webapi_object(input: DeriveInput) -> Result<proc_macro2::TokenStream, Error> {
|
|
let struct_name = input.ident;
|
|
let generics = input.generics;
|
|
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
|
let attrs = parse_object_attrs(&input.attrs)?;
|
|
let interface = attrs
|
|
.interface
|
|
.clone()
|
|
.ok_or_else(|| Error::new(struct_name.span(), "missing #[webapi(interface = \"...\")]"))?;
|
|
let has_explicit_prototype = attrs.prototype.is_some();
|
|
let prototype = attrs.prototype.clone().unwrap_or_else(|| interface.clone());
|
|
let own_to_string_tag = match attrs.own_to_string_tag.as_ref() {
|
|
Some(tag) => quote!(::std::option::Option::Some(#tag)),
|
|
None => quote!(::std::option::Option::None),
|
|
};
|
|
let fallback_to_string_tag = match attrs.fallback_to_string_tag.as_ref() {
|
|
Some(tag) => quote!(::std::option::Option::Some(#tag)),
|
|
None => quote!(::std::option::Option::None),
|
|
};
|
|
let define_own_to_string_tag = if attrs.readonly_to_string_tag {
|
|
quote! {
|
|
if let ::std::option::Option::Some(tag) = #own_to_string_tag {
|
|
::moli_webapi_declare::define_to_string_tag_with_attributes(
|
|
scope,
|
|
object,
|
|
tag,
|
|
::moli_webapi_declare::v8::PropertyAttribute::DONT_ENUM
|
|
| ::moli_webapi_declare::v8::PropertyAttribute::READ_ONLY,
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
quote! {
|
|
if let ::std::option::Option::Some(tag) = #own_to_string_tag {
|
|
::moli_webapi_declare::define_to_string_tag(scope, object, tag);
|
|
}
|
|
}
|
|
};
|
|
let set_prototype = if attrs.require_prototype {
|
|
quote! {
|
|
::moli_webapi_declare::set_required_interface_prototype(scope, object, #prototype)?;
|
|
true
|
|
}
|
|
} else {
|
|
quote! {
|
|
::moli_webapi_declare::set_interface_prototype(scope, object, #prototype)
|
|
}
|
|
};
|
|
let define_fallback_to_string_tag = quote! {
|
|
if !prototype_bound {
|
|
if let ::std::option::Option::Some(tag) = #fallback_to_string_tag {
|
|
::moli_webapi_declare::define_to_string_tag(scope, object, tag);
|
|
}
|
|
}
|
|
};
|
|
let fields = named_fields(&input.data)?;
|
|
let generated_constructor = expand_object_generated_constructor(&fields, &attrs, &struct_name)?;
|
|
let inferred_scope_lifetime = attrs
|
|
.scope_lifetime
|
|
.clone()
|
|
.or_else(|| single_lifetime_param(&generics));
|
|
let scope_lifetime = inferred_scope_lifetime.as_ref();
|
|
if let Some(lifetime) = scope_lifetime
|
|
&& !generics.params.iter().any(
|
|
|param| matches!(param, GenericParam::Lifetime(param) if param.lifetime == *lifetime),
|
|
)
|
|
{
|
|
return Err(Error::new(
|
|
lifetime.span(),
|
|
"#[webapi(scope_lifetime = ...)] must name a lifetime parameter on the struct",
|
|
));
|
|
}
|
|
let method_scope_lifetime = scope_lifetime
|
|
.map(|lifetime| quote!(#lifetime))
|
|
.unwrap_or_else(|| quote!('__webapi_scope));
|
|
let method_scope_generic = scope_lifetime.is_none().then(|| quote!(<'__webapi_scope>));
|
|
let fields = fields
|
|
.iter()
|
|
.filter_map(|field| expand_object_field(field, &attrs))
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
if fields.is_empty() && !attrs.allow_empty && !has_explicit_prototype {
|
|
return Err(Error::new(
|
|
struct_name.span(),
|
|
"empty WebApiObject declaration requires #[webapi(allow_empty)]",
|
|
));
|
|
}
|
|
|
|
let initialize_body = quote! {
|
|
#(#fields)*
|
|
::std::result::Result::Ok(())
|
|
};
|
|
let trait_impl = match scope_lifetime {
|
|
Some(lifetime) => {
|
|
quote! {
|
|
impl #impl_generics ::moli_webapi_declare::WebApiObjectDeclaration<#lifetime> for #struct_name #ty_generics #where_clause {
|
|
const INTERFACE: &'static str = #interface;
|
|
const OWN_TO_STRING_TAG: ::std::option::Option<&'static str> = #own_to_string_tag;
|
|
|
|
fn initialize(
|
|
&self,
|
|
scope: &mut ::moli_webapi_declare::v8::PinScope<#lifetime, '_>,
|
|
object: ::moli_webapi_declare::v8::Local<#lifetime, ::moli_webapi_declare::v8::Object>,
|
|
) -> ::std::result::Result<(), ::moli_webapi_declare::BindError> {
|
|
self.initialize(scope, object)
|
|
}
|
|
|
|
fn bind_into(
|
|
&self,
|
|
scope: &mut ::moli_webapi_declare::v8::PinScope<#lifetime, '_>,
|
|
object: ::moli_webapi_declare::v8::Local<#lifetime, ::moli_webapi_declare::v8::Object>,
|
|
) -> ::std::result::Result<(), ::moli_webapi_declare::BindError> {
|
|
self.bind_into(scope, object)
|
|
}
|
|
|
|
fn bind(
|
|
&self,
|
|
scope: &mut ::moli_webapi_declare::v8::PinScope<#lifetime, '_>,
|
|
) -> ::std::result::Result<
|
|
::moli_webapi_declare::v8::Local<#lifetime, ::moli_webapi_declare::v8::Object>,
|
|
::moli_webapi_declare::BindError,
|
|
> {
|
|
self.bind(scope)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
None => {
|
|
let mut trait_generics = generics.clone();
|
|
trait_generics
|
|
.params
|
|
.insert(0, syn::parse_quote!('__webapi_scope));
|
|
let (trait_impl_generics, _, trait_where_clause) = trait_generics.split_for_impl();
|
|
quote! {
|
|
impl #trait_impl_generics ::moli_webapi_declare::WebApiObjectDeclaration<'__webapi_scope> for #struct_name #ty_generics #trait_where_clause {
|
|
const INTERFACE: &'static str = #interface;
|
|
const OWN_TO_STRING_TAG: ::std::option::Option<&'static str> = #own_to_string_tag;
|
|
|
|
fn initialize(
|
|
&self,
|
|
scope: &mut ::moli_webapi_declare::v8::PinScope<'__webapi_scope, '_>,
|
|
object: ::moli_webapi_declare::v8::Local<'__webapi_scope, ::moli_webapi_declare::v8::Object>,
|
|
) -> ::std::result::Result<(), ::moli_webapi_declare::BindError> {
|
|
self.initialize(scope, object)
|
|
}
|
|
|
|
fn bind_into(
|
|
&self,
|
|
scope: &mut ::moli_webapi_declare::v8::PinScope<'__webapi_scope, '_>,
|
|
object: ::moli_webapi_declare::v8::Local<'__webapi_scope, ::moli_webapi_declare::v8::Object>,
|
|
) -> ::std::result::Result<(), ::moli_webapi_declare::BindError> {
|
|
self.bind_into(scope, object)
|
|
}
|
|
|
|
fn bind(
|
|
&self,
|
|
scope: &mut ::moli_webapi_declare::v8::PinScope<'__webapi_scope, '_>,
|
|
) -> ::std::result::Result<
|
|
::moli_webapi_declare::v8::Local<'__webapi_scope, ::moli_webapi_declare::v8::Object>,
|
|
::moli_webapi_declare::BindError,
|
|
> {
|
|
self.bind(scope)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
let value_impl = match scope_lifetime {
|
|
Some(lifetime) => {
|
|
quote! {
|
|
impl #impl_generics ::moli_webapi_declare::WebApiValue<#lifetime> for #struct_name #ty_generics #where_clause {
|
|
fn to_v8_value(
|
|
&self,
|
|
scope: &mut ::moli_webapi_declare::v8::PinScope<#lifetime, '_>,
|
|
) -> ::std::option::Option<::moli_webapi_declare::v8::Local<#lifetime, ::moli_webapi_declare::v8::Value>> {
|
|
self.bind(scope).ok().map(::std::convert::Into::into)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
None => {
|
|
let mut trait_generics = generics.clone();
|
|
trait_generics
|
|
.params
|
|
.insert(0, syn::parse_quote!('__webapi_scope));
|
|
let (trait_impl_generics, _, trait_where_clause) = trait_generics.split_for_impl();
|
|
quote! {
|
|
impl #trait_impl_generics ::moli_webapi_declare::WebApiValue<'__webapi_scope> for #struct_name #ty_generics #trait_where_clause {
|
|
fn to_v8_value(
|
|
&self,
|
|
scope: &mut ::moli_webapi_declare::v8::PinScope<'__webapi_scope, '_>,
|
|
) -> ::std::option::Option<::moli_webapi_declare::v8::Local<'__webapi_scope, ::moli_webapi_declare::v8::Value>> {
|
|
self.bind(scope).ok().map(::std::convert::Into::into)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
Ok(quote! {
|
|
impl #impl_generics #struct_name #ty_generics #where_clause {
|
|
#generated_constructor
|
|
|
|
pub fn initialize #method_scope_generic(
|
|
&self,
|
|
scope: &mut ::moli_webapi_declare::v8::PinScope<#method_scope_lifetime, '_>,
|
|
object: ::moli_webapi_declare::v8::Local<#method_scope_lifetime, ::moli_webapi_declare::v8::Object>,
|
|
) -> ::std::result::Result<(), ::moli_webapi_declare::BindError> {
|
|
#initialize_body
|
|
}
|
|
|
|
pub fn bind_into #method_scope_generic(
|
|
&self,
|
|
scope: &mut ::moli_webapi_declare::v8::PinScope<#method_scope_lifetime, '_>,
|
|
object: ::moli_webapi_declare::v8::Local<#method_scope_lifetime, ::moli_webapi_declare::v8::Object>,
|
|
) -> ::std::result::Result<(), ::moli_webapi_declare::BindError> {
|
|
let prototype_bound = { #set_prototype };
|
|
#define_own_to_string_tag
|
|
#define_fallback_to_string_tag
|
|
self.initialize(scope, object)
|
|
}
|
|
|
|
pub fn bind #method_scope_generic(
|
|
&self,
|
|
scope: &mut ::moli_webapi_declare::v8::PinScope<#method_scope_lifetime, '_>,
|
|
) -> ::std::result::Result<
|
|
::moli_webapi_declare::v8::Local<#method_scope_lifetime, ::moli_webapi_declare::v8::Object>,
|
|
::moli_webapi_declare::BindError,
|
|
> {
|
|
let object = ::moli_webapi_declare::v8::Object::new(scope);
|
|
self.bind_into(scope, object)?;
|
|
::std::result::Result::Ok(object)
|
|
}
|
|
}
|
|
|
|
#trait_impl
|
|
#value_impl
|
|
})
|
|
}
|
|
|
|
fn expand_object_generated_constructor(
|
|
fields: &[Field],
|
|
attrs: &ObjectAttrs,
|
|
struct_name: &Ident,
|
|
) -> Result<proc_macro2::TokenStream, Error> {
|
|
if attrs.no_dynamic_constructor {
|
|
return Ok(quote!());
|
|
}
|
|
let mut parameters = Vec::new();
|
|
let mut initializers = Vec::new();
|
|
for field in fields {
|
|
let ident = field
|
|
.ident
|
|
.as_ref()
|
|
.ok_or_else(|| Error::new(field.span(), "WebApiObject field requires a name"))?;
|
|
let field_attrs = parse_field_attrs(field)?;
|
|
if type_is_unit(&field.ty) {
|
|
initializers.push(quote!(#ident: ()));
|
|
} else if let Some(default) = field_attrs.constructor_default {
|
|
let default = match default {
|
|
ConstructorDefaultAttr::Default => quote!(::std::default::Default::default()),
|
|
ConstructorDefaultAttr::Expr(expr) => quote!(#expr),
|
|
};
|
|
initializers.push(quote!(#ident: #default));
|
|
} else {
|
|
let ty = &field.ty;
|
|
parameters.push(quote!(#ident: #ty));
|
|
initializers.push(quote!(#ident));
|
|
}
|
|
}
|
|
let constructor = Ident::new("new", struct_name.span());
|
|
Ok(quote! {
|
|
pub fn #constructor(#(#parameters),*) -> Self {
|
|
Self {
|
|
#(#initializers),*
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
struct FunctionTemplateFieldExpansions {
|
|
template_methods: Vec<proc_macro2::TokenStream>,
|
|
prototype_methods: Vec<proc_macro2::TokenStream>,
|
|
}
|
|
|
|
fn expand_function_template_fields(
|
|
fields: &[Field],
|
|
template_attrs: &FunctionTemplateAttrs,
|
|
template_name: &LitStr,
|
|
) -> Result<FunctionTemplateFieldExpansions, Error> {
|
|
let mut template_methods = Vec::new();
|
|
let mut prototype_methods = Vec::new();
|
|
let mut method_bindings = HashMap::new();
|
|
for (index, field) in fields.iter().enumerate() {
|
|
let mut attrs = parse_field_attrs(field)?;
|
|
if template_attrs.default_enumerable
|
|
&& attrs.symbol.is_none()
|
|
&& (attrs.method
|
|
|| attrs.static_method
|
|
|| attrs.accessor_property
|
|
|| attrs.native_data_property
|
|
|| attrs.intrinsic_data_property.is_some()
|
|
|| attrs.alias.is_some())
|
|
{
|
|
attrs.enumerable = true;
|
|
}
|
|
if attrs.constant {
|
|
template_methods.push(expand_function_template_constant_field(
|
|
field,
|
|
&attrs,
|
|
template_attrs.rename_all,
|
|
quote!(template),
|
|
)?);
|
|
prototype_methods.push(expand_function_template_constant_field(
|
|
field,
|
|
&attrs,
|
|
template_attrs.rename_all,
|
|
quote!(prototype),
|
|
)?);
|
|
continue;
|
|
}
|
|
if attrs.static_method {
|
|
let binding = format_ident!("__webapi_template_static_method_{index}");
|
|
template_methods.push(expand_function_template_static_method_field(
|
|
field,
|
|
&attrs,
|
|
template_attrs.rename_all,
|
|
template_name,
|
|
binding,
|
|
)?);
|
|
continue;
|
|
}
|
|
if attrs.method {
|
|
let binding = format_ident!("__webapi_template_method_{index}");
|
|
if attrs.symbol.is_none()
|
|
&& let Some(name) = webapi_field_name_literal(
|
|
field,
|
|
attrs.name.as_ref(),
|
|
template_attrs.rename_all,
|
|
)?
|
|
{
|
|
method_bindings.insert(name.value(), binding.clone());
|
|
}
|
|
prototype_methods.push(expand_function_template_method_field(
|
|
field,
|
|
&attrs,
|
|
template_attrs.rename_all,
|
|
template_name,
|
|
binding,
|
|
)?);
|
|
continue;
|
|
}
|
|
if attrs.accessor_property {
|
|
prototype_methods.push(expand_function_template_accessor_property_field(
|
|
field,
|
|
&attrs,
|
|
template_attrs.rename_all,
|
|
template_name,
|
|
)?);
|
|
continue;
|
|
}
|
|
if attrs.native_data_property {
|
|
prototype_methods.push(expand_function_template_native_data_property_field(
|
|
field,
|
|
&attrs,
|
|
template_attrs.rename_all,
|
|
)?);
|
|
continue;
|
|
}
|
|
if attrs.intrinsic_data_property.is_some() {
|
|
prototype_methods.push(expand_function_template_intrinsic_data_property_field(
|
|
field,
|
|
&attrs,
|
|
template_attrs.rename_all,
|
|
)?);
|
|
continue;
|
|
}
|
|
if attrs.alias.is_some() {
|
|
prototype_methods.push(expand_function_template_alias_field(
|
|
field,
|
|
&attrs,
|
|
template_attrs.rename_all,
|
|
&method_bindings,
|
|
)?);
|
|
continue;
|
|
}
|
|
if attrs.has_installation_kind() || attrs.has_installation_attribute() {
|
|
return Err(Error::new(
|
|
field.span(),
|
|
"function template fields with #[webapi(...)] attributes must declare #[webapi(method)], #[webapi(static_method)], #[webapi(accessor_property)], #[webapi(native_data_property)], #[webapi(intrinsic_data_property = ...)], #[webapi(constant)], or #[webapi(alias = ...)]",
|
|
));
|
|
}
|
|
}
|
|
Ok(FunctionTemplateFieldExpansions {
|
|
template_methods,
|
|
prototype_methods,
|
|
})
|
|
}
|
|
|
|
fn expand_function_template_constant_field(
|
|
field: &Field,
|
|
attrs: &crate::attrs::FieldAttrs,
|
|
rename_all: RenameRule,
|
|
template: proc_macro2::TokenStream,
|
|
) -> Result<proc_macro2::TokenStream, Error> {
|
|
let key = webapi_field_key(field, attrs, rename_all)?;
|
|
let name = key.display_name;
|
|
let property_key = key.property_key;
|
|
let Some(value) = attrs.value.as_ref() else {
|
|
return Err(Error::new(
|
|
field.span(),
|
|
"constant field requires #[webapi(value = expr)]",
|
|
));
|
|
};
|
|
let attributes = constant_property_attributes();
|
|
let field_read = field
|
|
.ident
|
|
.as_ref()
|
|
.map(|ident| quote!(let _ = ::std::stringify!(#ident);));
|
|
Ok(quote! {
|
|
#field_read
|
|
let __webapi_constant_value = (#value);
|
|
let __webapi_constant_v8_value =
|
|
::moli_webapi_declare::WebApiTemplateValue::to_v8_template_value(
|
|
&__webapi_constant_value,
|
|
scope,
|
|
)
|
|
.expect("failed to convert Web API constant value");
|
|
#template.set_with_attr(
|
|
#property_key,
|
|
__webapi_constant_v8_value.into(),
|
|
#attributes,
|
|
);
|
|
let _ = #name;
|
|
})
|
|
}
|
|
|
|
fn expand_function_template_static_method_field(
|
|
field: &Field,
|
|
attrs: &crate::attrs::FieldAttrs,
|
|
rename_all: RenameRule,
|
|
template_name: &LitStr,
|
|
binding: syn::Ident,
|
|
) -> Result<proc_macro2::TokenStream, Error> {
|
|
let key = webapi_field_key(field, attrs, rename_all)?;
|
|
let name = key.display_name;
|
|
let property_key = key.property_key;
|
|
let set_function_name = key.function_name.map(|function_name| {
|
|
quote! {
|
|
#binding.set_class_name(#function_name);
|
|
}
|
|
});
|
|
let Some(callback) = attrs.callback.as_ref() else {
|
|
return Err(Error::new(
|
|
field.span(),
|
|
"static method field requires #[webapi(callback = path)]",
|
|
));
|
|
};
|
|
let length = attrs.length.unwrap_or(0);
|
|
let attributes = template_property_attributes(attrs);
|
|
let build_function = expand_template_function_builder(
|
|
callback,
|
|
length,
|
|
attrs.data.as_ref(),
|
|
template_name,
|
|
&name,
|
|
);
|
|
let field_read = field
|
|
.ident
|
|
.as_ref()
|
|
.map(|ident| quote!(let _ = ::std::stringify!(#ident);));
|
|
Ok(quote! {
|
|
#field_read
|
|
let #binding = #build_function;
|
|
#set_function_name
|
|
template.set_with_attr(
|
|
#property_key,
|
|
#binding.into(),
|
|
#attributes,
|
|
);
|
|
let _ = #name;
|
|
})
|
|
}
|
|
|
|
fn expand_function_template_method_field(
|
|
field: &Field,
|
|
attrs: &crate::attrs::FieldAttrs,
|
|
rename_all: RenameRule,
|
|
template_name: &LitStr,
|
|
binding: syn::Ident,
|
|
) -> Result<proc_macro2::TokenStream, Error> {
|
|
let key = webapi_field_key(field, attrs, rename_all)?;
|
|
let name = key.display_name;
|
|
let property_key = key.property_key;
|
|
let set_function_name = key.function_name.map(|function_name| {
|
|
quote! {
|
|
#binding.set_class_name(#function_name);
|
|
}
|
|
});
|
|
let Some(callback) = attrs.callback.as_ref() else {
|
|
return Err(Error::new(
|
|
field.span(),
|
|
"method field requires #[webapi(callback = path)]",
|
|
));
|
|
};
|
|
let length = attrs.length.unwrap_or(0);
|
|
let attributes = template_property_attributes(attrs);
|
|
let build_function = expand_template_function_builder(
|
|
callback,
|
|
length,
|
|
attrs.data.as_ref(),
|
|
template_name,
|
|
&name,
|
|
);
|
|
let field_read = field
|
|
.ident
|
|
.as_ref()
|
|
.map(|ident| quote!(let _ = ::std::stringify!(#ident);));
|
|
Ok(quote! {
|
|
#field_read
|
|
let #binding = #build_function;
|
|
#set_function_name
|
|
prototype.set_with_attr(
|
|
#property_key,
|
|
#binding.into(),
|
|
#attributes,
|
|
);
|
|
let _ = #name;
|
|
})
|
|
}
|
|
|
|
fn expand_function_template_accessor_property_field(
|
|
field: &Field,
|
|
attrs: &crate::attrs::FieldAttrs,
|
|
rename_all: RenameRule,
|
|
template_name: &LitStr,
|
|
) -> Result<proc_macro2::TokenStream, Error> {
|
|
if attrs.readonly {
|
|
return Err(Error::new(
|
|
field.span(),
|
|
"function-template `accessor_property` fields have no writable attribute; omit #[webapi(setter)] instead of using readonly",
|
|
));
|
|
}
|
|
let key = webapi_field_key(field, attrs, rename_all)?;
|
|
let name = key.display_name;
|
|
let property_key = key.property_key;
|
|
let Some(getter) = attrs.getter.as_ref() else {
|
|
return Err(Error::new(
|
|
field.span(),
|
|
"`accessor_property` field requires #[webapi(getter = path)]",
|
|
));
|
|
};
|
|
let getter_builder = expand_template_accessor_function_builder(
|
|
getter,
|
|
0,
|
|
attrs.data.as_ref(),
|
|
template_name,
|
|
&name,
|
|
);
|
|
let setter = attrs.setter.as_ref().map(|setter| {
|
|
let setter_data = attrs.setter_data.as_ref().or(attrs.data.as_ref());
|
|
let setter_builder =
|
|
expand_template_accessor_function_builder(setter, 1, setter_data, template_name, &name);
|
|
quote! {
|
|
{
|
|
let __webapi_setter = #setter_builder;
|
|
let __webapi_setter_name =
|
|
::std::format!("set {}", #name);
|
|
if let ::std::option::Option::Some(__webapi_setter_name) =
|
|
::moli_webapi_declare::v8::String::new(
|
|
scope,
|
|
&__webapi_setter_name,
|
|
)
|
|
{
|
|
__webapi_setter.set_class_name(__webapi_setter_name);
|
|
}
|
|
::std::option::Option::Some(__webapi_setter)
|
|
}
|
|
}
|
|
});
|
|
let setter = setter.unwrap_or_else(|| quote!(::std::option::Option::None));
|
|
let attributes = accessor_property_attributes(attrs);
|
|
let field_read = field
|
|
.ident
|
|
.as_ref()
|
|
.map(|ident| quote!(let _ = ::std::stringify!(#ident);));
|
|
Ok(quote! {
|
|
#field_read
|
|
let __webapi_getter = #getter_builder;
|
|
let __webapi_getter_name = ::std::format!("get {}", #name);
|
|
if let ::std::option::Option::Some(__webapi_getter_name) =
|
|
::moli_webapi_declare::v8::String::new(
|
|
scope,
|
|
&__webapi_getter_name,
|
|
)
|
|
{
|
|
__webapi_getter.set_class_name(__webapi_getter_name);
|
|
}
|
|
let __webapi_setter = #setter;
|
|
prototype.set_accessor_property(
|
|
#property_key,
|
|
::std::option::Option::Some(__webapi_getter),
|
|
__webapi_setter,
|
|
#attributes,
|
|
);
|
|
let _ = #name;
|
|
})
|
|
}
|
|
|
|
fn expand_function_template_native_data_property_field(
|
|
field: &Field,
|
|
attrs: &crate::attrs::FieldAttrs,
|
|
rename_all: RenameRule,
|
|
) -> Result<proc_macro2::TokenStream, Error> {
|
|
if attrs.readonly {
|
|
return Err(Error::new(
|
|
field.span(),
|
|
"function-template `native_data_property` fields have no writable attribute; omit #[webapi(setter)] instead of using readonly",
|
|
));
|
|
}
|
|
if attrs.setter_data.is_some() {
|
|
return Err(Error::new(
|
|
field.span(),
|
|
"function-template `native_data_property` fields share one callback data value and cannot use #[webapi(setter_data = ...)]",
|
|
));
|
|
}
|
|
let key = webapi_field_key(field, attrs, rename_all)?;
|
|
let name = key.display_name;
|
|
let property_key = key.property_key;
|
|
let Some(getter) = attrs.getter.as_ref() else {
|
|
return Err(Error::new(
|
|
field.span(),
|
|
"`native_data_property` field requires #[webapi(getter = path)]",
|
|
));
|
|
};
|
|
let setter = attrs.setter.as_ref().map(|setter| {
|
|
quote! {
|
|
.setter(#setter)
|
|
}
|
|
});
|
|
let data = attrs.data.as_ref().map(|data| {
|
|
quote! {
|
|
.data((#data).into())
|
|
}
|
|
});
|
|
let attributes = accessor_property_attributes(attrs);
|
|
let field_read = field
|
|
.ident
|
|
.as_ref()
|
|
.map(|ident| quote!(let _ = ::std::stringify!(#ident);));
|
|
Ok(quote! {
|
|
#field_read
|
|
let __webapi_native_data_property_configuration =
|
|
::moli_webapi_declare::v8::NativeDataPropertyConfiguration::new(#getter)
|
|
#setter
|
|
#data
|
|
.property_attribute(#attributes);
|
|
prototype.set_native_data_property_with_configuration(
|
|
#property_key,
|
|
__webapi_native_data_property_configuration,
|
|
);
|
|
let _ = #name;
|
|
})
|
|
}
|
|
|
|
fn expand_function_template_intrinsic_data_property_field(
|
|
field: &Field,
|
|
attrs: &crate::attrs::FieldAttrs,
|
|
rename_all: RenameRule,
|
|
) -> Result<proc_macro2::TokenStream, Error> {
|
|
let key = webapi_field_key(field, attrs, rename_all)?;
|
|
let name = key.display_name;
|
|
let property_key = key.property_key;
|
|
let Some(intrinsic) = attrs.intrinsic_data_property.as_ref() else {
|
|
return Err(Error::new(
|
|
field.span(),
|
|
"function template intrinsic data property requires an intrinsic value",
|
|
));
|
|
};
|
|
let attributes = template_property_attributes(attrs);
|
|
let field_read = field
|
|
.ident
|
|
.as_ref()
|
|
.map(|ident| quote!(let _ = ::std::stringify!(#ident);));
|
|
Ok(quote! {
|
|
#field_read
|
|
prototype.set_intrinsic_data_property(
|
|
#property_key,
|
|
#intrinsic,
|
|
#attributes,
|
|
);
|
|
let _ = #name;
|
|
})
|
|
}
|
|
|
|
fn expand_function_template_alias_field(
|
|
field: &Field,
|
|
attrs: &crate::attrs::FieldAttrs,
|
|
rename_all: RenameRule,
|
|
method_bindings: &HashMap<String, syn::Ident>,
|
|
) -> Result<proc_macro2::TokenStream, Error> {
|
|
if attrs.callback.is_some()
|
|
|| attrs.getter.is_some()
|
|
|| attrs.setter.is_some()
|
|
|| attrs.function_name.is_some()
|
|
|| attrs.length.is_some()
|
|
|| attrs.data.is_some()
|
|
|| attrs.value.is_some()
|
|
|| attrs.init.is_some()
|
|
{
|
|
return Err(Error::new(
|
|
field.span(),
|
|
"function template alias fields cannot define callbacks, function metadata, data, or values",
|
|
));
|
|
}
|
|
let Some(source_name) = attrs.alias.as_ref() else {
|
|
return Err(Error::new(
|
|
field.span(),
|
|
"function template alias field requires a source name",
|
|
));
|
|
};
|
|
let Some(source_binding) = method_bindings.get(&source_name.value()) else {
|
|
return Err(Error::new(
|
|
source_name.span(),
|
|
"function template alias source must refer to an earlier string-named method",
|
|
));
|
|
};
|
|
let key = webapi_field_key(field, attrs, rename_all)?;
|
|
let name = key.display_name;
|
|
let property_key = key.property_key;
|
|
let attributes = template_property_attributes(attrs);
|
|
let field_read = field
|
|
.ident
|
|
.as_ref()
|
|
.map(|ident| quote!(let _ = ::std::stringify!(#ident);));
|
|
Ok(quote! {
|
|
#field_read
|
|
prototype.set_with_attr(
|
|
#property_key,
|
|
#source_binding.into(),
|
|
#attributes,
|
|
);
|
|
let _ = #name;
|
|
})
|
|
}
|
|
|
|
fn expand_interface_field(
|
|
field: &Field,
|
|
rename_all: RenameRule,
|
|
) -> Option<Result<proc_macro2::TokenStream, Error>> {
|
|
let attrs = match parse_field_attrs(field) {
|
|
Ok(attrs) => attrs,
|
|
Err(error) => return Some(Err(error)),
|
|
};
|
|
if !attrs.method && !attrs.accessor_property {
|
|
if attrs.has_installation_kind() || attrs.has_installation_attribute() {
|
|
return Some(Err(Error::new(
|
|
field.span(),
|
|
"interface fields with #[webapi(...)] attributes must declare #[webapi(method)] or #[webapi(accessor_property)]",
|
|
)));
|
|
}
|
|
return None;
|
|
}
|
|
if attrs.method {
|
|
let key = match webapi_field_key(field, &attrs, rename_all) {
|
|
Ok(key) => key,
|
|
Err(error) => return Some(Err(error)),
|
|
};
|
|
let name = key.display_name;
|
|
let property_key = key.property_key;
|
|
let set_function_name = key.function_name.map(|function_name| {
|
|
quote! {
|
|
function.set_name(#function_name);
|
|
}
|
|
});
|
|
let Some(callback) = attrs.callback.as_ref() else {
|
|
return Some(Err(Error::new(
|
|
field.span(),
|
|
"method field requires #[webapi(callback = path)]",
|
|
)));
|
|
};
|
|
let length = attrs.length.unwrap_or(0);
|
|
let enumerable = attrs.enumerable;
|
|
let writable = !attrs.readonly;
|
|
let configurable = !attrs.dont_delete;
|
|
let build_function =
|
|
expand_method_function_builder(callback, length, attrs.data.as_ref(), &name);
|
|
let field_read = field
|
|
.ident
|
|
.as_ref()
|
|
.map(|ident| quote!(let _ = &self.#ident;));
|
|
return Some(Ok(quote! {
|
|
#field_read
|
|
let function = #build_function.ok_or_else(|| {
|
|
::moli_webapi_declare::BindError::new(
|
|
::std::format!("failed to build `{}.{}` function", INTERFACE_NAME, #name)
|
|
)
|
|
})?;
|
|
#set_function_name
|
|
let mut descriptor =
|
|
::moli_webapi_declare::v8::PropertyDescriptor::new_from_value_writable(
|
|
function.into(),
|
|
#writable,
|
|
);
|
|
descriptor.set_configurable(#configurable);
|
|
descriptor.set_enumerable(#enumerable);
|
|
prototype
|
|
.define_property(
|
|
scope,
|
|
#property_key,
|
|
&descriptor,
|
|
)
|
|
.unwrap_or(false)
|
|
.then_some(())
|
|
.ok_or_else(|| {
|
|
::moli_webapi_declare::BindError::new(
|
|
::std::format!("failed to define `{}.{}` function", INTERFACE_NAME, #name)
|
|
)
|
|
})?;
|
|
}));
|
|
}
|
|
if attrs.accessor_property {
|
|
return Some(expand_accessor_property_field(
|
|
field,
|
|
&attrs,
|
|
rename_all,
|
|
quote!(prototype),
|
|
));
|
|
}
|
|
None
|
|
}
|
|
|
|
fn single_lifetime_param(generics: &syn::Generics) -> Option<syn::Lifetime> {
|
|
// Most object declarations carry exactly one lifetime, and that lifetime is
|
|
// the V8 scope lifetime used by `v8::Local<'scope, T>` fields. In that common
|
|
// case the derive can infer the generated `WebApiObjectDeclaration<'scope>`
|
|
// impl. Declarations with multiple lifetimes stay explicit so data lifetimes
|
|
// such as `&'tag str` are not accidentally treated as the V8 scope.
|
|
let mut lifetimes = generics.params.iter().filter_map(|param| match param {
|
|
GenericParam::Lifetime(param) => Some(param.lifetime.clone()),
|
|
_ => None,
|
|
});
|
|
let lifetime = lifetimes.next()?;
|
|
lifetimes.next().is_none().then_some(lifetime)
|
|
}
|
|
|
|
fn expand_object_field(
|
|
field: &Field,
|
|
object_attrs: &ObjectAttrs,
|
|
) -> Option<Result<proc_macro2::TokenStream, Error>> {
|
|
let mut attrs = match parse_field_attrs(field) {
|
|
Ok(attrs) => attrs,
|
|
Err(error) => return Some(Err(error)),
|
|
};
|
|
// Struct-level `#[webapi(data_properties)]` is the only mode where an unannotated
|
|
// field becomes part of the JavaScript surface by default. Without it,
|
|
// unannotated fields are declaration-only inputs that can still be consumed
|
|
// by other attributes, for example `#[webapi(method, data = self.cache)]`.
|
|
if object_attrs.default_data_properties
|
|
&& !attrs.method
|
|
&& !attrs.static_method
|
|
&& !attrs.constant
|
|
&& !attrs.accessor_property
|
|
&& !attrs.native_data_property
|
|
&& attrs.intrinsic_data_property.is_none()
|
|
&& !attrs.data_property
|
|
&& attrs.alias.is_none()
|
|
&& !attrs.hidden
|
|
&& !attrs.slot
|
|
&& !attrs.prototype
|
|
&& !attrs.to_string_tag
|
|
{
|
|
attrs.data_property = true;
|
|
}
|
|
if object_attrs.default_enumerable
|
|
&& attrs.symbol.is_none()
|
|
&& (attrs.data_property
|
|
|| attrs.method
|
|
|| attrs.accessor_property
|
|
|| attrs.native_data_property
|
|
|| attrs.alias.is_some())
|
|
{
|
|
attrs.enumerable = true;
|
|
}
|
|
if !attrs.has_installation_kind() {
|
|
if attrs.has_installation_attribute() {
|
|
return Some(Err(Error::new(
|
|
field.span(),
|
|
"declaration-only fields cannot use #[webapi(...)] installation attributes; add an installation kind or remove the attributes",
|
|
)));
|
|
}
|
|
return None;
|
|
}
|
|
if attrs.static_method {
|
|
return Some(Err(Error::new(
|
|
field.span(),
|
|
"object fields with #[webapi(...)] attributes cannot declare #[webapi(static_method)]",
|
|
)));
|
|
}
|
|
if attrs.intrinsic_data_property.is_some() {
|
|
return Some(Err(Error::new(
|
|
field.span(),
|
|
"`intrinsic_data_property` is only supported by WebApiFunctionTemplate",
|
|
)));
|
|
}
|
|
if attrs.constant {
|
|
let name = match webapi_field_name(field, attrs.name.as_ref(), object_attrs.rename_all) {
|
|
Ok(name) => name,
|
|
Err(error) => return Some(Err(error)),
|
|
};
|
|
let value = match expand_object_field_value(field, &attrs, false) {
|
|
Ok(value) => value,
|
|
Err(error) => return Some(Err(error)),
|
|
};
|
|
return Some(Ok(quote! {
|
|
#value
|
|
::moli_webapi_declare::define_declared_constant_property(
|
|
scope,
|
|
object,
|
|
#name,
|
|
__webapi_value_ref,
|
|
)?;
|
|
}));
|
|
}
|
|
if attrs.method {
|
|
return Some(expand_object_method_field(
|
|
field,
|
|
&attrs,
|
|
object_attrs.rename_all,
|
|
));
|
|
}
|
|
if attrs.accessor_property {
|
|
return Some(expand_accessor_property_field(
|
|
field,
|
|
&attrs,
|
|
object_attrs.rename_all,
|
|
quote!(object),
|
|
));
|
|
}
|
|
if attrs.native_data_property {
|
|
return Some(expand_native_data_property_field(
|
|
field,
|
|
&attrs,
|
|
object_attrs.rename_all,
|
|
));
|
|
}
|
|
if attrs.alias.is_some() {
|
|
return Some(expand_alias_field(field, &attrs, object_attrs.rename_all));
|
|
}
|
|
if attrs.prototype || attrs.to_string_tag {
|
|
let field_is_option = type_is_option(&field.ty);
|
|
let optional = field_is_option && attrs.value.is_none() && attrs.init.is_none();
|
|
let value = match expand_object_field_value(field, &attrs, optional) {
|
|
Ok(value) => value,
|
|
Err(error) => return Some(Err(error)),
|
|
};
|
|
let bind_value = if attrs.prototype {
|
|
quote! {
|
|
::moli_webapi_declare::set_declared_prototype(
|
|
scope,
|
|
object,
|
|
__webapi_value_ref,
|
|
)?;
|
|
}
|
|
} else {
|
|
let attributes = to_string_tag_property_attributes(&attrs);
|
|
quote! {
|
|
::moli_webapi_declare::define_declared_to_string_tag_with_attributes(
|
|
scope,
|
|
object,
|
|
__webapi_value_ref,
|
|
#attributes,
|
|
)?;
|
|
}
|
|
};
|
|
if optional {
|
|
return Some(Ok(quote! {
|
|
if let ::std::option::Option::Some(__webapi_value_ref) = #value {
|
|
#bind_value
|
|
}
|
|
}));
|
|
}
|
|
return Some(Ok(quote! {
|
|
#value
|
|
#bind_value
|
|
}));
|
|
}
|
|
// No installation kind means the field is intentionally not written to the
|
|
// object. This allows declarations to carry Rust/V8 values used only while
|
|
// generating methods or metadata. Keeping the skip here prevents accidental
|
|
// own-data-property exposure on web-facing wrappers.
|
|
if !attrs.data_property && !attrs.hidden && !attrs.slot {
|
|
return None;
|
|
}
|
|
let rename_all = if attrs.hidden || attrs.slot {
|
|
RenameRule::None
|
|
} else {
|
|
object_attrs.rename_all
|
|
};
|
|
let name = match webapi_field_name(field, attrs.name.as_ref(), rename_all) {
|
|
Ok(name) => name,
|
|
Err(error) => return Some(Err(error)),
|
|
};
|
|
let field_is_option = type_is_option(&field.ty);
|
|
let optional = field_is_option && attrs.value.is_none() && attrs.init.is_none();
|
|
let value = match expand_object_field_value(field, &attrs, optional) {
|
|
Ok(value) => value,
|
|
Err(error) => return Some(Err(error)),
|
|
};
|
|
let define_value = if attrs.slot {
|
|
quote! {
|
|
::moli_webapi_declare::define_declared_private_slot(
|
|
scope,
|
|
object,
|
|
#name,
|
|
__webapi_value_ref,
|
|
)?;
|
|
}
|
|
} else if attrs.hidden {
|
|
let writable = syn::LitBool::new(!attrs.readonly, proc_macro2::Span::call_site());
|
|
let configurable = syn::LitBool::new(!attrs.dont_delete, proc_macro2::Span::call_site());
|
|
quote! {
|
|
::moli_webapi_declare::define_declared_hidden_property_with_descriptor(
|
|
scope,
|
|
object,
|
|
#name,
|
|
__webapi_value_ref,
|
|
#writable,
|
|
#configurable,
|
|
)?;
|
|
}
|
|
} else if attrs.readonly || attrs.dont_delete {
|
|
let mut property_attributes = if attrs.enumerable {
|
|
quote!(::moli_webapi_declare::v8::PropertyAttribute::NONE)
|
|
} else {
|
|
quote!(::moli_webapi_declare::v8::PropertyAttribute::DONT_ENUM)
|
|
};
|
|
if attrs.readonly {
|
|
property_attributes = quote! {
|
|
#property_attributes
|
|
| ::moli_webapi_declare::v8::PropertyAttribute::READ_ONLY
|
|
};
|
|
}
|
|
if attrs.dont_delete {
|
|
property_attributes = quote! {
|
|
#property_attributes
|
|
| ::moli_webapi_declare::v8::PropertyAttribute::DONT_DELETE
|
|
};
|
|
}
|
|
quote! {
|
|
::moli_webapi_declare::define_declared_data_property_with_attributes(
|
|
scope,
|
|
object,
|
|
#name,
|
|
__webapi_value_ref,
|
|
#property_attributes,
|
|
)?;
|
|
}
|
|
} else if attrs.enumerable {
|
|
quote! {
|
|
::moli_webapi_declare::define_declared_enumerable_data_property(
|
|
scope,
|
|
object,
|
|
#name,
|
|
__webapi_value_ref,
|
|
)?;
|
|
}
|
|
} else {
|
|
quote! {
|
|
::moli_webapi_declare::define_declared_data_property(
|
|
scope,
|
|
object,
|
|
#name,
|
|
__webapi_value_ref,
|
|
)?;
|
|
}
|
|
};
|
|
if optional {
|
|
return Some(Ok(quote! {
|
|
if let ::std::option::Option::Some(__webapi_value_ref) = #value {
|
|
#define_value
|
|
}
|
|
}));
|
|
}
|
|
if attrs.hidden {
|
|
return Some(Ok(quote! {
|
|
#value
|
|
#define_value
|
|
}));
|
|
}
|
|
if attrs.slot {
|
|
return Some(Ok(quote! {
|
|
#value
|
|
::moli_webapi_declare::define_declared_private_slot(
|
|
scope,
|
|
object,
|
|
#name,
|
|
__webapi_value_ref,
|
|
)?;
|
|
}));
|
|
}
|
|
Some(Ok(quote! {
|
|
#value
|
|
#define_value
|
|
}))
|
|
}
|
|
|
|
fn expand_accessor_property_field(
|
|
field: &Field,
|
|
attrs: &crate::attrs::FieldAttrs,
|
|
rename_all: RenameRule,
|
|
object: proc_macro2::TokenStream,
|
|
) -> Result<proc_macro2::TokenStream, Error> {
|
|
if attrs.readonly {
|
|
return Err(Error::new(
|
|
field.span(),
|
|
"runtime-object `accessor_property` fields have no writable attribute; omit #[webapi(setter)] instead of using readonly",
|
|
));
|
|
}
|
|
let key = webapi_field_key(field, attrs, rename_all)?;
|
|
let name = key.display_name;
|
|
let property_key = key.property_key;
|
|
if attrs.getter.is_some() && attrs.getter_value.is_some() {
|
|
return Err(Error::new(
|
|
field.span(),
|
|
"`accessor_property` field cannot declare both #[webapi(getter = ...)] and #[webapi(getter_value = ...)]",
|
|
));
|
|
}
|
|
let getter = if let Some(getter) = attrs.getter.as_ref() {
|
|
let getter = expand_accessor_function_builder(getter, 0, attrs.data.as_ref(), &name);
|
|
quote! {
|
|
#getter.ok_or_else(|| {
|
|
::moli_webapi_declare::BindError::new(
|
|
::std::format!("failed to build declared `{}` getter", #name)
|
|
)
|
|
})?
|
|
}
|
|
} else if let Some(getter_value) = attrs.getter_value.as_ref() {
|
|
quote!(#getter_value)
|
|
} else {
|
|
return Err(Error::new(
|
|
field.span(),
|
|
"`accessor_property` field requires #[webapi(getter = path)] or #[webapi(getter_value = expr)]",
|
|
));
|
|
};
|
|
let setter = attrs.setter.as_ref().map(|setter| {
|
|
let setter_data = attrs.setter_data.as_ref().or(attrs.data.as_ref());
|
|
expand_accessor_function_builder(setter, 1, setter_data, &name)
|
|
});
|
|
let setter = match setter {
|
|
Some(setter) => quote! {
|
|
::std::option::Option::Some(
|
|
#setter.ok_or_else(|| {
|
|
::moli_webapi_declare::BindError::new(
|
|
::std::format!("failed to build declared `{}` setter", #name)
|
|
)
|
|
})?
|
|
)
|
|
},
|
|
None => quote!(::std::option::Option::None),
|
|
};
|
|
let attributes = accessor_property_attributes(attrs);
|
|
let field_read = field
|
|
.ident
|
|
.as_ref()
|
|
.map(|ident| quote!(let _ = &self.#ident;));
|
|
Ok(quote! {
|
|
#field_read
|
|
let __webapi_getter = #getter;
|
|
let __webapi_setter = #setter;
|
|
::moli_webapi_declare::define_declared_accessor_property_by_key(
|
|
scope,
|
|
#object,
|
|
#property_key,
|
|
#name,
|
|
__webapi_getter,
|
|
__webapi_setter,
|
|
#attributes,
|
|
)?;
|
|
})
|
|
}
|
|
|
|
fn expand_native_data_property_field(
|
|
field: &Field,
|
|
attrs: &crate::attrs::FieldAttrs,
|
|
rename_all: RenameRule,
|
|
) -> Result<proc_macro2::TokenStream, Error> {
|
|
if attrs.readonly {
|
|
return Err(Error::new(
|
|
field.span(),
|
|
"`native_data_property` fields have no writable attribute; omit #[webapi(setter)] instead of using readonly",
|
|
));
|
|
}
|
|
let key = webapi_field_key(field, attrs, rename_all)?;
|
|
let name = key.display_name;
|
|
let property_key = key.property_key;
|
|
let Some(getter) = attrs.getter.as_ref() else {
|
|
return Err(Error::new(
|
|
field.span(),
|
|
"`native_data_property` field requires #[webapi(getter = path)]",
|
|
));
|
|
};
|
|
let setter = attrs.setter.as_ref().map(|setter| {
|
|
quote! {
|
|
.setter(#setter)
|
|
}
|
|
});
|
|
let data = attrs.data.as_ref().map(|data| {
|
|
quote! {
|
|
.data((#data).into())
|
|
}
|
|
});
|
|
let attributes = accessor_property_attributes(attrs);
|
|
let field_read = field
|
|
.ident
|
|
.as_ref()
|
|
.map(|ident| quote!(let _ = &self.#ident;));
|
|
Ok(quote! {
|
|
#field_read
|
|
let __webapi_native_data_property_configuration =
|
|
::moli_webapi_declare::v8::NativeDataPropertyConfiguration::new(#getter)
|
|
#setter
|
|
#data
|
|
.property_attribute(#attributes);
|
|
object
|
|
.set_native_data_property_with_configuration(
|
|
scope,
|
|
#property_key,
|
|
__webapi_native_data_property_configuration,
|
|
)
|
|
.unwrap_or(false)
|
|
.then_some(())
|
|
.ok_or_else(|| {
|
|
::moli_webapi_declare::BindError::new(
|
|
::std::format!("failed to define declared `{}` native data property", #name)
|
|
)
|
|
})?;
|
|
let _ = #name;
|
|
})
|
|
}
|
|
|
|
fn expand_object_method_field(
|
|
field: &Field,
|
|
attrs: &crate::attrs::FieldAttrs,
|
|
rename_all: RenameRule,
|
|
) -> Result<proc_macro2::TokenStream, Error> {
|
|
let key = webapi_field_key(field, attrs, rename_all)?;
|
|
let name = key.display_name;
|
|
let property_key = key.property_key;
|
|
let set_function_name = key.function_name.map(|function_name| {
|
|
quote! {
|
|
function.set_name(#function_name);
|
|
}
|
|
});
|
|
let Some(callback) = attrs.callback.as_ref() else {
|
|
return Err(Error::new(
|
|
field.span(),
|
|
"method field requires #[webapi(callback = path)]",
|
|
));
|
|
};
|
|
let length = attrs.length.unwrap_or(0);
|
|
let enumerable = attrs.enumerable;
|
|
let writable = !attrs.readonly;
|
|
let configurable = !attrs.dont_delete;
|
|
let build_function =
|
|
expand_method_function_builder(callback, length, attrs.data.as_ref(), &name);
|
|
let install_method = quote! {
|
|
let function = #build_function.ok_or_else(|| {
|
|
::moli_webapi_declare::BindError::new(
|
|
::std::format!("failed to build declared `{}` method", #name)
|
|
)
|
|
})?;
|
|
#set_function_name
|
|
let mut descriptor =
|
|
::moli_webapi_declare::v8::PropertyDescriptor::new_from_value_writable(
|
|
function.into(),
|
|
#writable,
|
|
);
|
|
descriptor.set_configurable(#configurable);
|
|
descriptor.set_enumerable(#enumerable);
|
|
object
|
|
.define_property(
|
|
scope,
|
|
#property_key,
|
|
&descriptor,
|
|
)
|
|
.unwrap_or(false)
|
|
.then_some(())
|
|
.ok_or_else(|| {
|
|
::moli_webapi_declare::BindError::new(
|
|
::std::format!("failed to define declared `{}` method", #name)
|
|
)
|
|
})?;
|
|
};
|
|
let Some(ident) = field.ident.as_ref() else {
|
|
return Ok(quote! {
|
|
#install_method
|
|
});
|
|
};
|
|
if type_is_option(&field.ty) {
|
|
return Ok(quote! {
|
|
if self.#ident.is_some() {
|
|
#install_method
|
|
}
|
|
});
|
|
}
|
|
Ok(quote! {
|
|
let _ = &self.#ident;
|
|
#install_method
|
|
})
|
|
}
|
|
|
|
fn expand_alias_field(
|
|
field: &Field,
|
|
attrs: &crate::attrs::FieldAttrs,
|
|
rename_all: RenameRule,
|
|
) -> Result<proc_macro2::TokenStream, Error> {
|
|
let key = webapi_field_key(field, attrs, rename_all)?;
|
|
let name = key.display_name;
|
|
let property_key = key.property_key;
|
|
let Some(source_name) = attrs.alias.as_ref() else {
|
|
return Err(Error::new(
|
|
field.span(),
|
|
"alias field requires a source name",
|
|
));
|
|
};
|
|
let enumerable = syn::LitBool::new(attrs.enumerable, proc_macro2::Span::call_site());
|
|
let writable = syn::LitBool::new(!attrs.readonly, proc_macro2::Span::call_site());
|
|
let configurable = syn::LitBool::new(!attrs.dont_delete, proc_macro2::Span::call_site());
|
|
let field_read = field
|
|
.ident
|
|
.as_ref()
|
|
.map(|ident| quote!(let _ = &self.#ident;));
|
|
let optional = type_is_option(&field.ty);
|
|
let install_alias = quote! {
|
|
let __webapi_alias_source_key =
|
|
::moli_webapi_declare::__private::v8str(scope, #source_name);
|
|
object
|
|
.has_own_property(scope, __webapi_alias_source_key.into())
|
|
.unwrap_or(false)
|
|
.then_some(())
|
|
.ok_or_else(|| {
|
|
::moli_webapi_declare::BindError::new(
|
|
::std::format!(
|
|
"failed to find declared alias source `{}` for `{}`",
|
|
#source_name,
|
|
#name
|
|
)
|
|
)
|
|
})?;
|
|
let __webapi_alias_value = object
|
|
.get(scope, __webapi_alias_source_key.into())
|
|
.ok_or_else(|| {
|
|
::moli_webapi_declare::BindError::new(
|
|
::std::format!(
|
|
"failed to read declared alias source `{}` for `{}`",
|
|
#source_name,
|
|
#name
|
|
)
|
|
)
|
|
})?;
|
|
let mut descriptor =
|
|
::moli_webapi_declare::v8::PropertyDescriptor::new_from_value_writable(
|
|
__webapi_alias_value,
|
|
#writable,
|
|
);
|
|
descriptor.set_configurable(#configurable);
|
|
descriptor.set_enumerable(#enumerable);
|
|
object
|
|
.define_property(scope, #property_key, &descriptor)
|
|
.unwrap_or(false)
|
|
.then_some(())
|
|
.ok_or_else(|| {
|
|
::moli_webapi_declare::BindError::new(
|
|
::std::format!("failed to define declared `{}` alias", #name)
|
|
)
|
|
})?;
|
|
};
|
|
if optional {
|
|
let Some(ident) = field.ident.as_ref() else {
|
|
return Err(Error::new(
|
|
field.span(),
|
|
"optional alias field requires a field name",
|
|
));
|
|
};
|
|
return Ok(quote! {
|
|
if self.#ident.is_some() {
|
|
#install_alias
|
|
}
|
|
});
|
|
}
|
|
Ok(quote! {
|
|
#field_read
|
|
#install_alias
|
|
})
|
|
}
|
|
|
|
fn expand_accessor_function_builder(
|
|
callback: &syn::Path,
|
|
length: i32,
|
|
data: Option<&syn::Expr>,
|
|
display_name: &proc_macro2::TokenStream,
|
|
) -> proc_macro2::TokenStream {
|
|
match data {
|
|
Some(data) => quote! {
|
|
{
|
|
let __webapi_callback_data = (#data);
|
|
let __webapi_callback_data =
|
|
::moli_webapi_declare::WebApiValue::to_v8_value(
|
|
&__webapi_callback_data,
|
|
scope,
|
|
)
|
|
.ok_or_else(|| {
|
|
::moli_webapi_declare::BindError::new(
|
|
::std::format!("failed to convert declared `{}` callback data", #display_name)
|
|
)
|
|
})?;
|
|
::moli_webapi_declare::v8::Function::builder(#callback)
|
|
.length(#length)
|
|
.data(__webapi_callback_data)
|
|
.constructor_behavior(
|
|
::moli_webapi_declare::v8::ConstructorBehavior::Throw,
|
|
)
|
|
.build(scope)
|
|
}
|
|
},
|
|
None => quote! {
|
|
::moli_webapi_declare::v8::Function::builder(#callback)
|
|
.length(#length)
|
|
.constructor_behavior(
|
|
::moli_webapi_declare::v8::ConstructorBehavior::Throw,
|
|
)
|
|
.build(scope)
|
|
},
|
|
}
|
|
}
|
|
|
|
fn expand_method_function_builder(
|
|
callback: &syn::Path,
|
|
length: i32,
|
|
data: Option<&syn::Expr>,
|
|
display_name: &proc_macro2::TokenStream,
|
|
) -> proc_macro2::TokenStream {
|
|
// `data` is evaluated inside the generated `initialize` body, so it can use
|
|
// the V8 `scope` parameter and `self` fields. This is how declarations pass
|
|
// non-reflectable callback state without declaring that state as a JS
|
|
// data_property or private slot.
|
|
match data {
|
|
Some(data) => quote! {
|
|
{
|
|
let __webapi_callback_data = (#data);
|
|
let __webapi_callback_data =
|
|
::moli_webapi_declare::WebApiValue::to_v8_value(
|
|
&__webapi_callback_data,
|
|
scope,
|
|
)
|
|
.ok_or_else(|| {
|
|
::moli_webapi_declare::BindError::new(
|
|
::std::format!("failed to convert declared `{}` callback data", #display_name)
|
|
)
|
|
})?;
|
|
::moli_webapi_declare::v8::Function::builder(#callback)
|
|
.length(#length)
|
|
.data(__webapi_callback_data)
|
|
.constructor_behavior(
|
|
::moli_webapi_declare::v8::ConstructorBehavior::Throw,
|
|
)
|
|
.build(scope)
|
|
}
|
|
},
|
|
None => quote! {
|
|
::moli_webapi_declare::v8::Function::builder(#callback)
|
|
.length(#length)
|
|
.constructor_behavior(
|
|
::moli_webapi_declare::v8::ConstructorBehavior::Throw,
|
|
)
|
|
.build(scope)
|
|
},
|
|
}
|
|
}
|
|
|
|
fn expand_template_function_builder(
|
|
callback: &syn::Path,
|
|
length: i32,
|
|
data: Option<&syn::Expr>,
|
|
template_name: &LitStr,
|
|
display_name: &proc_macro2::TokenStream,
|
|
) -> proc_macro2::TokenStream {
|
|
match data {
|
|
Some(data) => quote! {
|
|
{
|
|
let __webapi_callback_data = (#data);
|
|
let __webapi_callback_data =
|
|
::moli_webapi_declare::WebApiTemplateValue::to_v8_template_value(
|
|
&__webapi_callback_data,
|
|
scope,
|
|
)
|
|
.unwrap_or_else(|| {
|
|
panic!(
|
|
"failed to convert Web API `{}` member `{}` template callback data",
|
|
#template_name,
|
|
#display_name,
|
|
)
|
|
});
|
|
::moli_webapi_declare::v8::FunctionTemplate::builder(#callback)
|
|
.length(#length)
|
|
.data(__webapi_callback_data)
|
|
.constructor_behavior(
|
|
::moli_webapi_declare::v8::ConstructorBehavior::Throw,
|
|
)
|
|
.build(scope)
|
|
}
|
|
},
|
|
None => quote! {
|
|
::moli_webapi_declare::v8::FunctionTemplate::builder(#callback)
|
|
.length(#length)
|
|
.constructor_behavior(
|
|
::moli_webapi_declare::v8::ConstructorBehavior::Throw,
|
|
)
|
|
.build(scope)
|
|
},
|
|
}
|
|
}
|
|
|
|
fn expand_template_accessor_function_builder(
|
|
callback: &syn::Path,
|
|
length: i32,
|
|
data: Option<&syn::Expr>,
|
|
template_name: &LitStr,
|
|
display_name: &proc_macro2::TokenStream,
|
|
) -> proc_macro2::TokenStream {
|
|
match data {
|
|
Some(data) => quote! {
|
|
{
|
|
let __webapi_callback_data = (#data);
|
|
let __webapi_callback_data =
|
|
::moli_webapi_declare::WebApiTemplateValue::to_v8_template_value(
|
|
&__webapi_callback_data,
|
|
scope,
|
|
)
|
|
.unwrap_or_else(|| {
|
|
panic!(
|
|
"failed to convert Web API `{}` member `{}` template callback data",
|
|
#template_name,
|
|
#display_name,
|
|
)
|
|
});
|
|
::moli_webapi_declare::v8::FunctionTemplate::builder(#callback)
|
|
.length(#length)
|
|
.data(__webapi_callback_data)
|
|
.constructor_behavior(
|
|
::moli_webapi_declare::v8::ConstructorBehavior::Throw,
|
|
)
|
|
.build(scope)
|
|
}
|
|
},
|
|
None => quote! {
|
|
::moli_webapi_declare::v8::FunctionTemplate::builder(#callback)
|
|
.length(#length)
|
|
.constructor_behavior(
|
|
::moli_webapi_declare::v8::ConstructorBehavior::Throw,
|
|
)
|
|
.build(scope)
|
|
},
|
|
}
|
|
}
|
|
|
|
fn accessor_property_attributes(attrs: &crate::attrs::FieldAttrs) -> proc_macro2::TokenStream {
|
|
let mut property_attributes = if attrs.enumerable {
|
|
quote!(::moli_webapi_declare::v8::PropertyAttribute::NONE)
|
|
} else {
|
|
quote!(::moli_webapi_declare::v8::PropertyAttribute::DONT_ENUM)
|
|
};
|
|
if attrs.readonly {
|
|
property_attributes = quote! {
|
|
#property_attributes
|
|
| ::moli_webapi_declare::v8::PropertyAttribute::READ_ONLY
|
|
};
|
|
}
|
|
if attrs.dont_delete {
|
|
property_attributes = quote! {
|
|
#property_attributes
|
|
| ::moli_webapi_declare::v8::PropertyAttribute::DONT_DELETE
|
|
};
|
|
}
|
|
property_attributes
|
|
}
|
|
|
|
fn template_property_attributes(attrs: &crate::attrs::FieldAttrs) -> proc_macro2::TokenStream {
|
|
let mut property_attributes = if attrs.enumerable {
|
|
quote!(::moli_webapi_declare::v8::PropertyAttribute::NONE)
|
|
} else {
|
|
quote!(::moli_webapi_declare::v8::PropertyAttribute::DONT_ENUM)
|
|
};
|
|
if attrs.readonly {
|
|
property_attributes = quote! {
|
|
#property_attributes
|
|
| ::moli_webapi_declare::v8::PropertyAttribute::READ_ONLY
|
|
};
|
|
}
|
|
if attrs.dont_delete {
|
|
property_attributes = quote! {
|
|
#property_attributes
|
|
| ::moli_webapi_declare::v8::PropertyAttribute::DONT_DELETE
|
|
};
|
|
}
|
|
property_attributes
|
|
}
|
|
|
|
fn constant_property_attributes() -> proc_macro2::TokenStream {
|
|
quote! {
|
|
::moli_webapi_declare::webidl_constant_property_attributes()
|
|
}
|
|
}
|
|
|
|
fn to_string_tag_property_attributes(attrs: &crate::attrs::FieldAttrs) -> proc_macro2::TokenStream {
|
|
let mut property_attributes = quote!(::moli_webapi_declare::v8::PropertyAttribute::DONT_ENUM);
|
|
if attrs.readonly {
|
|
property_attributes = quote! {
|
|
#property_attributes
|
|
| ::moli_webapi_declare::v8::PropertyAttribute::READ_ONLY
|
|
};
|
|
}
|
|
if attrs.dont_delete {
|
|
property_attributes = quote! {
|
|
#property_attributes
|
|
| ::moli_webapi_declare::v8::PropertyAttribute::DONT_DELETE
|
|
};
|
|
}
|
|
property_attributes
|
|
}
|
|
|
|
fn webapi_field_key(
|
|
field: &Field,
|
|
attrs: &crate::attrs::FieldAttrs,
|
|
rename_all: RenameRule,
|
|
) -> Result<WebApiFieldKey, Error> {
|
|
let mut key = if let Some(symbol) = attrs.symbol.as_ref() {
|
|
webapi_symbol_field_key(symbol)?
|
|
} else {
|
|
let name = webapi_field_name(field, attrs.name.as_ref(), rename_all)?;
|
|
WebApiFieldKey {
|
|
display_name: name.clone(),
|
|
property_key: quote!(::moli_webapi_declare::__private::v8str(scope, #name).into()),
|
|
function_name: Some(quote!(
|
|
::moli_webapi_declare::__private::v8str(scope, #name)
|
|
)),
|
|
}
|
|
};
|
|
if let Some(function_name) = attrs.function_name.as_ref() {
|
|
key.function_name = Some(quote!(
|
|
::moli_webapi_declare::__private::v8str(scope, #function_name)
|
|
));
|
|
}
|
|
Ok(key)
|
|
}
|
|
|
|
fn webapi_symbol_field_key(symbol: &LitStr) -> Result<WebApiFieldKey, Error> {
|
|
match symbol.value().as_str() {
|
|
"iterator" => {
|
|
let display_name = LitStr::new("[Symbol.iterator]", symbol.span());
|
|
Ok(WebApiFieldKey {
|
|
display_name: quote!(#display_name),
|
|
property_key: quote!(::moli_webapi_declare::v8::Symbol::get_iterator(scope).into()),
|
|
function_name: None,
|
|
})
|
|
}
|
|
"asyncIterator" => {
|
|
let display_name = LitStr::new("[Symbol.asyncIterator]", symbol.span());
|
|
Ok(WebApiFieldKey {
|
|
display_name: quote!(#display_name),
|
|
property_key: quote!(
|
|
::moli_webapi_declare::v8::Symbol::get_async_iterator(scope).into()
|
|
),
|
|
function_name: None,
|
|
})
|
|
}
|
|
"toStringTag" => {
|
|
let display_name = LitStr::new("[Symbol.toStringTag]", symbol.span());
|
|
Ok(WebApiFieldKey {
|
|
display_name: quote!(#display_name),
|
|
property_key: quote!(
|
|
::moli_webapi_declare::v8::Symbol::get_to_string_tag(scope).into()
|
|
),
|
|
function_name: None,
|
|
})
|
|
}
|
|
_ => Err(Error::new(symbol.span(), "unsupported Web API symbol key")),
|
|
}
|
|
}
|
|
|
|
fn webapi_field_name(
|
|
field: &Field,
|
|
explicit: Option<&syn::Expr>,
|
|
rename_all: RenameRule,
|
|
) -> Result<proc_macro2::TokenStream, Error> {
|
|
if let Some(name) = explicit {
|
|
return Ok(quote!(#name));
|
|
}
|
|
let Some(ident) = field.ident.as_ref() else {
|
|
return Err(Error::new(field.span(), "object field requires a name"));
|
|
};
|
|
let name = ident.to_string();
|
|
let name = name.strip_prefix("r#").unwrap_or(&name);
|
|
let name = name.strip_prefix('_').unwrap_or(name);
|
|
if name.is_empty() {
|
|
return Err(Error::new(field.span(), "webapi field requires a name"));
|
|
}
|
|
let name = apply_rename_rule(name, rename_all);
|
|
let name = LitStr::new(&name, ident.span());
|
|
Ok(quote!(#name))
|
|
}
|
|
|
|
fn webapi_field_name_literal(
|
|
field: &Field,
|
|
explicit: Option<&syn::Expr>,
|
|
rename_all: RenameRule,
|
|
) -> Result<Option<LitStr>, Error> {
|
|
if let Some(name) = explicit {
|
|
return match name {
|
|
syn::Expr::Lit(expr) => match &expr.lit {
|
|
Lit::Str(name) => Ok(Some(name.clone())),
|
|
_ => Ok(None),
|
|
},
|
|
_ => Ok(None),
|
|
};
|
|
}
|
|
let Some(ident) = field.ident.as_ref() else {
|
|
return Err(Error::new(field.span(), "object field requires a name"));
|
|
};
|
|
let name = ident.to_string();
|
|
let name = name.strip_prefix("r#").unwrap_or(&name);
|
|
let name = name.strip_prefix('_').unwrap_or(name);
|
|
if name.is_empty() {
|
|
return Err(Error::new(field.span(), "webapi field requires a name"));
|
|
}
|
|
let name = apply_rename_rule(name, rename_all);
|
|
Ok(Some(LitStr::new(&name, ident.span())))
|
|
}
|
|
|
|
fn apply_rename_rule(name: &str, rename_all: RenameRule) -> String {
|
|
match rename_all {
|
|
RenameRule::None => name.to_owned(),
|
|
RenameRule::CamelCase => snake_to_camel_case(name),
|
|
}
|
|
}
|
|
|
|
fn snake_to_camel_case(name: &str) -> String {
|
|
let mut output = String::with_capacity(name.len());
|
|
let mut uppercase_next = false;
|
|
for character in name.chars() {
|
|
if character == '_' {
|
|
uppercase_next = !output.is_empty();
|
|
continue;
|
|
}
|
|
if uppercase_next {
|
|
if character.is_alphabetic() {
|
|
output.extend(character.to_uppercase());
|
|
uppercase_next = false;
|
|
} else {
|
|
output.push(character);
|
|
}
|
|
} else {
|
|
output.push(character);
|
|
}
|
|
}
|
|
output
|
|
}
|
|
|
|
fn expand_object_field_value(
|
|
field: &Field,
|
|
attrs: &crate::attrs::FieldAttrs,
|
|
optional: bool,
|
|
) -> Result<proc_macro2::TokenStream, Error> {
|
|
if let Some(value) = attrs.value.as_ref() {
|
|
let field_read = field
|
|
.ident
|
|
.as_ref()
|
|
.map(|ident| quote!(let _ = &self.#ident;));
|
|
return Ok(quote! {
|
|
#field_read
|
|
let __webapi_value = (#value);
|
|
let __webapi_value_ref = &__webapi_value;
|
|
});
|
|
}
|
|
if let Some(init) = attrs.init.as_ref() {
|
|
let field_read = field
|
|
.ident
|
|
.as_ref()
|
|
.map(|ident| quote!(let _ = &self.#ident;));
|
|
return Ok(match init {
|
|
ValueInitAttr::Null => {
|
|
quote! {
|
|
#field_read
|
|
let __webapi_value = ::moli_webapi_declare::v8::null(scope);
|
|
let __webapi_value_ref = &__webapi_value;
|
|
}
|
|
}
|
|
ValueInitAttr::Object => {
|
|
quote! {
|
|
#field_read
|
|
let __webapi_value = ::moli_webapi_declare::v8::Object::new(scope);
|
|
let __webapi_value_ref = &__webapi_value;
|
|
}
|
|
}
|
|
ValueInitAttr::NullObject => {
|
|
quote! {
|
|
#field_read
|
|
let __webapi_value = ::moli_webapi_declare::v8::Object::new(scope);
|
|
let __webapi_null = ::moli_webapi_declare::v8::null(scope);
|
|
let _ = __webapi_value.set_prototype(scope, __webapi_null.into());
|
|
let __webapi_value_ref = &__webapi_value;
|
|
}
|
|
}
|
|
ValueInitAttr::Array => {
|
|
quote! {
|
|
#field_read
|
|
let __webapi_value = ::moli_webapi_declare::v8::Array::new(scope, 0);
|
|
let __webapi_value_ref = &__webapi_value;
|
|
}
|
|
}
|
|
ValueInitAttr::Undefined => {
|
|
quote! {
|
|
#field_read
|
|
let __webapi_value = ::moli_webapi_declare::v8::undefined(scope);
|
|
let __webapi_value_ref = &__webapi_value;
|
|
}
|
|
}
|
|
ValueInitAttr::True => {
|
|
quote! {
|
|
#field_read
|
|
let __webapi_value = ::moli_webapi_declare::v8::Boolean::new(scope, true);
|
|
let __webapi_value_ref = &__webapi_value;
|
|
}
|
|
}
|
|
ValueInitAttr::False => {
|
|
quote! {
|
|
#field_read
|
|
let __webapi_value = ::moli_webapi_declare::v8::Boolean::new(scope, false);
|
|
let __webapi_value_ref = &__webapi_value;
|
|
}
|
|
}
|
|
ValueInitAttr::Zero => {
|
|
quote! {
|
|
#field_read
|
|
let __webapi_value = ::moli_webapi_declare::v8::Number::new(scope, 0.0);
|
|
let __webapi_value_ref = &__webapi_value;
|
|
}
|
|
}
|
|
ValueInitAttr::EmptyString => {
|
|
quote! {
|
|
#field_read
|
|
let __webapi_value = ::moli_webapi_declare::v8::String::empty(scope);
|
|
let __webapi_value_ref = &__webapi_value;
|
|
}
|
|
}
|
|
ValueInitAttr::String(value) => {
|
|
let field_name = field
|
|
.ident
|
|
.as_ref()
|
|
.map(|ident| ident.to_string())
|
|
.unwrap_or_else(|| "<unnamed>".to_string());
|
|
let field_name = LitStr::new(&field_name, field.span());
|
|
quote! {
|
|
#field_read
|
|
let __webapi_init_value = #value;
|
|
let __webapi_value =
|
|
::moli_webapi_declare::WebApiValue::to_v8_value(
|
|
&__webapi_init_value,
|
|
scope,
|
|
)
|
|
.ok_or_else(|| {
|
|
::moli_webapi_declare::BindError::new(
|
|
::std::format!("failed to convert declared `{}` initializer", #field_name)
|
|
)
|
|
})?;
|
|
let __webapi_value_ref = &__webapi_value;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
if attrs.data_property || attrs.hidden || attrs.slot || attrs.prototype || attrs.to_string_tag {
|
|
let Some(ident) = field.ident.as_ref() else {
|
|
return Err(Error::new(field.span(), "object field requires a name"));
|
|
};
|
|
if optional {
|
|
return Ok(quote! {
|
|
self.#ident.as_ref()
|
|
});
|
|
}
|
|
return Ok(quote! {
|
|
let __webapi_value_ref = &self.#ident;
|
|
});
|
|
}
|
|
Err(Error::new(
|
|
field.span(),
|
|
"object field requires #[webapi(value = expr)]",
|
|
))
|
|
}
|
|
|
|
fn type_is_option(ty: &Type) -> bool {
|
|
let Type::Path(path) = ty else {
|
|
return false;
|
|
};
|
|
path.qself.is_none()
|
|
&& path
|
|
.path
|
|
.segments
|
|
.last()
|
|
.is_some_and(|segment| segment.ident == "Option")
|
|
}
|
|
|
|
fn type_is_unit(ty: &Type) -> bool {
|
|
matches!(ty, Type::Tuple(tuple) if tuple.elems.is_empty())
|
|
}
|
|
|
|
fn named_fields(data: &Data) -> Result<Vec<Field>, Error> {
|
|
match data {
|
|
Data::Struct(data) => match &data.fields {
|
|
Fields::Named(fields) => Ok(fields.named.iter().cloned().collect()),
|
|
_ => Err(Error::new(
|
|
data.struct_token.span(),
|
|
"expected a struct with named fields",
|
|
)),
|
|
},
|
|
_ => Err(Error::new(
|
|
proc_macro2::Span::call_site(),
|
|
"expected a struct",
|
|
)),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::{expand_webapi_function_template, expand_webapi_interface, expand_webapi_object};
|
|
|
|
#[test]
|
|
fn declaration_only_field_attributes_are_rejected() {
|
|
let input = syn::parse_quote! {
|
|
#[webapi(interface = "Object")]
|
|
struct BadObject {
|
|
#[webapi(init = "null")]
|
|
ignored: (),
|
|
}
|
|
};
|
|
let error = match expand_webapi_object(input) {
|
|
Ok(_) => panic!("declaration-only field attributes should be rejected"),
|
|
Err(error) => error,
|
|
};
|
|
assert_eq!(
|
|
error.to_string(),
|
|
"declaration-only fields cannot use #[webapi(...)] installation attributes; add an installation kind or remove the attributes"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn default_data_property_fields_can_use_data_property_initializers() {
|
|
let input = syn::parse_quote! {
|
|
#[webapi(interface = "Object", data_properties)]
|
|
struct DefaultObject {
|
|
#[webapi(init = "null")]
|
|
value: (),
|
|
}
|
|
};
|
|
expand_webapi_object(input).expect("default data-property initializer should expand");
|
|
}
|
|
|
|
#[test]
|
|
fn object_generated_constructor_defaults_to_new_for_dynamic_fields() {
|
|
let input = syn::parse_quote! {
|
|
#[webapi(interface = "Object")]
|
|
struct DynamicObject {
|
|
#[webapi(data_property, init = true)]
|
|
brand: (),
|
|
|
|
#[webapi(data_property)]
|
|
value: u32,
|
|
}
|
|
};
|
|
let tokens = expand_webapi_object(input)
|
|
.expect("dynamic object should expand")
|
|
.to_string();
|
|
assert!(tokens.contains("pub fn new (value : u32) -> Self"));
|
|
assert!(tokens.contains("brand : ()"));
|
|
assert!(tokens.contains("value"));
|
|
}
|
|
|
|
#[test]
|
|
fn object_generated_constructor_defaults_to_new_for_unit_fields() {
|
|
let input = syn::parse_quote! {
|
|
#[webapi(interface = "Object")]
|
|
struct StaticObject {
|
|
#[webapi(data_property, init = true)]
|
|
brand: (),
|
|
|
|
#[webapi(method, callback = static_callback)]
|
|
action: (),
|
|
}
|
|
};
|
|
let tokens = expand_webapi_object(input)
|
|
.expect("static object should expand")
|
|
.to_string();
|
|
assert!(tokens.contains("pub fn new () -> Self"));
|
|
assert!(tokens.contains("brand : ()"));
|
|
assert!(tokens.contains("action : ()"));
|
|
}
|
|
|
|
#[test]
|
|
fn object_generated_constructor_uses_constructor_defaults() {
|
|
let input = syn::parse_quote! {
|
|
#[webapi(interface = "Object")]
|
|
struct DefaultedObject {
|
|
#[webapi(data_property)]
|
|
value: u32,
|
|
|
|
#[webapi(data_property, constructor_default = "ready")]
|
|
state: &'static str,
|
|
|
|
#[webapi(data_property, constructor_default = Vec::new())]
|
|
items: Vec<u32>,
|
|
|
|
#[webapi(data_property, constructor_default)]
|
|
count: usize,
|
|
}
|
|
};
|
|
let tokens = expand_webapi_object(input)
|
|
.expect("defaulted object should expand")
|
|
.to_string();
|
|
assert!(tokens.contains("pub fn new (value : u32) -> Self"));
|
|
assert!(tokens.contains("state : \"ready\""));
|
|
assert!(tokens.contains("items : Vec :: new ()"));
|
|
assert!(tokens.contains("count : :: std :: default :: Default :: default ()"));
|
|
}
|
|
|
|
#[test]
|
|
fn object_generated_constructor_defaults_can_reference_previous_fields() {
|
|
let input = syn::parse_quote! {
|
|
#[webapi(interface = "Object")]
|
|
struct DerivedDefaultObject {
|
|
#[webapi(data_property)]
|
|
client_x: i32,
|
|
|
|
#[webapi(data_property, constructor_default = client_x)]
|
|
x: i32,
|
|
}
|
|
};
|
|
let tokens = expand_webapi_object(input)
|
|
.expect("derived default object should expand")
|
|
.to_string();
|
|
assert!(tokens.contains("pub fn new (client_x : i32) -> Self"));
|
|
assert!(tokens.contains("client_x"));
|
|
assert!(tokens.contains("x : client_x"));
|
|
}
|
|
|
|
#[test]
|
|
fn object_generated_constructor_can_be_suppressed() {
|
|
let input = syn::parse_quote! {
|
|
#[webapi(interface = "Object", no_dynamic_constructor)]
|
|
struct ManualObject {
|
|
#[webapi(data_property)]
|
|
value: u32,
|
|
}
|
|
};
|
|
let tokens = expand_webapi_object(input)
|
|
.expect("manual object should expand")
|
|
.to_string();
|
|
assert!(!tokens.contains("pub fn new"));
|
|
}
|
|
|
|
#[test]
|
|
fn interface_fields_with_ignored_attributes_are_rejected() {
|
|
let input = syn::parse_quote! {
|
|
#[webapi(name = "Sample")]
|
|
struct BadInterface {
|
|
#[webapi(enumerable)]
|
|
ignored: (),
|
|
}
|
|
};
|
|
let error = match expand_webapi_interface(input) {
|
|
Ok(_) => panic!("ignored interface field attributes should be rejected"),
|
|
Err(error) => error,
|
|
};
|
|
assert_eq!(
|
|
error.to_string(),
|
|
"interface fields with #[webapi(...)] attributes must declare #[webapi(method)] or #[webapi(accessor_property)]"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn interface_fields_with_unsupported_installation_kinds_are_rejected() {
|
|
for kind in ["data_property", "slot", "hidden"] {
|
|
let input = syn::parse_str(&format!(
|
|
r#"
|
|
#[webapi(name = "Sample")]
|
|
struct BadInterface {{
|
|
#[webapi({kind})]
|
|
ignored: (),
|
|
}}
|
|
"#
|
|
))
|
|
.expect("parse interface");
|
|
let error = match expand_webapi_interface(input) {
|
|
Ok(_) => panic!("unsupported interface field kind should be rejected"),
|
|
Err(error) => error,
|
|
};
|
|
assert_eq!(
|
|
error.to_string(),
|
|
"interface fields with #[webapi(...)] attributes must declare #[webapi(method)] or #[webapi(accessor_property)]"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn function_template_fields_with_unsupported_installation_kinds_are_rejected() {
|
|
for kind in [
|
|
"data_property",
|
|
"slot",
|
|
"hidden",
|
|
"prototype",
|
|
"to_string_tag",
|
|
] {
|
|
let input = syn::parse_str(&format!(
|
|
r#"
|
|
#[webapi(name = "Sample")]
|
|
struct BadTemplate {{
|
|
#[webapi({kind})]
|
|
ignored: (),
|
|
}}
|
|
"#
|
|
))
|
|
.expect("parse function template");
|
|
let error = match expand_webapi_function_template(input) {
|
|
Ok(_) => panic!("unsupported function template field kind should be rejected"),
|
|
Err(error) => error,
|
|
};
|
|
assert_eq!(
|
|
error.to_string(),
|
|
"function template fields with #[webapi(...)] attributes must declare #[webapi(method)], #[webapi(static_method)], #[webapi(accessor_property)], #[webapi(native_data_property)], #[webapi(intrinsic_data_property = ...)], #[webapi(constant)], or #[webapi(alias = ...)]"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn function_template_intrinsic_data_properties_are_supported() {
|
|
let input = syn::parse_quote! {
|
|
#[webapi(name = "Sample", enumerable)]
|
|
struct SampleTemplate {
|
|
#[webapi(
|
|
intrinsic_data_property = v8::Intrinsic::ArrayProtoValues
|
|
)]
|
|
values: (),
|
|
|
|
#[webapi(
|
|
intrinsic_data_property = v8::Intrinsic::ArrayProtoValues,
|
|
symbol = "iterator",
|
|
readonly,
|
|
dont_delete
|
|
)]
|
|
iterator: (),
|
|
}
|
|
};
|
|
expand_webapi_function_template(input)
|
|
.expect("intrinsic data properties should expand on function templates");
|
|
}
|
|
|
|
#[test]
|
|
fn function_template_intrinsic_prototype_parent_is_supported() {
|
|
let input = syn::parse_quote! {
|
|
#[webapi(
|
|
name = "Example Iterator",
|
|
intrinsic_prototype_parent = v8::Intrinsic::IteratorPrototype,
|
|
prototype_to_string_tag = "Example Iterator",
|
|
readonly_prototype
|
|
)]
|
|
struct IteratorTemplate {
|
|
#[webapi(method, callback = sample_callback)]
|
|
value: (),
|
|
}
|
|
};
|
|
expand_webapi_function_template(input)
|
|
.expect("intrinsic prototype parents should expand on function templates");
|
|
}
|
|
|
|
#[test]
|
|
fn object_intrinsic_data_properties_are_rejected() {
|
|
let input = syn::parse_quote! {
|
|
#[webapi(interface = "Object")]
|
|
struct BadObject {
|
|
#[webapi(
|
|
intrinsic_data_property = v8::Intrinsic::ArrayProtoValues
|
|
)]
|
|
values: (),
|
|
}
|
|
};
|
|
let error = match expand_webapi_object(input) {
|
|
Ok(_) => panic!("runtime object intrinsic property should be rejected"),
|
|
Err(error) => error,
|
|
};
|
|
assert_eq!(
|
|
error.to_string(),
|
|
"`intrinsic_data_property` is only supported by WebApiFunctionTemplate"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn function_template_native_data_properties_are_supported() {
|
|
let input = syn::parse_quote! {
|
|
#[webapi(name = "Sample", enumerable)]
|
|
struct SampleTemplate {
|
|
#[webapi(native_data_property, getter = sample_getter)]
|
|
value: (),
|
|
|
|
#[webapi(native_data_property = "named", getter = sample_getter, setter = sample_setter, dont_delete)]
|
|
named: (),
|
|
}
|
|
};
|
|
expand_webapi_function_template(input)
|
|
.expect("native accessors should expand on function templates");
|
|
}
|
|
|
|
#[test]
|
|
fn function_template_accessor_property_fields_are_supported() {
|
|
let input = syn::parse_quote! {
|
|
#[webapi(name = "Sample", enumerable)]
|
|
struct SampleTemplate {
|
|
#[webapi(accessor_property, getter = sample_getter)]
|
|
value: (),
|
|
|
|
#[webapi(accessor_property = "named", getter = sample_getter, setter = sample_setter, data = getter_data, setter_data = setter_data, dont_delete)]
|
|
named: (),
|
|
}
|
|
};
|
|
expand_webapi_function_template(input).expect("template accessors should expand");
|
|
}
|
|
|
|
#[test]
|
|
fn function_template_readonly_accessor_property_fields_are_rejected() {
|
|
let input = syn::parse_quote! {
|
|
#[webapi(name = "Sample")]
|
|
struct BadTemplate {
|
|
#[webapi(accessor_property, getter = sample_getter, readonly)]
|
|
value: (),
|
|
}
|
|
};
|
|
let error = match expand_webapi_function_template(input) {
|
|
Ok(_) => panic!("readonly function-template accessor property should be rejected"),
|
|
Err(error) => error,
|
|
};
|
|
assert_eq!(
|
|
error.to_string(),
|
|
"function-template `accessor_property` fields have no writable attribute; omit #[webapi(setter)] instead of using readonly"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn object_readonly_accessor_property_fields_are_rejected() {
|
|
let input = syn::parse_quote! {
|
|
#[webapi(interface = "Sample")]
|
|
struct BadObject {
|
|
#[webapi(accessor_property, getter = sample_getter, readonly)]
|
|
value: (),
|
|
}
|
|
};
|
|
let error = match expand_webapi_object(input) {
|
|
Ok(_) => panic!("readonly runtime-object accessor property should be rejected"),
|
|
Err(error) => error,
|
|
};
|
|
assert_eq!(
|
|
error.to_string(),
|
|
"runtime-object `accessor_property` fields have no writable attribute; omit #[webapi(setter)] instead of using readonly"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn object_accessor_property_rejects_callback_and_getter_value_together() {
|
|
let input = syn::parse_quote! {
|
|
#[webapi(interface = "Sample")]
|
|
struct BadObject<'scope> {
|
|
getter: ::moli_webapi_declare::v8::Local<'scope, ::moli_webapi_declare::v8::Function>,
|
|
|
|
#[webapi(accessor_property, getter = sample_getter, getter_value = self.getter)]
|
|
value: (),
|
|
}
|
|
};
|
|
let error = match expand_webapi_object(input) {
|
|
Ok(_) => panic!("getter and getter_value should be rejected together"),
|
|
Err(error) => error,
|
|
};
|
|
assert_eq!(
|
|
error.to_string(),
|
|
"`accessor_property` field cannot declare both #[webapi(getter = ...)] and #[webapi(getter_value = ...)]"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn object_native_data_property_fields_are_supported() {
|
|
let input = syn::parse_quote! {
|
|
#[webapi(interface = "Sample", enumerable)]
|
|
struct SampleObject {
|
|
#[webapi(native_data_property, getter = sample_getter)]
|
|
value: (),
|
|
|
|
#[webapi(native_data_property = "named", getter = sample_getter, setter = sample_setter, dont_delete)]
|
|
named: (),
|
|
}
|
|
};
|
|
expand_webapi_object(input).expect("native accessors should expand on objects");
|
|
}
|
|
|
|
#[test]
|
|
fn object_readonly_native_data_property_fields_are_rejected() {
|
|
let input = syn::parse_quote! {
|
|
#[webapi(interface = "Sample")]
|
|
struct BadObject {
|
|
#[webapi(native_data_property, getter = sample_getter, readonly)]
|
|
value: (),
|
|
}
|
|
};
|
|
let error = match expand_webapi_object(input) {
|
|
Ok(_) => panic!("readonly native data property should be rejected"),
|
|
Err(error) => error,
|
|
};
|
|
assert_eq!(
|
|
error.to_string(),
|
|
"`native_data_property` fields have no writable attribute; omit #[webapi(setter)] instead of using readonly"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn object_static_method_fields_are_rejected() {
|
|
let input = syn::parse_quote! {
|
|
#[webapi(interface = "Object", data_properties)]
|
|
struct BadObject {
|
|
#[webapi(static_method, callback = sample_callback)]
|
|
create: (),
|
|
}
|
|
};
|
|
let error = match expand_webapi_object(input) {
|
|
Ok(_) => panic!("object static_method should be rejected"),
|
|
Err(error) => error,
|
|
};
|
|
assert_eq!(
|
|
error.to_string(),
|
|
"object fields with #[webapi(...)] attributes cannot declare #[webapi(static_method)]"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn object_constant_fields_are_supported() {
|
|
let input = syn::parse_quote! {
|
|
#[webapi(interface = "Object", data_properties)]
|
|
struct BadObject {
|
|
#[webapi(constant = "READY", value = 4u32)]
|
|
ready: (),
|
|
}
|
|
};
|
|
expand_webapi_object(input).expect("object constant should expand");
|
|
}
|
|
|
|
#[test]
|
|
fn function_template_alias_source_must_be_earlier_string_method() {
|
|
let input = syn::parse_quote! {
|
|
#[webapi(name = "Sample")]
|
|
struct BadTemplate {
|
|
#[webapi(alias = "entries", symbol = "iterator")]
|
|
iterator: (),
|
|
|
|
#[webapi(method, callback = sample_callback)]
|
|
entries: (),
|
|
}
|
|
};
|
|
let error = match expand_webapi_function_template(input) {
|
|
Ok(_) => panic!("out-of-order template alias should be rejected"),
|
|
Err(error) => error,
|
|
};
|
|
assert_eq!(
|
|
error.to_string(),
|
|
"function template alias source must refer to an earlier string-named method"
|
|
);
|
|
|
|
let input = syn::parse_quote! {
|
|
#[webapi(name = "Sample")]
|
|
struct BadTemplate {
|
|
#[webapi(method, symbol = "iterator", callback = sample_callback)]
|
|
entries: (),
|
|
|
|
#[webapi(alias = "entries", symbol = "asyncIterator")]
|
|
async_iterator: (),
|
|
}
|
|
};
|
|
let error = match expand_webapi_function_template(input) {
|
|
Ok(_) => panic!("symbol method source should not be a template alias source"),
|
|
Err(error) => error,
|
|
};
|
|
assert_eq!(
|
|
error.to_string(),
|
|
"function template alias source must refer to an earlier string-named method"
|
|
);
|
|
}
|
|
}
|