diff --git a/qbittorrent-web-api-gen/src/generate/api_group.rs b/qbittorrent-web-api-gen/src/generate/api_group.rs index 26abae1..3c73a19 100644 --- a/qbittorrent-web-api-gen/src/generate/api_group.rs +++ b/qbittorrent-web-api-gen/src/generate/api_group.rs @@ -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, +} + +impl GroupGeneration { + pub fn new(api_group: parser::ApiGroup, response_derives: Vec) -> 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 { - self.methods + self.methods() .iter() .map(|method| GroupMethod::new(self, method)) .collect() } + fn description(&self) -> &Option { + &self.api_group.description + } + + fn methods(&self) -> &Vec { + &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::(s).unwrap()) + .map(|derive| quote! { #derive }); + + quote! { + #[derive(#(#derives),*)] + } + } + + fn all_derives(&self) -> impl Iterator { + 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()) } } diff --git a/qbittorrent-web-api-gen/src/generate/api_method.rs b/qbittorrent-web-api-gen/src/generate/api_method.rs index abdb8cf..5155009 100644 --- a/qbittorrent-web-api-gen/src/generate/api_method.rs +++ b/qbittorrent-web-api-gen/src/generate/api_method.rs @@ -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()) } diff --git a/qbittorrent-web-api-gen/src/generate/group.rs b/qbittorrent-web-api-gen/src/generate/group.rs index f2988bf..2900fdf 100644 --- a/qbittorrent-web-api-gen/src/generate/group.rs +++ b/qbittorrent-web-api-gen/src/generate/group.rs @@ -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) -> TokenStream { +pub fn generate_groups(groups: Vec, resp_derives: Vec) -> 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) -> 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,)* } diff --git a/qbittorrent-web-api-gen/src/generate/group_method.rs b/qbittorrent-web-api-gen/src/generate/group_method.rs index e8fb818..d7686c6 100644 --- a/qbittorrent-web-api-gen/src/generate/group_method.rs +++ b/qbittorrent-web-api-gen/src/generate/group_method.rs @@ -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) => { diff --git a/qbittorrent-web-api-gen/src/generate/mod.rs b/qbittorrent-web-api-gen/src/generate/mod.rs index 7292d8a..4a1273b 100644 --- a/qbittorrent-web-api-gen/src/generate/mod.rs +++ b/qbittorrent-web-api-gen/src/generate/mod.rs @@ -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 { + 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() +}