This commit is contained in:
Joel Wachsler 2022-07-22 21:55:26 +00:00
parent 07e825bcd4
commit 4a1db65877
5 changed files with 344 additions and 120 deletions

File diff suppressed because it is too large Load Diff

View File

@ -156,7 +156,7 @@ impl types::Type {
&self.get_type_info().description, &self.get_type_info().description,
quote! { quote! {
#[serde(rename = #orig_name)] #[serde(rename = #orig_name)]
#name_snake: #type_name pub #name_snake: #type_name
}, },
) )
} }
@ -223,6 +223,69 @@ impl<'a> GroupMethod<'a> {
Self { group, method } Self { group, method }
} }
fn generate_method(&self) -> TokenStream {
let method_name = self.method.name_snake();
let structs = self.method.structs();
let enums = self.method.enums();
let builder = self.generate_request_builder();
let response_struct = self.generate_response_struct();
let request_method = self.generate_request_method();
quote! {
pub mod #method_name {
#structs
#enums
#builder
#response_struct
#request_method
}
}
}
fn generate_request_method(&self) -> TokenStream {
let method_name = self.method.name_snake();
let parameters = self
.method
.types
.mandatory_params()
.iter()
.map(|param| param.to_parameter())
.collect();
let form_builder = self.mandatory_parameters_as_form_builder();
let method_impl = if self.method.types.optional_parameters().is_empty() {
self.generate_send_method(
&method_name,
parameters,
quote! { self.auth },
quote! { form },
quote! {
let form = reqwest::multipart::Form::new();
#form_builder
},
)
} else {
quote! {
pub fn #method_name(&self, #(#parameters),*) -> Builder<'_> {
let form = reqwest::multipart::Form::new();
#form_builder
Builder { group: self, form }
}
}
};
let group_struct_name = self.group.struct_name();
let method_impl_with_docs = util::add_docs(&self.method.description, method_impl);
quote! {
impl<'a> super::#group_struct_name<'a> {
#method_impl_with_docs
}
}
}
fn generate_response_struct(&self) -> TokenStream { fn generate_response_struct(&self) -> TokenStream {
let response = match self.method.types.response() { let response = match self.method.types.response() {
Some(res) => res, Some(res) => res,
@ -239,20 +302,26 @@ impl<'a> GroupMethod<'a> {
} }
} }
fn generate_optional_builder(&self) -> TokenStream { /// Returns a TokenStream containing a request builder if there are optional
let optional_params = match self.method.types.optional_parameters() { /// parameters, otherwise an empty TokenStream is returned.
Some(params) => params, fn generate_request_builder(&self) -> TokenStream {
None => return quote! {}, let optional_params = self.method.types.optional_parameters();
}; if optional_params.is_empty() {
return quote! {};
}
let builder_methods = optional_params let builder_methods = optional_params
.iter() .iter()
.map(|param| param.generate_optional_builder_method_with_docs()); .map(|param| param.generate_optional_builder_method_with_docs());
let group_name = self.group.struct_name(); let group_name = self.group.struct_name();
let mandatory_params = self.mandatory_parameters(); let send_method = self.generate_send_method(
let mandatory_param_form_builder = self.mandatory_parameters_as_form_builder(); &util::to_ident("send"),
let send_method = self.generate_optional_builder_send_method(); vec![],
quote! { self.group.auth },
quote! { self.form },
quote! {},
);
quote! { quote! {
pub struct Builder<'a> { pub struct Builder<'a> {
@ -261,104 +330,55 @@ impl<'a> GroupMethod<'a> {
} }
impl<'a> Builder<'a> { impl<'a> Builder<'a> {
pub fn new(group: &'a super::#group_name, #mandatory_params) -> Self {
let form = reqwest::multipart::Form::new();
#mandatory_param_form_builder
Self { group, form }
}
#send_method #send_method
#(#builder_methods)* #(#builder_methods)*
} }
} }
} }
fn generate_optional_builder_send_method(&self) -> TokenStream { fn generate_send_method(
&self,
method_name: &Ident,
parameters: Vec<TokenStream>,
auth_access: TokenStream,
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);
match self.method.types.response() { let (response_type, response_parse) = match self.method.types.response() {
Some(_) => { Some(_) => (quote! { Response }, quote! { .json::<Response>() }),
quote! { None => (quote! { String }, quote! { .text() }),
pub async fn send(self) -> super::super::Result<Response> {
let res = self
.group
.auth
.authenticated_client(#method_url)
.multipart(self.form)
.send()
.await?
.json::<Response>()
.await?;
Ok(res)
}
}
}
None => {
quote! {
pub async fn send(self) -> super::super::Result<String> {
let res = self
.group
.auth
.authenticated_client(#method_url)
.multipart(self.form)
.send()
.await?
.text()
.await?;
Ok(res)
}
}
}
}
}
fn mandatory_parameters(&self) -> TokenStream {
let mandatory_params = match self.method.types.mandatory_params() {
Some(p) => p,
None => return quote! {},
}; };
let params = mandatory_params.iter().map(|param| param.to_parameter());
quote! { quote! {
#(#params),* pub async fn #method_name(self, #(#parameters),*) -> super::super::Result<#response_type> {
#form_factory
let res = #auth_access
.authenticated_client(#method_url)
.multipart(#form_access)
.send()
.await?
#response_parse
.await?;
Ok(res)
}
} }
} }
fn mandatory_parameters_as_form_builder(&self) -> TokenStream { fn mandatory_parameters_as_form_builder(&self) -> TokenStream {
let mandatory_params = match self.method.types.mandatory_params() { let builder = self
Some(p) => p, .method
None => return quote! {}, .types
}; .mandatory_params()
.into_iter()
let builder = mandatory_params
.iter()
.map(|param| param.generate_form_builder(quote! { form })); .map(|param| param.generate_form_builder(quote! { form }));
quote! { quote! {
#(let #builder)* #(let #builder)*
} }
} }
fn generate_method(&self) -> TokenStream {
let method_name = self.method.name_snake();
let structs = self.method.structs();
let enums = self.method.enums();
let builder = self.generate_optional_builder();
let response_struct = self.generate_response_struct();
quote! {
pub mod #method_name {
#structs
#enums
#builder
#response_struct
}
}
}
} }
impl types::Type { impl types::Type {

View File

@ -13,6 +13,7 @@ ApiMethod {
TypeInfo { TypeInfo {
name: "id", name: "id",
is_optional: false, is_optional: false,
is_list: false,
description: Some( description: Some(
"ID of the search job", "ID of the search job",
), ),
@ -22,6 +23,7 @@ ApiMethod {
TypeInfo { TypeInfo {
name: "limit", name: "limit",
is_optional: true, is_optional: true,
is_list: false,
description: Some( description: Some(
"max number of results to return. 0 or negative means no limit", "max number of results to return. 0 or negative means no limit",
), ),
@ -31,6 +33,7 @@ ApiMethod {
TypeInfo { TypeInfo {
name: "offset", name: "offset",
is_optional: true, is_optional: true,
is_list: false,
description: Some( description: Some(
"result to start at. A negative number means count backwards (e.g. -2 returns the 2 most recent results)", "result to start at. A negative number means count backwards (e.g. -2 returns the 2 most recent results)",
), ),
@ -47,6 +50,7 @@ ApiMethod {
TypeInfo { TypeInfo {
name: "descrLink", name: "descrLink",
is_optional: false, is_optional: false,
is_list: false,
description: Some( description: Some(
"URL of the torrent's description page", "URL of the torrent's description page",
), ),
@ -56,6 +60,7 @@ ApiMethod {
TypeInfo { TypeInfo {
name: "fileName", name: "fileName",
is_optional: false, is_optional: false,
is_list: false,
description: Some( description: Some(
"Name of the file", "Name of the file",
), ),
@ -65,6 +70,7 @@ ApiMethod {
TypeInfo { TypeInfo {
name: "fileSize", name: "fileSize",
is_optional: false, is_optional: false,
is_list: false,
description: Some( description: Some(
"Size of the file in Bytes", "Size of the file in Bytes",
), ),
@ -74,6 +80,7 @@ ApiMethod {
TypeInfo { TypeInfo {
name: "fileUrl", name: "fileUrl",
is_optional: false, is_optional: false,
is_list: false,
description: Some( description: Some(
"Torrent download link (usually either .torrent file or magnet link)", "Torrent download link (usually either .torrent file or magnet link)",
), ),
@ -83,6 +90,7 @@ ApiMethod {
TypeInfo { TypeInfo {
name: "nbLeechers", name: "nbLeechers",
is_optional: false, is_optional: false,
is_list: false,
description: Some( description: Some(
"Number of leechers", "Number of leechers",
), ),
@ -92,6 +100,7 @@ ApiMethod {
TypeInfo { TypeInfo {
name: "nbSeeders", name: "nbSeeders",
is_optional: false, is_optional: false,
is_list: false,
description: Some( description: Some(
"Number of seeders", "Number of seeders",
), ),
@ -101,6 +110,7 @@ ApiMethod {
TypeInfo { TypeInfo {
name: "siteUrl", name: "siteUrl",
is_optional: false, is_optional: false,
is_list: false,
description: Some( description: Some(
"URL of the torrent site", "URL of the torrent site",
), ),
@ -117,6 +127,7 @@ ApiMethod {
type_info: TypeInfo { type_info: TypeInfo {
name: "results", name: "results",
is_optional: false, is_optional: false,
is_list: false,
description: Some( description: Some(
"Array of result objects- see table below", "Array of result objects- see table below",
), ),
@ -128,6 +139,7 @@ ApiMethod {
TypeInfo { TypeInfo {
name: "status", name: "status",
is_optional: false, is_optional: false,
is_list: false,
description: Some( description: Some(
"Current status of the search job (either Running or Stopped)", "Current status of the search job (either Running or Stopped)",
), ),
@ -137,6 +149,7 @@ ApiMethod {
TypeInfo { TypeInfo {
name: "total", name: "total",
is_optional: false, is_optional: false,
is_list: false,
description: Some( description: Some(
"Total number of results. If the status is Running this number may continue to increase", "Total number of results. If the status is Running this number may continue to increase",
), ),

View File

@ -27,21 +27,30 @@ impl CompositeTypes {
} }
} }
pub fn parameters(&self) -> Option<&Vec<types::Type>> { pub fn parameters(&self) -> Vec<&types::Type> {
self.composite_types.iter().find_map(|type_| match type_ { self.composite_types
CompositeType::Parameters(p) => Some(&p.types), .iter()
_ => None, .find_map(|type_| match type_ {
}) CompositeType::Parameters(p) => Some(p.types.iter().collect()),
_ => None,
})
.unwrap_or_default()
} }
pub fn optional_parameters(&self) -> Option<Vec<&types::Type>> { pub fn optional_parameters(&self) -> Vec<&types::Type> {
self.parameters() self.parameters()
.map(|params| params.iter().filter(|param| param.is_optional()).collect()) .iter()
.filter(|param| param.is_optional())
.copied()
.collect()
} }
pub fn mandatory_params(&self) -> Option<Vec<&types::Type>> { pub fn mandatory_params(&self) -> Vec<&types::Type> {
self.parameters() self.parameters()
.map(|params| params.iter().filter(|param| !param.is_optional()).collect()) .iter()
.filter(|param| !param.is_optional())
.copied()
.collect()
} }
pub fn response(&self) -> Option<&Vec<types::Type>> { pub fn response(&self) -> Option<&Vec<types::Type>> {
@ -386,11 +395,6 @@ mod tests {
".check" ".check"
); );
// prevent user from accidentally leaving the current macro in a test
if Path::new(file).exists() {
panic!("Test case already exists: {file}");
}
fs::write(file, api_method_as_str).unwrap(); fs::write(file, api_method_as_str).unwrap();
fs::write(tree_file, tree_as_str).unwrap(); fs::write(tree_file, tree_as_str).unwrap();
}; };

View File

@ -16,16 +16,16 @@ pub struct TypeDescription {
pub struct TypeInfo { pub struct TypeInfo {
pub name: String, pub name: String,
pub is_optional: bool, pub is_optional: bool,
// is_list: bool, pub is_list: bool,
pub description: Option<String>, pub description: Option<String>,
} }
impl TypeInfo { impl TypeInfo {
pub fn new(name: &str, is_optional: bool, description: Option<String>) -> Self { pub fn new(name: &str, is_optional: bool, is_list: bool, description: Option<String>) -> Self {
Self { Self {
name: name.into(), name: name.into(),
is_optional, is_optional,
// is_list, is_list,
description, description,
} }
} }
@ -74,10 +74,6 @@ impl Type {
} }
} }
// pub fn is_list(&self) -> bool {
// self.get_type_info().is_list || matches!(self, Type::StringArray(_))
// }
pub fn to_borrowed_type(&self) -> String { pub fn to_borrowed_type(&self) -> String {
match self { match self {
Type::Number(_) => "i32".into(), Type::Number(_) => "i32".into(),
@ -116,7 +112,12 @@ impl Type {
.trim(); .trim();
let is_optional = name.contains(OPTIONAL); let is_optional = name.contains(OPTIONAL);
let create_type_info = || TypeInfo::new(type_name, is_optional, description.clone()); let is_list = description
.clone()
.map(|desc| desc.contains("array"))
.unwrap_or(false);
let create_type_info =
|| TypeInfo::new(type_name, is_optional, is_list, description.clone());
let create_object_type = |name: &str| { let create_object_type = |name: &str| {
Some(Type::Object(Object { Some(Type::Object(Object {
@ -126,6 +127,7 @@ impl Type {
}; };
match type_as_str { match type_as_str {
"raw" => None,
"bool" => Some(Type::Bool(create_type_info())), "bool" => Some(Type::Bool(create_type_info())),
"integer" | "number" | "int" => Some(Type::Number(create_type_info())), "integer" | "number" | "int" => Some(Type::Number(create_type_info())),
"string" => Some(Type::String(create_type_info())), "string" => Some(Type::String(create_type_info())),