Split group method generation

This commit is contained in:
Joel Wachsler 2022-07-12 11:59:13 +00:00
parent a383e60294
commit c633a6c33f
4 changed files with 395 additions and 372 deletions

View File

@ -0,0 +1,162 @@
use case::CaseExt;
use quote::quote;
use crate::{generate::util, parser, types};
use super::return_type::create_return_type;
pub fn create_method_with_params(
group: &parser::ApiGroup,
method: &parser::ApiMethod,
params: &[types::Type],
method_name: &proc_macro2::Ident,
url: &str,
) -> (proc_macro2::TokenStream, Option<proc_macro2::TokenStream>) {
let parameter_type = util::to_ident(&format!(
"{}{}Parameters",
group.name.to_camel(),
method.name.to_camel()
));
let mandatory_params = params
.iter()
.filter(|param| !param.get_type_info().is_optional);
let mandatory_param_args = mandatory_params.clone().map(|param| {
let name = util::to_ident(&param.get_type_info().name.to_snake());
let t = util::to_ident(&param.to_borrowed_type());
if param.should_borrow() {
quote! {
#name: &#t
}
} else {
quote! {
#name: #t
}
}
});
let mandatory_param_names = mandatory_params.clone().map(|param| {
let name = util::to_ident(&param.get_type_info().name.to_snake());
quote! {
#name
}
});
let mandatory_param_args_clone = mandatory_param_args.clone();
let mandatory_param_form_build = mandatory_params.map(|param| {
let n = &param.get_type_info().name;
let name = util::to_ident(&n.to_snake());
quote! {
let form = form.text(#n, #name.to_string());
}
});
let optional_params = params
.iter()
.filter(|param| param.get_type_info().is_optional)
.map(|param| {
let n = &param.get_type_info().name;
let name = util::to_ident(&n.to_snake());
let t = util::to_ident(&param.to_borrowed_type());
let method = if param.should_borrow() {
quote! {
pub fn #name(mut self, value: &#t) -> Self {
self.form = self.form.text(#n, value.to_string());
self
}
}
} else {
quote! {
pub fn #name(mut self, value: #t) -> Self {
self.form = self.form.text(#n, value.to_string());
self
}
}
};
util::add_docs(&param.get_type_info().description, method)
});
let group_name = util::to_ident(&group.name.to_camel());
let send = match create_return_type(group, method) {
Some((return_type_name, return_type)) => {
quote! {
impl<'a> #parameter_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_params)*
pub async fn send(self) -> Result<#return_type_name> {
let res = self.group
.auth
.authenticated_client(#url)
.multipart(self.form)
.send()
.await?
.json::<#return_type_name>()
.await?;
Ok(res)
}
}
#return_type
}
}
None => {
quote! {
impl<'a> #parameter_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_params)*
pub async fn send(self) -> Result<String> {
let res = self.group
.auth
.authenticated_client(#url)
.multipart(self.form)
.send()
.await?
.text()
.await?;
Ok(res)
}
}
}
}
};
(
util::add_docs(
&method.description,
quote! {
pub fn #method_name(&self, #(#mandatory_param_args_clone),*) -> #parameter_type {
#parameter_type::new(self, #(#mandatory_param_names),*)
}
},
),
Some(quote! {
pub struct #parameter_type<'a> {
group: &'a #group_name<'a>,
form: reqwest::multipart::Form,
}
#send
}),
)
}

View File

@ -0,0 +1,50 @@
use crate::{generate::util, parser};
use quote::quote;
use super::return_type::create_return_type;
pub fn create_method_without_params(
group: &parser::ApiGroup,
method: &parser::ApiMethod,
method_name: proc_macro2::Ident,
url: &str,
) -> (proc_macro2::TokenStream, Option<proc_macro2::TokenStream>) {
match create_return_type(group, method) {
Some((return_type_name, return_type)) => (
util::add_docs(
&method.description,
quote! {
pub async fn #method_name(&self) -> Result<#return_type_name> {
let res = self.auth
.authenticated_client(#url)
.send()
.await?
.json::<#return_type_name>()
.await?;
Ok(res)
}
},
),
Some(return_type),
),
None => (
util::add_docs(
&method.description,
quote! {
pub async fn #method_name(&self) -> Result<String> {
let res = self.auth
.authenticated_client(#url)
.send()
.await?
.text()
.await?;
Ok(res)
}
},
), // assume that all methods without a return type returns a string
None,
),
}
}

View File

@ -1,9 +1,15 @@
use std::collections::HashMap; mod method_with_params;
mod method_without_params;
mod return_type;
use crate::{generate::util, parser, types}; use crate::{generate::util, parser};
use case::CaseExt; use case::CaseExt;
use quote::{format_ident, quote}; use quote::quote;
use regex::Regex;
use self::{
method_with_params::create_method_with_params,
method_without_params::create_method_without_params,
};
pub fn generate_methods( pub fn generate_methods(
group: &parser::ApiGroup, group: &parser::ApiGroup,
@ -43,371 +49,3 @@ fn generate_method(
None => create_method_without_params(group, method, method_name, &url), None => create_method_without_params(group, method, method_name, &url),
} }
} }
fn create_method_without_params(
group: &parser::ApiGroup,
method: &parser::ApiMethod,
method_name: proc_macro2::Ident,
url: &str,
) -> (proc_macro2::TokenStream, Option<proc_macro2::TokenStream>) {
match create_return_type(group, method) {
Some((return_type_name, return_type)) => (
util::add_docs(
&method.description,
quote! {
pub async fn #method_name(&self) -> Result<#return_type_name> {
let res = self.auth
.authenticated_client(#url)
.send()
.await?
.json::<#return_type_name>()
.await?;
Ok(res)
}
},
),
Some(return_type),
),
None => (
util::add_docs(
&method.description,
quote! {
pub async fn #method_name(&self) -> Result<String> {
let res = self.auth
.authenticated_client(#url)
.send()
.await?
.text()
.await?;
Ok(res)
}
},
), // assume that all methods without a return type returns a string
None,
),
}
}
fn create_method_with_params(
group: &parser::ApiGroup,
method: &parser::ApiMethod,
params: &[types::Type],
method_name: &proc_macro2::Ident,
url: &str,
) -> (proc_macro2::TokenStream, Option<proc_macro2::TokenStream>) {
let parameter_type = util::to_ident(&format!(
"{}{}Parameters",
group.name.to_camel(),
method.name.to_camel()
));
let mandatory_params = params
.iter()
.filter(|param| !param.get_type_info().is_optional);
let mandatory_param_args = mandatory_params.clone().map(|param| {
let name = util::to_ident(&param.get_type_info().name.to_snake());
let t = util::to_ident(&param.to_borrowed_type());
if param.should_borrow() {
quote! {
#name: &#t
}
} else {
quote! {
#name: #t
}
}
});
let mandatory_param_names = mandatory_params.clone().map(|param| {
let name = util::to_ident(&param.get_type_info().name.to_snake());
quote! {
#name
}
});
let mandatory_param_args_clone = mandatory_param_args.clone();
let mandatory_param_form_build = mandatory_params.map(|param| {
let n = &param.get_type_info().name;
let name = util::to_ident(&n.to_snake());
quote! {
let form = form.text(#n, #name.to_string());
}
});
let optional_params = params
.iter()
.filter(|param| param.get_type_info().is_optional)
.map(|param| {
let n = &param.get_type_info().name;
let name = util::to_ident(&n.to_snake());
let t = util::to_ident(&param.to_borrowed_type());
let method = if param.should_borrow() {
quote! {
pub fn #name(mut self, value: &#t) -> Self {
self.form = self.form.text(#n, value.to_string());
self
}
}
} else {
quote! {
pub fn #name(mut self, value: #t) -> Self {
self.form = self.form.text(#n, value.to_string());
self
}
}
};
util::add_docs(&param.get_type_info().description, method)
});
let group_name = util::to_ident(&group.name.to_camel());
let send = match create_return_type(group, method) {
Some((return_type_name, return_type)) => {
quote! {
impl<'a> #parameter_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_params)*
pub async fn send(self) -> Result<#return_type_name> {
let res = self.group
.auth
.authenticated_client(#url)
.multipart(self.form)
.send()
.await?
.json::<#return_type_name>()
.await?;
Ok(res)
}
}
#return_type
}
}
None => {
quote! {
impl<'a> #parameter_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_params)*
pub async fn send(self) -> Result<String> {
let res = self.group
.auth
.authenticated_client(#url)
.multipart(self.form)
.send()
.await?
.text()
.await?;
Ok(res)
}
}
}
}
};
(
util::add_docs(
&method.description,
quote! {
pub fn #method_name(&self, #(#mandatory_param_args_clone),*) -> #parameter_type {
#parameter_type::new(self, #(#mandatory_param_names),*)
}
},
),
Some(quote! {
pub struct #parameter_type<'a> {
group: &'a #group_name<'a>,
form: reqwest::multipart::Form,
}
#send
}),
)
}
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| {
format!(
"{}{}{}",
group.name.to_camel(),
method.name.to_camel(),
name.to_camel()
)
};
let enum_types_with_names =
return_type
.parameters
.iter()
.flat_map(|parameter| match &parameter.return_type {
types::Type::Number(types::TypeInfo {
ref name,
type_description: Some(type_description),
..
}) => {
let enum_fields = type_description.values.iter().map(|value| {
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());
util::add_docs(
&Some(value.description.clone()),
quote! {
#[serde(rename = #v)]
#ident
},
)
});
let enum_name = util::to_ident(&to_enum_name(name));
Some((
name,
quote! {
#[allow(clippy::enum_variant_names)]
#[derive(Debug, Deserialize, PartialEq, Eq)]
pub enum #enum_name {
#(#enum_fields,)*
}
},
))
}
types::Type::String(types::TypeInfo {
ref name,
type_description: Some(type_description),
..
}) => {
let enum_fields = type_description.values.iter().map(|type_description| {
let value = &type_description.value;
let value_as_ident = util::to_ident(&value.to_camel());
util::add_docs(
&Some(type_description.description.clone()),
quote! {
#[serde(rename = #value)]
#value_as_ident
},
)
});
let enum_name = util::to_ident(&to_enum_name(name));
Some((
name,
quote! {
#[allow(clippy::enum_variant_names)]
#[derive(Debug, Deserialize, PartialEq, Eq)]
pub enum #enum_name {
#(#enum_fields,)*
}
},
))
}
_ => None,
});
let enum_names: HashMap<&String, String> = enum_types_with_names
.clone()
.map(|(enum_name, _)| (enum_name, to_enum_name(enum_name)))
.collect();
let enum_types = enum_types_with_names.map(|(_, enum_type)| enum_type);
let parameters = return_type.parameters.iter().map(|parameter| {
let namestr = &parameter.name;
let name = util::to_ident(&namestr.to_snake().replace("__", "_"));
let rtype = if let Some(enum_type) = enum_names.get(namestr) {
util::to_ident(enum_type)
} else {
util::to_ident(&parameter.return_type.to_owned_type())
};
let type_info = parameter.return_type.get_type_info();
let rtype_as_quote = if type_info.is_list {
quote! {
std::vec::Vec<#rtype>
}
} else {
quote! {
#rtype
}
};
// "type" is a reserved keyword in Rust, so we use a different name.
if namestr == "type" {
let non_reserved_name = format_ident!("t_{}", name);
quote! {
#[serde(rename = #namestr)]
pub #non_reserved_name: #rtype_as_quote
}
} else {
quote! {
#[serde(rename = #namestr)]
pub #name: #rtype_as_quote
}
}
});
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 {
#(#parameters,)*
}
#(#enum_types)*
},
))
}

View File

@ -0,0 +1,173 @@
use std::collections::HashMap;
use case::CaseExt;
use quote::{quote, format_ident};
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| {
format!(
"{}{}{}",
group.name.to_camel(),
method.name.to_camel(),
name.to_camel()
)
};
let enum_types_with_names =
return_type
.parameters
.iter()
.flat_map(|parameter| match &parameter.return_type {
types::Type::Number(types::TypeInfo {
ref name,
type_description: Some(type_description),
..
}) => {
let enum_fields = type_description.values.iter().map(|value| {
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());
util::add_docs(
&Some(value.description.clone()),
quote! {
#[serde(rename = #v)]
#ident
},
)
});
let enum_name = util::to_ident(&to_enum_name(name));
Some((
name,
quote! {
#[allow(clippy::enum_variant_names)]
#[derive(Debug, Deserialize, PartialEq, Eq)]
pub enum #enum_name {
#(#enum_fields,)*
}
},
))
}
types::Type::String(types::TypeInfo {
ref name,
type_description: Some(type_description),
..
}) => {
let enum_fields = type_description.values.iter().map(|type_description| {
let value = &type_description.value;
let value_as_ident = util::to_ident(&value.to_camel());
util::add_docs(
&Some(type_description.description.clone()),
quote! {
#[serde(rename = #value)]
#value_as_ident
},
)
});
let enum_name = util::to_ident(&to_enum_name(name));
Some((
name,
quote! {
#[allow(clippy::enum_variant_names)]
#[derive(Debug, Deserialize, PartialEq, Eq)]
pub enum #enum_name {
#(#enum_fields,)*
}
},
))
}
_ => None,
});
let enum_names: HashMap<&String, String> = enum_types_with_names
.clone()
.map(|(enum_name, _)| (enum_name, to_enum_name(enum_name)))
.collect();
let enum_types = enum_types_with_names.map(|(_, enum_type)| enum_type);
let parameters = return_type.parameters.iter().map(|parameter| {
let namestr = &parameter.name;
let name = util::to_ident(&namestr.to_snake().replace("__", "_"));
let rtype = if let Some(enum_type) = enum_names.get(namestr) {
util::to_ident(enum_type)
} else {
util::to_ident(&parameter.return_type.to_owned_type())
};
let type_info = parameter.return_type.get_type_info();
let rtype_as_quote = if type_info.is_list {
quote! {
std::vec::Vec<#rtype>
}
} else {
quote! {
#rtype
}
};
// "type" is a reserved keyword in Rust, so we use a different name.
if namestr == "type" {
let non_reserved_name = format_ident!("t_{}", name);
quote! {
#[serde(rename = #namestr)]
pub #non_reserved_name: #rtype_as_quote
}
} else {
quote! {
#[serde(rename = #namestr)]
pub #name: #rtype_as_quote
}
}
});
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 {
#(#parameters,)*
}
#(#enum_types)*
},
))
}