uses a proc-macro to automate command definitions
This commit is contained in:
129
api-proc-macro/src/into_command_definition.rs
Normal file
129
api-proc-macro/src/into_command_definition.rs
Normal file
@@ -0,0 +1,129 @@
|
||||
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(fields) => abort!(
|
||||
fields,
|
||||
"IntoCommandDefinition not supported for unnamed structs"
|
||||
),
|
||||
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(fields) => abort!(
|
||||
fields,
|
||||
"IntoCommandDefinition not supported for unnamed structs"
|
||||
),
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user