Support for adding derives

This commit is contained in:
Joel Wachsler 2022-07-24 00:02:57 +00:00
parent ab973237ed
commit c1eeb399a0
5 changed files with 167 additions and 45 deletions

View File

@ -5,7 +5,20 @@ use quote::quote;
use super::{group_method::GroupMethod, skeleton::auth_ident, util};
impl parser::ApiGroup {
#[derive(Debug)]
pub struct GroupGeneration {
api_group: parser::ApiGroup,
response_derives: Vec<String>,
}
impl GroupGeneration {
pub fn new(api_group: parser::ApiGroup, response_derives: Vec<String>) -> Self {
Self {
api_group,
response_derives,
}
}
pub fn generate(&self) -> TokenStream {
let struct_name = self.struct_name();
let group_name_snake = self.name_snake();
@ -45,7 +58,7 @@ impl parser::ApiGroup {
let auth = auth_ident();
util::add_docs(
&self.description,
self.description(),
quote! {
impl super::#auth {
pub fn #name_snake(&self) -> #struct_name {
@ -69,21 +82,57 @@ impl parser::ApiGroup {
}
fn group_methods(&self) -> Vec<GroupMethod> {
self.methods
self.methods()
.iter()
.map(|method| GroupMethod::new(self, method))
.collect()
}
fn description(&self) -> &Option<String> {
&self.api_group.description
}
fn methods(&self) -> &Vec<parser::ApiMethod> {
&self.api_group.methods
}
pub fn url(&self) -> String {
self.api_group.url.clone()
}
pub fn struct_name(&self) -> Ident {
self.name_camel()
}
pub fn response_derives(&self, additional_derives: Vec<&str>) -> TokenStream {
let derives = self
.all_derives()
.chain(additional_derives.into_iter())
.map(|s| syn::parse_str::<syn::Path>(s).unwrap())
.map(|derive| quote! { #derive });
quote! {
#[derive(#(#derives),*)]
}
}
fn all_derives(&self) -> impl Iterator<Item = &str> {
let base = vec!["serde::Deserialize", "Debug"].into_iter();
let additional = self
.response_derives
.iter()
.map(|s| s.as_str())
.filter(|item| item != &"serde::Deserialize")
.filter(|item| item != &"Debug");
base.chain(additional)
}
fn name_camel(&self) -> Ident {
util::to_ident(&self.name.to_camel())
util::to_ident(&self.api_group.name.to_camel())
}
fn name_snake(&self) -> Ident {
util::to_ident(&self.name.to_snake())
util::to_ident(&self.api_group.name.to_snake())
}
}

View File

@ -1,30 +1,11 @@
use case::CaseExt;
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use proc_macro2::Ident;
use crate::parser;
use super::util;
impl parser::ApiMethod {
pub fn structs(&self) -> TokenStream {
let objects = self.types.objects();
let structs = objects.iter().map(|obj| obj.generate_struct());
quote! {
#(#structs)*
}
}
pub fn enums(&self) -> TokenStream {
let enums = self.types.enums();
let generated_enums = enums.iter().map(|e| e.generate());
quote! {
#(#generated_enums)*
}
}
pub fn name_snake(&self) -> Ident {
util::to_ident(&self.name.to_snake())
}

View File

@ -3,13 +3,14 @@ use case::CaseExt;
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use super::util;
use super::{api_group::GroupGeneration, util};
pub fn generate_groups(groups: Vec<parser::ApiGroup>) -> TokenStream {
pub fn generate_groups(groups: Vec<parser::ApiGroup>, resp_derives: Vec<String>) -> TokenStream {
let gr = groups
.iter()
.into_iter()
// implemented manually
.filter(|group| group.name != "authentication")
.map(|group| GroupGeneration::new(group, resp_derives.clone()))
.map(generate_group);
quote! {
@ -17,7 +18,7 @@ pub fn generate_groups(groups: Vec<parser::ApiGroup>) -> TokenStream {
}
}
fn generate_group(group: &parser::ApiGroup) -> TokenStream {
fn generate_group(group: GroupGeneration) -> TokenStream {
let group = group.generate();
quote! {
@ -25,13 +26,28 @@ fn generate_group(group: &parser::ApiGroup) -> TokenStream {
}
}
impl parser::TypeWithName {
#[derive(Debug)]
pub struct StructGenerator<'a> {
type_: &'a parser::TypeWithName,
group: &'a GroupGeneration,
}
impl<'a> StructGenerator<'a> {
pub fn new(type_: &'a parser::TypeWithName, group: &'a GroupGeneration) -> Self {
Self { type_, group }
}
pub fn generate_struct(&self) -> TokenStream {
let fields = self.types.iter().map(|obj| obj.generate_struct_field());
let name = util::to_ident(&self.name);
let fields = self
.type_
.types
.iter()
.map(|obj| obj.generate_struct_field());
let name = util::to_ident(&self.type_.name);
let derives = self.group.response_derives(vec![]);
quote! {
#[derive(Debug, serde::Deserialize)]
#derives
pub struct #name {
#(#fields,)*
}
@ -90,14 +106,29 @@ impl types::Type {
}
}
impl parser::Enum {
#[derive(Debug)]
pub struct EnumGeneration<'a> {
enum_: &'a parser::Enum,
group: &'a GroupGeneration,
}
impl<'a> EnumGeneration<'a> {
pub fn new(enum_: &'a parser::Enum, group: &'a GroupGeneration) -> Self {
Self { enum_, group }
}
pub fn generate(&self) -> TokenStream {
let values = self.values.iter().map(|enum_value| enum_value.generate());
let name = util::to_ident(&self.name);
let values = self
.enum_
.values
.iter()
.map(|enum_value| enum_value.generate());
let name = util::to_ident(&self.enum_.name);
let derives = self.group.response_derives(vec!["PartialEq", "Eq"]);
quote! {
#[allow(clippy::enum_variant_names)]
#[derive(Debug, serde::Deserialize, PartialEq, Eq)]
#derives
pub enum #name {
#(#values,)*
}

View File

@ -2,23 +2,27 @@ use crate::parser;
use proc_macro2::{Ident, TokenStream};
use quote::quote;
use super::util;
use super::{
api_group::GroupGeneration,
group::{EnumGeneration, StructGenerator},
util,
};
#[derive(Debug)]
pub struct GroupMethod<'a> {
group: &'a parser::ApiGroup,
group: &'a GroupGeneration,
method: &'a parser::ApiMethod,
}
impl<'a> GroupMethod<'a> {
pub fn new(group: &'a parser::ApiGroup, method: &'a parser::ApiMethod) -> Self {
pub fn new(group: &'a GroupGeneration, method: &'a parser::ApiMethod) -> Self {
Self { group, method }
}
pub fn generate_method(&self) -> TokenStream {
let method_name = self.method.name_snake();
let structs = self.method.structs();
let enums = self.method.enums();
let structs = self.structs();
let enums = self.enums();
let builder = self.generate_request_builder();
let response_struct = self.generate_response_struct();
let request_method = self.generate_request_method();
@ -34,6 +38,29 @@ impl<'a> GroupMethod<'a> {
}
}
pub fn structs(&self) -> TokenStream {
let objects = self.method.types.objects();
let structs = objects
.iter()
.map(|obj| StructGenerator::new(obj, self.group).generate_struct());
quote! {
#(#structs)*
}
}
pub fn enums(&self) -> TokenStream {
let enums = self.method.types.enums();
let generated_enums = enums
.iter()
.map(|enum_| EnumGeneration::new(enum_, self.group))
.map(|e| e.generate());
quote! {
#(#generated_enums)*
}
}
fn generate_request_method(&self) -> TokenStream {
let method_name = self.method.name_snake();
@ -89,8 +116,10 @@ impl<'a> GroupMethod<'a> {
.iter()
.map(|field| field.generate_struct_field());
let derives = self.group.response_derives(vec![]);
quote! {
#[derive(Debug, serde::Deserialize)]
#derives
pub struct Response {
#(#struct_fields,)*
}
@ -139,7 +168,7 @@ impl<'a> GroupMethod<'a> {
form_access: TokenStream,
form_factory: TokenStream,
) -> TokenStream {
let method_url = format!("/api/v2/{}/{}", self.group.url, self.method.url);
let method_url = format!("/api/v2/{}/{}", self.group.url(), self.method.url);
let (response_type, response_parse) = match self.method.types.response() {
Some(resp) => {

View File

@ -15,12 +15,13 @@ use self::{group::generate_groups, skeleton::generate_skeleton};
pub fn generate(ast: &syn::DeriveInput, api_content: &str) -> TokenStream {
let ident = &ast.ident;
let resp_derives = get_response_derives(ast);
let token_tree = md_parser::TokenTreeFactory::create(api_content);
let api_groups = parser::parse_api_groups(token_tree);
let skeleton = generate_skeleton(ident);
let groups = generate_groups(api_groups);
let groups = generate_groups(api_groups, resp_derives);
let impl_ident = syn::Ident::new(&format!("{}_impl", ident).to_snake(), ident.span());
quote! {
@ -30,3 +31,34 @@ pub fn generate(ast: &syn::DeriveInput, api_content: &str) -> TokenStream {
}
}
}
fn get_response_derives(ast: &syn::DeriveInput) -> Vec<String> {
ast.attrs
.iter()
.find(|attr| {
attr.path
.get_ident()
.map(|ident| ident == "api_gen")
.unwrap_or(false)
})
.into_iter()
.flat_map(|attr| attr.parse_meta().ok())
.filter_map(|meta| match meta {
syn::Meta::List(list) => Some(list.nested),
_ => None,
})
.flat_map(|nested| nested.into_iter())
.filter_map(|value| match value {
syn::NestedMeta::Meta(meta) => Some(meta),
_ => None,
})
.filter_map(|value| match value {
syn::Meta::NameValue(name_value) => Some(name_value.lit),
_ => None,
})
.filter_map(|lit| match lit {
syn::Lit::Str(str) => Some(str.value().split(',').map(|s| s.trim()).collect()),
_ => None,
})
.collect()
}