124 lines
4.6 KiB
Rust
124 lines
4.6 KiB
Rust
use proc_macro_error::abort;
|
|
use quote::{quote, quote_spanned};
|
|
use syn::spanned::Spanned;
|
|
use syn::{parse_macro_input, parse_quote, Data, DeriveInput, Fields, GenericParam, Generics};
|
|
|
|
pub fn derive_into_command_definition_impl(
|
|
item: proc_macro::TokenStream,
|
|
) -> proc_macro::TokenStream {
|
|
let DeriveInput {
|
|
ident,
|
|
data,
|
|
generics,
|
|
..
|
|
}: DeriveInput = parse_macro_input!(item as DeriveInput);
|
|
|
|
let data = match data {
|
|
Data::Struct(data) => data,
|
|
Data::Enum(data) => abort!(
|
|
data.enum_token,
|
|
"IntoCommandDefinition not supported for enum"
|
|
),
|
|
Data::Union(data) => abort!(
|
|
data.union_token,
|
|
"IntoCommandDefinition not supported for union"
|
|
),
|
|
};
|
|
|
|
let generics = add_trait_bounds(generics);
|
|
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
|
|
|
|
let num_fields = data.fields.len();
|
|
|
|
let create_param_stream = match &data.fields {
|
|
Fields::Named(fields) => {
|
|
let field_entries = fields.named.iter().map(|field| {
|
|
let name = field.ident.clone().map(|id| id.to_string());
|
|
let field_type = &field.ty;
|
|
quote_spanned! { field.span() =>
|
|
parameters.push(api::messages::command::CommandParameterDefinition {
|
|
name: #name.to_string(),
|
|
data_type: <#field_type as api::data_type::ToDataType>::DATA_TYPE,
|
|
});
|
|
}
|
|
});
|
|
quote! { #(#field_entries)* }
|
|
}
|
|
Fields::Unnamed(fields) => abort!(
|
|
fields,
|
|
"IntoCommandDefinition not supported for unnamed structs"
|
|
),
|
|
Fields::Unit => quote! {},
|
|
};
|
|
let parse_param_stream = match &data.fields {
|
|
Fields::Named(fields) => {
|
|
let field_entries = fields.named.iter().map(|field| {
|
|
let name = &field.ident;
|
|
let name_string = field.ident.clone().map(|id| id.to_string());
|
|
let field_type = &field.ty;
|
|
quote_spanned! { field.span() =>
|
|
let #name: #field_type = (*command
|
|
.parameters
|
|
.get(#name_string)
|
|
.ok_or_else(|| api::messages::command::IntoCommandDefinitionError::ParameterMissing(#name_string.to_string()))?)
|
|
.try_into()
|
|
.map_err(|_| api::messages::command::IntoCommandDefinitionError::MismatchedType {
|
|
parameter: #name_string.to_string(),
|
|
expected: <#field_type as api::data_type::ToDataType>::DATA_TYPE,
|
|
})?;
|
|
}
|
|
});
|
|
quote! { #(#field_entries)* }
|
|
}
|
|
Fields::Unnamed(_) => unreachable!("Already checked this"),
|
|
Fields::Unit => quote! {},
|
|
};
|
|
let param_name_stream = match &data.fields {
|
|
Fields::Named(fields) => {
|
|
let field_entries = fields.named.iter().map(|field| {
|
|
let name = &field.ident;
|
|
quote_spanned! { field.span() => #name, }
|
|
});
|
|
quote! { #(#field_entries)* }
|
|
}
|
|
Fields::Unnamed(_) => unreachable!("Already checked this"),
|
|
Fields::Unit => quote! {},
|
|
};
|
|
|
|
let result = quote! {
|
|
impl #impl_generics api::messages::command::IntoCommandDefinition for #ident #ty_generics #where_clause {
|
|
fn create(name: std::string::String) -> api::messages::command::CommandDefinition {
|
|
let mut parameters = std::vec::Vec::with_capacity( #num_fields );
|
|
#create_param_stream
|
|
api::messages::command::CommandDefinition {
|
|
name: name,
|
|
parameters: parameters,
|
|
}
|
|
}
|
|
|
|
fn parse(command: api::messages::command::Command) -> core::result::Result<Self, api::messages::command::IntoCommandDefinitionError> {
|
|
#parse_param_stream
|
|
Ok(Self {
|
|
#param_name_stream
|
|
})
|
|
}
|
|
}
|
|
};
|
|
|
|
result.into()
|
|
}
|
|
|
|
fn add_trait_bounds(mut generics: Generics) -> Generics {
|
|
for param in &mut generics.params {
|
|
if let GenericParam::Type(ref mut type_param) = *param {
|
|
type_param
|
|
.bounds
|
|
.push(parse_quote!(api::data_type::ToDataType));
|
|
type_param.bounds.push(parse_quote!(
|
|
core::convert::TryFrom<api::data_value::DataValue>
|
|
));
|
|
}
|
|
}
|
|
generics
|
|
}
|