diff --git a/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs b/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs deleted file mode 100644 index 5796699..0000000 --- a/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs +++ /dev/null @@ -1,328 +0,0 @@ -use std::rc::Rc; - -use case::CaseExt; -use proc_macro2::TokenStream; -use quote::quote; - -use crate::{ - generate::util, - parser::{self, ApiMethod, ApiParameters}, - types, -}; - -use super::{ - return_type::create_return_type, send_method_builder::SendMethodBuilder, MethodsAndExtra, -}; - -pub fn create_method_with_params( - group: &parser::ApiGroup, - method: &parser::ApiMethod, - params: &parser::ApiParameters, - method_name: &proc_macro2::Ident, - url: &str, -) -> MethodsAndExtra { - let param_type = util::to_ident(&format!( - "{}{}Parameters", - group.name.to_camel(), - method.name.to_camel() - )); - - let parameters = Parameters::new(params); - let has_optional_parameters = !parameters.optional.is_empty(); - let generator = MethodGenerator { - group, - url, - parameters, - param_type, - method, - method_name, - }; - - if has_optional_parameters { - generator.generate_method_with_builder() - } else { - generator.generate_method_without_builder() - } -} - -#[derive(Debug)] -struct MethodGenerator<'a> { - group: &'a parser::ApiGroup, - url: &'a str, - parameters: Parameters<'a>, - param_type: proc_macro2::Ident, - method: &'a ApiMethod, - method_name: &'a proc_macro2::Ident, -} - -impl<'a> MethodGenerator<'a> { - fn generate_method_without_builder(&self) -> MethodsAndExtra { - let form_builder = self.parameters.mandatory.form_builder(); - let form_attributes = - quote! { .multipart(reqwest::multipart::Form::new()#(#form_builder)*) }; - - let builder = SendMethodBuilder::new(self.method_name, self.url, quote! { self.auth }) - .description(&self.method.description) - .with_args(&self.parameters.mandatory.generate_params()) - .with_extra_form_args(&[form_attributes]); - - match create_return_type(self.group, self.method) { - Some((return_type_name, return_type)) => { - MethodsAndExtra::new(builder.return_type(&return_type_name).build()) - .with_structs(return_type) - } - None => MethodsAndExtra::new(builder.build()), - } - } - - fn generate_method_with_builder(&self) -> MethodsAndExtra { - let group_name = self.group_name(); - let send_builder = self.send_builder(&util::to_ident("send")); - let send_impl_generator = - SendImplGenerator::new(&group_name, &self.parameters, &self.param_type); - - let send = match create_return_type(self.group, self.method) { - Some((return_type_name, return_type)) => { - let send_impl = - send_impl_generator.generate(send_builder.return_type(&return_type_name)); - - quote! { - #send_impl - #return_type - } - } - None => send_impl_generator.generate(send_builder), - }; - let builder = generate_builder( - &self.parameters, - self.method, - self.method_name, - &self.param_type, - ); - let param_type = &self.param_type; - - let group_impl = quote! { - pub struct #param_type<'a> { - group: &'a #group_name<'a>, - form: reqwest::multipart::Form, - } - - #send - }; - - MethodsAndExtra::new(builder).with_structs(group_impl) - } - - fn group_name(&self) -> proc_macro2::Ident { - util::to_ident(&self.group.name.to_camel()) - } - - fn send_builder(&self, name: &proc_macro2::Ident) -> SendMethodBuilder { - SendMethodBuilder::new(name, self.url, quote! { self.group.auth }).with_form() - } -} - -fn generate_builder( - parameters: &Parameters, - method: &ApiMethod, - method_name: &proc_macro2::Ident, - param_type: &proc_macro2::Ident, -) -> proc_macro2::TokenStream { - let mandatory_param_names = parameters.mandatory.names(); - let mandatory_param_args = parameters.mandatory.generate_params(); - - util::add_docs( - &method.description, - quote! { - pub fn #method_name(&self, #(#mandatory_param_args),*) -> #param_type { - #param_type::new(self, #(#mandatory_param_names),*) - } - }, - ) -} - -#[derive(Debug)] -struct SendImplGenerator<'a> { - group_name: &'a proc_macro2::Ident, - parameters: &'a Parameters<'a>, - param_type: &'a proc_macro2::Ident, -} - -impl<'a> SendImplGenerator<'a> { - fn new( - group_name: &'a proc_macro2::Ident, - parameters: &'a Parameters<'a>, - param_type: &'a proc_macro2::Ident, - ) -> Self { - Self { - group_name, - parameters, - param_type, - } - } - - fn generate(&self, send_method_builder: SendMethodBuilder) -> TokenStream { - let parameters = self.parameters; - - let optional_builder_methods = parameters.optional.generate_builder_methods(); - let mandatory_param_form_build = parameters.mandatory.form_builder(); - let mandatory_param_args = parameters.mandatory.generate_params(); - let param_type = self.param_type; - let group_name = self.group_name; - let send_method = send_method_builder.build(); - - quote! { - impl<'a> #param_type<'a> { - fn new(group: &'a #group_name, #(#mandatory_param_args),*) -> Self { - let form = reqwest::multipart::Form::new()#(#mandatory_param_form_build)*; - Self { group, form } - } - - #(#optional_builder_methods)* - #send_method - } - } - } -} - -#[derive(Debug)] -struct Parameters<'a> { - mandatory: MandatoryParams<'a>, - optional: OptionalParams<'a>, -} - -impl<'a> Parameters<'a> { - fn new(api_parameters: &'a ApiParameters) -> Self { - let rc_params = Rc::new(api_parameters); - - let mandatory = MandatoryParams::new(&rc_params); - let optional = OptionalParams::new(&rc_params); - - Self { - mandatory, - optional, - } - } -} - -#[derive(Debug)] -struct MandatoryParams<'a> { - params: Vec>, -} - -impl<'a> MandatoryParams<'a> { - fn new(params: &'a ApiParameters) -> Self { - Self { - params: Parameter::from(¶ms.mandatory), - } - } - - fn generate_params(&self) -> Vec { - self.params - .iter() - .map(|p| p.generate_param_with_name()) - .collect() - } - - fn form_builder(&self) -> Vec { - self.params - .iter() - .map(|param| { - let name_ident = param.name_ident(); - let name = param.name(); - quote! { .text(#name, #name_ident.to_string()) } - }) - .collect() - } - - fn names(&self) -> Vec { - self.params - .iter() - .map(|p| p.name_ident()) - .map(|name_ident| quote! { #name_ident }) - .collect() - } -} - -#[derive(Debug)] -struct OptionalParams<'a> { - params: Vec>, -} - -impl<'a> OptionalParams<'a> { - fn new(params: &'a ApiParameters) -> Self { - Self { - params: Parameter::from(¶ms.optional), - } - } - - fn is_empty(&self) -> bool { - self.params.is_empty() - } - - fn generate_builder_methods(&self) -> Vec { - self.params - .iter() - .map(Self::generate_builder_method) - .collect() - } - - fn generate_builder_method(param: &Parameter) -> TokenStream { - let name = param.name(); - let name_ident = param.name_ident(); - - let param_type = util::to_ident(¶m.p_type.to_borrowed_type()); - - let builder_param = if param.p_type.should_borrow() { - quote! { &#param_type } - } else { - quote! { #param_type } - }; - - util::add_docs( - ¶m.p_type.get_type_info().description, - quote! { - pub fn #name_ident(mut self, value: #builder_param) -> Self { - self.form = self.form.text(#name, value.to_string()); - self - } - }, - ) - } -} - -#[derive(Debug)] -struct Parameter<'a> { - p_type: &'a types::Type, -} - -impl<'a> Parameter<'a> { - fn new(p_type: &'a types::Type) -> Self { - Self { p_type } - } - - fn from(parameters: &[types::Type]) -> Vec> { - parameters.iter().map(Parameter::new).collect() - } - - fn name(&self) -> String { - self.p_type.get_type_info().name.to_snake() - } - - fn name_ident(&self) -> proc_macro2::Ident { - util::to_ident(&self.name()) - } - - fn generate_param_with_name(&self) -> TokenStream { - let t = util::to_ident(&self.p_type.to_borrowed_type()); - - let name_ident = self.name_ident(); - let t = if self.p_type.should_borrow() { - quote! { &#t } - } else { - quote! { #t } - }; - - quote! { #name_ident: #t } - } -} diff --git a/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs b/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs deleted file mode 100644 index ec1d3a7..0000000 --- a/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs +++ /dev/null @@ -1,27 +0,0 @@ -use quote::quote; - -use super::{ - return_type::create_return_type, send_method_builder::SendMethodBuilder, MethodsAndExtra, -}; -use crate::parser; - -pub fn create_method_without_params( - group: &parser::ApiGroup, - method: &parser::ApiMethod, - method_name: proc_macro2::Ident, - url: &str, -) -> MethodsAndExtra { - let builder = SendMethodBuilder::new(&method_name, url, quote! { self.auth }) - .description(&method.description); - - match create_return_type(group, method) { - Some((return_type_name, return_type)) => { - MethodsAndExtra::new(builder.return_type(&return_type_name).build()) - .with_structs(return_type) - } - None => { - // assume that all methods without a return type returns a string - MethodsAndExtra::new(builder.build()) - } - } -} diff --git a/qbittorrent-web-api-gen/src/generate/group/method/mod.rs b/qbittorrent-web-api-gen/src/generate/group/method/mod.rs deleted file mode 100644 index 041cec4..0000000 --- a/qbittorrent-web-api-gen/src/generate/group/method/mod.rs +++ /dev/null @@ -1,72 +0,0 @@ -mod method_with_params; -mod method_without_params; -mod return_type; -mod send_method_builder; - -use crate::{generate::util, parser}; -use case::CaseExt; -use quote::quote; - -use self::{ - method_with_params::create_method_with_params, - method_without_params::create_method_without_params, -}; - -pub fn generate_methods( - group: &parser::ApiGroup, - auth: &syn::Ident, - group_name_camel: &syn::Ident, -) -> proc_macro2::TokenStream { - let methods_and_extra = group - .methods - .iter() - .map(|method| generate_method(group, method)); - - let methods = methods_and_extra - .clone() - .map(|MethodsAndExtra { methods, .. }| methods); - - let extra = methods_and_extra.flat_map(|MethodsAndExtra { extra: structs, .. }| structs); - - quote! { - impl <'a> #group_name_camel<'a> { - pub fn new(auth: &'a #auth) -> Self { - Self { auth } - } - - #(#methods)* - } - - #(#extra)* - } -} - -#[derive(Debug)] -pub struct MethodsAndExtra { - methods: proc_macro2::TokenStream, - extra: Option, -} - -impl MethodsAndExtra { - pub fn new(methods: proc_macro2::TokenStream) -> Self { - Self { - methods, - extra: None, - } - } - - pub fn with_structs(mut self, structs: proc_macro2::TokenStream) -> Self { - self.extra = Some(structs); - self - } -} - -fn generate_method(group: &parser::ApiGroup, method: &parser::ApiMethod) -> MethodsAndExtra { - let method_name = util::to_ident(&method.name.to_snake()); - let url = format!("/api/v2/{}/{}", group.url, method.url); - - match &method.parameters { - Some(params) => create_method_with_params(group, method, params, &method_name, &url), - None => create_method_without_params(group, method, method_name, &url), - } -} diff --git a/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs b/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs deleted file mode 100644 index cba375b..0000000 --- a/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs +++ /dev/null @@ -1,203 +0,0 @@ -use std::collections::HashMap; - -use case::CaseExt; -use quote::{format_ident, quote}; -use regex::Regex; - -use crate::{generate::util, parser, types}; - -pub fn create_return_type( - group: &parser::ApiGroup, - method: &parser::ApiMethod, -) -> Option<(proc_macro2::TokenStream, proc_macro2::TokenStream)> { - let return_type = match &method.return_type { - Some(t) => t, - None => return None, - }; - - let to_enum_name = |name: &str| to_enum_name(&group.name, &method.name, name); - - let enum_types_with_names: Vec<(String, proc_macro2::TokenStream)> = - create_enum_with_names(return_type, &group.name, &method.name); - - let enum_names: HashMap = enum_types_with_names - .iter() - .map(|(enum_name, _)| (enum_name.clone(), to_enum_name(enum_name))) - .collect(); - - let enum_types = enum_types_with_names.iter().map(|(_, enum_type)| enum_type); - - let builder_fields = return_type - .parameters - .iter() - .map(|parameter| generate_builder_field(parameter, &enum_names)); - - let return_type_name = util::to_ident(&format!( - "{}{}Result", - &group.name.to_camel(), - &method.name.to_camel() - )); - - let result_type = if return_type.is_list { - quote! { std::vec::Vec<#return_type_name> } - } else { - quote! { #return_type_name } - }; - - Some(( - result_type, - quote! { - #[derive(Debug, Deserialize)] - pub struct #return_type_name { - #(#builder_fields,)* - } - - #(#enum_types)* - }, - )) -} - -fn create_enum_with_names( - return_type: &parser::ReturnType, - group_name: &str, - method_name: &str, -) -> Vec<(String, proc_macro2::TokenStream)> { - return_type - .parameters - .iter() - .flat_map(create_enum_fields) - .map(|(name, enum_fields)| create_enum(enum_fields, group_name, method_name, name)) - .collect() -} - -fn create_enum( - enum_fields: Vec, - group_name: &str, - method_name: &str, - name: String, -) -> (String, proc_macro2::TokenStream) { - let enum_name = util::to_ident(&to_enum_name(group_name, method_name, &name)); - ( - name, - quote! { - #[allow(clippy::enum_variant_names)] - #[derive(Debug, Deserialize, PartialEq, Eq)] - pub enum #enum_name { - #(#enum_fields,)* - } - }, - ) -} - -fn create_enum_fields( - parameter: &parser::ReturnTypeParameter, -) -> Option<(String, Vec)> { - match ¶meter.return_type { - types::Type::Number(types::TypeInfo { - ref name, - type_description: Some(type_description), - .. - }) => create_enum_field_value(type_description, name, create_number_enum_value), - types::Type::String(types::TypeInfo { - ref name, - type_description: Some(type_description), - .. - }) => create_enum_field_value(type_description, name, create_string_enum_value), - _ => None, - } -} - -fn generate_builder_field( - parameter: &parser::ReturnTypeParameter, - enum_names: &HashMap, -) -> proc_macro2::TokenStream { - let name_string = ¶meter.name; - let name = util::to_ident(&name_string.to_snake().replace("__", "_")); - let enum_name = match enum_names.get(name_string) { - Some(enum_type) => enum_type.to_owned(), - None => parameter.return_type.to_owned_type(), - }; - let return_type = util::to_ident(&enum_name); - let return_type_as_quote = if parameter.return_type.is_list() { - quote! { std::vec::Vec<#return_type> } - } else { - quote! { #return_type } - }; - let generate_field = |field_name| { - quote! { - #[serde(rename = #name_string)] - pub #field_name: #return_type_as_quote - } - }; - - // "type" is a reserved keyword in Rust, so we just add "t_" to it. - if name_string == "type" { - generate_field(format_ident!("t_{}", name)) - } else { - generate_field(name) - } -} - -fn create_enum_field_value( - type_description: &types::TypeDescription, - name: &str, - f: F, -) -> Option<(String, Vec)> -where - F: Fn(&types::TypeDescriptions) -> proc_macro2::TokenStream, -{ - let enum_fields: Vec = type_description - .values - .iter() - .map(f) - .collect::>(); - - let nn = name.to_string(); - - Some((nn, enum_fields)) -} - -fn create_string_enum_value( - type_description: &types::TypeDescriptions, -) -> proc_macro2::TokenStream { - let value = &type_description.value; - let value_as_ident = util::to_ident(&value.to_camel()); - create_enum_field(&value_as_ident, value, &type_description.description) -} - -fn create_number_enum_value(value: &types::TypeDescriptions) -> proc_macro2::TokenStream { - let v = &value.value; - let re = Regex::new(r#"\(.*\)"#).unwrap(); - let desc = &value - .description - .replace(' ', "_") - .replace('-', "_") - .replace(',', "_"); - let desc_without_parentheses = re.replace_all(desc, ""); - let ident = util::to_ident(&desc_without_parentheses.to_camel()); - - create_enum_field(&ident, v, &value.description) -} - -fn create_enum_field( - ident: &syn::Ident, - rename: &str, - description: &str, -) -> proc_macro2::TokenStream { - util::add_docs( - &Some(description.to_string()), - quote! { - #[serde(rename = #rename)] - #ident - }, - ) -} - -fn to_enum_name(group_name: &str, method_name: &str, name: &str) -> String { - format!( - "{}{}{}", - group_name.to_camel(), - method_name.to_camel(), - name.to_camel() - ) -} diff --git a/qbittorrent-web-api-gen/src/generate/group/method/send_method_builder.rs b/qbittorrent-web-api-gen/src/generate/group/method/send_method_builder.rs deleted file mode 100644 index ffd3ad8..0000000 --- a/qbittorrent-web-api-gen/src/generate/group/method/send_method_builder.rs +++ /dev/null @@ -1,98 +0,0 @@ -use quote::quote; - -use crate::generate::util; - -pub struct SendMethodBuilder { - method_name: syn::Ident, - url: String, - auth_module_path: proc_macro2::TokenStream, - return_type: Option, - description: Option, - args: Vec, - extra_form_args: Vec, - form: bool, -} - -impl SendMethodBuilder { - pub fn new( - method_name: &syn::Ident, - url: &str, - auth_module_path: proc_macro2::TokenStream, - ) -> Self { - Self { - method_name: method_name.clone(), - url: url.to_string(), - auth_module_path, - return_type: None, - description: None, - form: false, - args: vec![], - extra_form_args: vec![], - } - } - - pub fn return_type(mut self, value: &proc_macro2::TokenStream) -> Self { - self.return_type = Some(value.clone()); - self - } - - pub fn description(mut self, value: &Option) -> Self { - self.description = value.clone(); - self - } - - pub fn with_form(mut self) -> Self { - self.form = true; - self - } - - pub fn with_args(mut self, value: &[proc_macro2::TokenStream]) -> Self { - self.args = value.to_vec(); - self - } - - pub fn with_extra_form_args(mut self, value: &[proc_macro2::TokenStream]) -> Self { - self.extra_form_args = value.to_vec(); - self - } - - pub fn build(&self) -> proc_macro2::TokenStream { - let method_name = &self.method_name; - let (return_type, parse_type) = match &self.return_type { - Some(t) => (t.clone(), quote! { .json::<#t>() }), - None => (quote! { String }, quote! { .text() }), - }; - let url = &self.url; - let auth_module_path = &self.auth_module_path; - let form = if self.form { - quote! { .multipart(self.form) } - } else { - quote! {} - }; - let arg_list = &self.args; - let args = if !arg_list.is_empty() { - quote! { , #(#arg_list),* } - } else { - quote! {} - }; - let extra_form_args = &self.extra_form_args; - - util::add_docs( - &self.description, - quote! { - pub async fn #method_name(self #args) -> Result<#return_type> { - let res = #auth_module_path - .authenticated_client(#url) - #form - #(#extra_form_args)* - .send() - .await? - #parse_type - .await?; - - Ok(res) - } - }, - ) - } -}