diff --git a/Cargo.lock b/Cargo.lock index daf43ab..c54737b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -597,6 +597,7 @@ dependencies = [ "reqwest", "serde", "serde_json", + "serde_repr", "thiserror", "tokio", ] @@ -614,6 +615,7 @@ dependencies = [ "reqwest", "serde", "serde_json", + "serde_repr", "syn", "thiserror", "tokio", @@ -778,6 +780,17 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" diff --git a/Cargo.toml b/Cargo.toml index 823db59..17ed794 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ tokio = { version = "1.19.2", features = ["full"] } qbittorrent-web-api-gen = { path = "./qbittorrent-web-api-gen", version = "0.6.0" } serde = { version = "1.0.138", features = ["derive"] } serde_json = "1.0.82" +serde_repr = "0.1.9" thiserror = "1.0.31" [workspace] diff --git a/qbittorrent-web-api-gen/Cargo.toml b/qbittorrent-web-api-gen/Cargo.toml index 3bdcc52..f6fa4a8 100644 --- a/qbittorrent-web-api-gen/Cargo.toml +++ b/qbittorrent-web-api-gen/Cargo.toml @@ -32,6 +32,7 @@ case = "1.0.0" thiserror = "1.0.31" serde = { version = "1.0.138", features = ["derive"] } serde_json = "1.0.82" +serde_repr = "0.1.9" regex = "1.6.0" [dev-dependencies] diff --git a/qbittorrent-web-api-gen/api-4_1.md b/qbittorrent-web-api-gen/api-4_1.md index db5c267..9068f43 100644 --- a/qbittorrent-web-api-gen/api-4_1.md +++ b/qbittorrent-web-api-gen/api-4_1.md @@ -1667,7 +1667,7 @@ Possible values of `priority`: Value | Description -----------|------------ `0` | Do not download -`1` | Normal priority +`4` | Normal priority `6` | High priority `7` | Maximal priority @@ -2181,7 +2181,7 @@ Possible values of `priority`: Value | Description -----------|------------ `0` | Do not download -`1` | Normal priority +`4` | Normal priority `6` | High priority `7` | Maximal priority diff --git a/qbittorrent-web-api-gen/groups.txt b/qbittorrent-web-api-gen/groups.txt index d0d2dd7..d9b8b3b 100644 --- a/qbittorrent-web-api-gen/groups.txt +++ b/qbittorrent-web-api-gen/groups.txt @@ -4426,7 +4426,7 @@ "Normal priority", ), value: "NormalPriority", - original_value: "1", + original_value: "4", }, EnumValue { description: Some( @@ -5247,7 +5247,7 @@ "Normal priority", ), value: "NormalPriority", - original_value: "1", + original_value: "4", }, EnumValue { description: Some( diff --git a/qbittorrent-web-api-gen/src/generate/api_group.rs b/qbittorrent-web-api-gen/src/generate/api_group.rs index 7452bb8..c1df457 100644 --- a/qbittorrent-web-api-gen/src/generate/api_group.rs +++ b/qbittorrent-web-api-gen/src/generate/api_group.rs @@ -111,11 +111,22 @@ impl<'a> GroupGeneration<'a> { } pub fn struct_derives(&self) -> TokenStream { - self.derives(self.struct_derives, &[]) + self.derives( + self.struct_derives, + &["serde::Deserialize", "serde::Serialize"], + ) } pub fn enum_derives(&self) -> TokenStream { - self.derives(self.enum_derives, &["PartialEq", "Eq"]) + self.derives( + self.enum_derives, + &[ + "PartialEq", + "Eq", + "serde_repr::Deserialize_repr", + "serde_repr::Serialize_repr", + ], + ) } pub fn derives(&self, derives: &'a [&'a str], additional_derives: &[&str]) -> TokenStream { @@ -131,13 +142,8 @@ impl<'a> GroupGeneration<'a> { } fn all_derives(&self, derives: &'a [&'a str]) -> impl Iterator { - let base = vec!["serde::Deserialize", "serde::Serialize", "Debug"].into_iter(); - let additional = derives - .iter() - .copied() - .filter(|item| item != &"serde::Deserialize") - .filter(|item| item != &"serde::Serialize") - .filter(|item| item != &"Debug"); + let base = vec!["Debug"].into_iter(); + let additional = derives.iter().copied().filter(|item| item != &"Debug"); base.chain(additional) } diff --git a/qbittorrent-web-api-gen/src/generate/group.rs b/qbittorrent-web-api-gen/src/generate/group.rs index 5694bcf..8ca7aa0 100644 --- a/qbittorrent-web-api-gen/src/generate/group.rs +++ b/qbittorrent-web-api-gen/src/generate/group.rs @@ -143,6 +143,7 @@ impl<'a> EnumGeneration<'a> { quote! { #[allow(clippy::enum_variant_names)] #derives + #[repr(i8)] pub enum #name { #(#values,)* } @@ -160,11 +161,15 @@ impl parser::EnumValue { // special enum value which does not follow conventions if orig_name == "\"/path/to/download/to\"" { - quote! { - PathToDownloadTo(String) - } + // don't know how to handle this one + // quote! { PathToDownloadTo(String) } + return quote! { PathToDownloadTo = -1 }; + }; + + let name_camel = self.name_camel(); + if let Ok(v) = orig_name.parse::() { + quote! { #name_camel = #v } } else { - let name_camel = self.name_camel(); quote! { #[serde(rename = #orig_name)] #name_camel diff --git a/qbittorrent-web-api-gen/src/generate/group_method.rs b/qbittorrent-web-api-gen/src/generate/group_method.rs index c8b9461..2064c29 100644 --- a/qbittorrent-web-api-gen/src/generate/group_method.rs +++ b/qbittorrent-web-api-gen/src/generate/group_method.rs @@ -75,16 +75,15 @@ impl<'a> GroupMethod<'a> { 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 - }, - ) + let builder = + SendMethodBuilder::new(&method_name, quote! { self.auth }, quote! { form }) + .set_parameters(parameters) + .set_form_factory(quote! { + let form = reqwest::multipart::Form::new(); + #form_builder + }); + + self.generate_send_method(builder) } else { quote! { pub fn #method_name(&self, #(#parameters),*) -> Builder<'_> { @@ -139,13 +138,11 @@ impl<'a> GroupMethod<'a> { .map(|param| param.generate_optional_builder_method_with_docs()); let group_name = self.group.struct_name(); - let send_method = self.generate_send_method( + let send_method = self.generate_send_method(SendMethodBuilder::new( &util::to_ident("send"), - vec![], quote! { self.group.auth }, quote! { self.form }, - quote! {}, - ); + )); quote! { pub struct Builder<'a> { @@ -160,29 +157,70 @@ impl<'a> GroupMethod<'a> { } } - fn generate_send_method( - &self, - method_name: &Ident, - parameters: Vec, - auth_access: TokenStream, - form_access: TokenStream, - form_factory: TokenStream, - ) -> TokenStream { - let method_url = format!("/api/v2/{}/{}", self.group.url(), self.method.url); + fn generate_send_method(&self, builder: SendMethodBuilder) -> TokenStream { + let builder = builder.set_group(self); + let send_method = builder.generate_send_method(); + let send_method_raw = builder + .set_override_return_type(quote! { String }) + .append_to_method_name("_raw") + .generate_send_method(); - let (response_type, response_parse) = match self.method.types.response() { - Some(resp) => { - if resp.is_list { - ( - quote! { std::vec::Vec }, - quote! { .json::>() }, - ) - } else { - (quote! { Response }, quote! { .json::() }) - } - } - None => (quote! { String }, quote! { .text() }), - }; + let send_method_raw_with_docs = util::add_docs( + &Some("Returns the raw response of the request.".into()), + send_method_raw, + ); + + quote! { + #send_method + #send_method_raw_with_docs + } + } + + fn mandatory_parameters_as_form_builder(&self) -> TokenStream { + let builder = self + .method + .types + .mandatory_params() + .into_iter() + .map(|param| param.generate_form_builder(quote! { form })); + + quote! { + #(let #builder)* + } + } +} + +pub struct SendMethodBuilder<'a> { + group: Option<&'a GroupMethod<'a>>, + method_name: Ident, + parameters: Vec, + auth_access: TokenStream, + form_access: TokenStream, + form_factory: Option, + override_return_type: Option, +} + +impl<'a> SendMethodBuilder<'a> { + pub fn new(method_name: &'a Ident, auth_access: TokenStream, form_access: TokenStream) -> Self { + Self { + group: Option::None, + method_name: method_name.clone(), + parameters: vec![], + auth_access, + form_access, + form_factory: Option::None, + override_return_type: Option::None, + } + } + + fn generate_send_method(&self) -> TokenStream { + let method_url = self.method_url(); + let (response_type, response_parse) = self.return_type(); + let form_factory = self.form_factory(); + let parameters = self.parameters.clone(); + let form_access = self.form_access.clone(); + let method_name = self.method_name.clone(); + let auth_access = self.auth_access.clone(); quote! { pub async fn #method_name(self, #(#parameters),*) -> super::super::Result<#response_type> { @@ -200,16 +238,65 @@ impl<'a> GroupMethod<'a> { } } - fn mandatory_parameters_as_form_builder(&self) -> TokenStream { - let builder = self - .method - .types - .mandatory_params() - .into_iter() - .map(|param| param.generate_form_builder(quote! { form })); - - quote! { - #(let #builder)* + fn return_type(&self) -> (TokenStream, TokenStream) { + if let Some(override_return_type) = self.override_return_type.clone() { + (override_return_type, quote! { .text() }) + } else { + match self.group().method.types.response() { + Some(resp) => { + if resp.is_list { + ( + quote! { std::vec::Vec }, + quote! { .json::>() }, + ) + } else { + (quote! { Response }, quote! { .json::() }) + } + } + None => (quote! { String }, quote! { .text() }), + } } } + + fn method_url(&self) -> String { + format!( + "/api/v2/{}/{}", + self.group().group.url(), + self.group().method.url + ) + } + + fn set_override_return_type(mut self, override_return_type: TokenStream) -> Self { + self.override_return_type = Some(override_return_type); + self + } + + fn set_form_factory(mut self, form_factory: TokenStream) -> Self { + self.form_factory = Some(form_factory); + self + } + + fn form_factory(&self) -> TokenStream { + self.form_factory.clone().unwrap_or_else(|| quote! {}) + } + + fn set_parameters(mut self, parameters: Vec) -> Self { + self.parameters = parameters; + self + } + + fn append_to_method_name(mut self, append: &str) -> Self { + let new_name = util::to_ident(&format!("{}{}", self.method_name, append)); + self.method_name = new_name; + self + } + + fn set_group(mut self, group: &'a GroupMethod<'a>) -> Self { + self.group = Option::Some(group); + self + } + + fn group(&self) -> &GroupMethod { + self.group.expect("Group is not defined") + } } diff --git a/qbittorrent-web-api-gen/token_tree.txt b/qbittorrent-web-api-gen/token_tree.txt index 79d470c..64162ad 100644 --- a/qbittorrent-web-api-gen/token_tree.txt +++ b/qbittorrent-web-api-gen/token_tree.txt @@ -8007,9 +8007,9 @@ TokenTree { ], }, TableRow { - raw: "`1` | Normal priority", + raw: "`4` | Normal priority", columns: [ - "1", + "4", "Normal priority", ], }, @@ -10269,9 +10269,9 @@ TokenTree { ], }, TableRow { - raw: "`1` | Normal priority", + raw: "`4` | Normal priority", columns: [ - "1", + "4", "Normal priority", ], },