Progress
This commit is contained in:
parent
a93e8e7a02
commit
68bb159dc3
File diff suppressed because one or more lines are too long
|
@ -1,14 +1,11 @@
|
||||||
mod method;
|
use crate::{parser, types};
|
||||||
|
|
||||||
use crate::parser;
|
|
||||||
use case::CaseExt;
|
use case::CaseExt;
|
||||||
|
use proc_macro2::{Ident, TokenStream};
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
|
||||||
use self::method::generate_methods;
|
|
||||||
|
|
||||||
use super::{skeleton::auth_ident, util};
|
use super::{skeleton::auth_ident, util};
|
||||||
|
|
||||||
pub fn generate_groups(groups: Vec<parser::ApiGroup>) -> proc_macro2::TokenStream {
|
pub fn generate_groups(groups: Vec<parser::ApiGroup>) -> TokenStream {
|
||||||
let gr = groups
|
let gr = groups
|
||||||
.iter()
|
.iter()
|
||||||
// implemented manually
|
// implemented manually
|
||||||
|
@ -20,30 +17,410 @@ pub fn generate_groups(groups: Vec<parser::ApiGroup>) -> proc_macro2::TokenStrea
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_group(group: &parser::ApiGroup) -> proc_macro2::TokenStream {
|
fn generate_group(group: &parser::ApiGroup) -> TokenStream {
|
||||||
let group_name_camel = util::to_ident(&group.name.to_camel());
|
let group = group.generate();
|
||||||
let group_name_snake = util::to_ident(&group.name.to_snake());
|
|
||||||
let auth = auth_ident();
|
|
||||||
let methods = generate_methods(group, &auth, &group_name_camel);
|
|
||||||
|
|
||||||
let group_method = util::add_docs(
|
|
||||||
&group.description,
|
|
||||||
quote! {
|
|
||||||
pub fn #group_name_snake(&self) -> #group_name_camel {
|
|
||||||
#group_name_camel::new(self)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
pub struct #group_name_camel<'a> {
|
#group
|
||||||
auth: &'a #auth,
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl parser::ApiGroup {
|
||||||
|
fn generate(&self) -> TokenStream {
|
||||||
|
let struct_name = self.struct_name();
|
||||||
|
let group_name_snake = self.name_snake();
|
||||||
|
let group_methods = self.generate_group_methods();
|
||||||
|
|
||||||
|
let group_struct = self.group_struct();
|
||||||
|
let group_factory = self.group_factory();
|
||||||
|
let auth = auth_ident();
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
pub mod #group_name_snake {
|
||||||
|
impl <'a> #struct_name<'a> {
|
||||||
|
pub fn new(auth: &'a super::#auth) -> Self {
|
||||||
|
Self { auth }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#group_struct
|
||||||
|
#group_factory
|
||||||
|
|
||||||
|
#(#group_methods)*
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#methods
|
fn generate_group_methods(&self) -> Vec<TokenStream> {
|
||||||
|
let group_methods = self.group_methods();
|
||||||
|
group_methods
|
||||||
|
.iter()
|
||||||
|
.map(|group_method| group_method.generate_method())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
impl #auth {
|
fn group_factory(&self) -> TokenStream {
|
||||||
#group_method
|
let struct_name = self.struct_name();
|
||||||
|
let name_snake = self.name_snake();
|
||||||
|
let auth = auth_ident();
|
||||||
|
|
||||||
|
util::add_docs(
|
||||||
|
&self.description,
|
||||||
|
quote! {
|
||||||
|
impl super::#auth {
|
||||||
|
pub fn #name_snake(&self) -> #struct_name {
|
||||||
|
#struct_name::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn group_struct(&self) -> TokenStream {
|
||||||
|
let struct_name = self.struct_name();
|
||||||
|
let auth = auth_ident();
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct #struct_name<'a> {
|
||||||
|
auth: &'a super::#auth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn group_methods(&self) -> Vec<GroupMethod> {
|
||||||
|
self.methods
|
||||||
|
.iter()
|
||||||
|
.map(|method| GroupMethod::new(self, method))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn struct_name(&self) -> Ident {
|
||||||
|
self.name_camel()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name_camel(&self) -> Ident {
|
||||||
|
util::to_ident(&self.name.to_camel())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name_snake(&self) -> Ident {
|
||||||
|
util::to_ident(&self.name.to_snake())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl parser::ApiMethod {
|
||||||
|
fn structs(&self) -> TokenStream {
|
||||||
|
let objects = self.types.objects();
|
||||||
|
let structs = objects.iter().map(|obj| obj.generate_struct());
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#(#structs)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enums(&self) -> TokenStream {
|
||||||
|
let enums = self.types.enums();
|
||||||
|
let generated_enums = enums.iter().map(|e| e.generate());
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#(#generated_enums)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name_camel(&self) -> Ident {
|
||||||
|
util::to_ident(&self.name.to_camel())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name_snake(&self) -> Ident {
|
||||||
|
util::to_ident(&self.name.to_snake())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl parser::TypeWithName {
|
||||||
|
fn generate_struct(&self) -> TokenStream {
|
||||||
|
let fields = self.types.iter().map(|obj| obj.generate_struct_field());
|
||||||
|
let name = util::to_ident(&self.name);
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
pub struct #name {
|
||||||
|
#(#fields,)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl types::Type {
|
||||||
|
fn generate_struct_field(&self) -> TokenStream {
|
||||||
|
let name_snake = self.name_snake();
|
||||||
|
let type_name = util::to_ident(&self.to_owned_type());
|
||||||
|
let orig_name = self.name();
|
||||||
|
|
||||||
|
util::add_docs(
|
||||||
|
&self.get_type_info().description,
|
||||||
|
quote! {
|
||||||
|
#[serde(rename = #orig_name)]
|
||||||
|
#name_snake: #type_name
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> String {
|
||||||
|
self.get_type_info().name.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name_camel(&self) -> Ident {
|
||||||
|
util::to_ident(&self.name().to_camel())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name_snake(&self) -> Ident {
|
||||||
|
util::to_ident(&self.name().to_snake())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl parser::Enum {
|
||||||
|
fn generate(&self) -> TokenStream {
|
||||||
|
let values = self.values.iter().map(|enum_value| enum_value.generate());
|
||||||
|
let name = util::to_ident(&self.name);
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#[allow(clippy::enum_variant_names)]
|
||||||
|
#[derive(Debug, serde::Deserialize, PartialEq, Eq)]
|
||||||
|
pub enum #name {
|
||||||
|
#(#values,)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl parser::EnumValue {
|
||||||
|
fn generate(&self) -> TokenStream {
|
||||||
|
util::add_docs(&self.description, self.generate_field())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_field(&self) -> TokenStream {
|
||||||
|
let orig_name = self.original_value.clone();
|
||||||
|
|
||||||
|
// special enum value which does not follow conventions
|
||||||
|
if orig_name == "\"/path/to/download/to\"" {
|
||||||
|
quote! {
|
||||||
|
PathToDownloadTo(String)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let name_camel = self.name_camel();
|
||||||
|
quote! {
|
||||||
|
#[serde(rename = #orig_name)]
|
||||||
|
#name_camel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name_camel(&self) -> Ident {
|
||||||
|
util::to_ident(&self.value.to_camel())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name_snake(&self) -> Ident {
|
||||||
|
util::to_ident(&self.value.to_snake())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct GroupMethod<'a> {
|
||||||
|
group: &'a parser::ApiGroup,
|
||||||
|
method: &'a parser::ApiMethod,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> GroupMethod<'a> {
|
||||||
|
fn new(group: &'a parser::ApiGroup, method: &'a parser::ApiMethod) -> Self {
|
||||||
|
Self { group, method }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_response_struct(&self) -> TokenStream {
|
||||||
|
let response = match self.method.types.response() {
|
||||||
|
Some(res) => res,
|
||||||
|
None => return quote! {},
|
||||||
|
};
|
||||||
|
|
||||||
|
let struct_fields = response.iter().map(|field| field.generate_struct_field());
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#[derive(Debug, serde::Deserialize)]
|
||||||
|
pub struct Response {
|
||||||
|
#(#struct_fields,)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_optional_builder(&self) -> TokenStream {
|
||||||
|
let optional_params = match self.method.types.optional_parameters() {
|
||||||
|
Some(params) => params,
|
||||||
|
None => return quote! {},
|
||||||
|
};
|
||||||
|
|
||||||
|
let builder_methods = optional_params
|
||||||
|
.iter()
|
||||||
|
.map(|param| param.generate_optional_builder_method_with_docs());
|
||||||
|
|
||||||
|
let group_name = self.group.struct_name();
|
||||||
|
let mandatory_params = self.mandatory_parameters();
|
||||||
|
let mandatory_param_form_builder = self.mandatory_parameters_as_form_builder();
|
||||||
|
let send_method = self.generate_optional_builder_send_method();
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
pub struct Builder<'a> {
|
||||||
|
group: &'a super::#group_name<'a>,
|
||||||
|
form: reqwest::multipart::Form,
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
#(#builder_methods)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_optional_builder_send_method(&self) -> TokenStream {
|
||||||
|
let method_url = format!("/api/v2/{}/{}", self.group.url, self.method.url);
|
||||||
|
|
||||||
|
match self.method.types.response() {
|
||||||
|
Some(_) => {
|
||||||
|
quote! {
|
||||||
|
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! {
|
||||||
|
#(#params),*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn mandatory_parameters_as_form_builder(&self) -> TokenStream {
|
||||||
|
let mandatory_params = match self.method.types.mandatory_params() {
|
||||||
|
Some(p) => p,
|
||||||
|
None => return quote! {},
|
||||||
|
};
|
||||||
|
|
||||||
|
let builder = mandatory_params
|
||||||
|
.iter()
|
||||||
|
.map(|param| param.generate_form_builder(quote! { form }));
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#(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 {
|
||||||
|
fn generate_optional_builder_method_with_docs(&self) -> TokenStream {
|
||||||
|
util::add_docs(
|
||||||
|
&self.get_type_info().description,
|
||||||
|
self.generate_optional_builder_method(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn borrowed_type_ident(&self) -> Ident {
|
||||||
|
util::to_ident(&self.to_borrowed_type())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_parameter(&self) -> TokenStream {
|
||||||
|
let name_snake = self.name_snake();
|
||||||
|
let borrowed_type = self.borrowed_type();
|
||||||
|
|
||||||
|
quote! { #name_snake: #borrowed_type }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_form_builder(&self, add_to: TokenStream) -> TokenStream {
|
||||||
|
let name_str = self.name();
|
||||||
|
let name_snake = self.name_snake();
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
#add_to = #add_to.text(#name_str, #name_snake.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generate_optional_builder_method(&self) -> TokenStream {
|
||||||
|
let name_snake = self.name_snake();
|
||||||
|
let borrowed_type = self.borrowed_type();
|
||||||
|
let form_builder = self.generate_form_builder(quote! { self.form });
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
pub fn #name_snake(mut self, #name_snake: #borrowed_type) -> Self {
|
||||||
|
#form_builder;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn borrowed_type(&self) -> TokenStream {
|
||||||
|
if self.should_borrow() {
|
||||||
|
let type_ = self.borrowed_type_ident();
|
||||||
|
quote! { &#type_ }
|
||||||
|
} else {
|
||||||
|
let type_ = self.borrowed_type_ident();
|
||||||
|
quote! { #type_ }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,13 +10,7 @@ pub fn generate_skeleton(ident: &syn::Ident) -> proc_macro2::TokenStream {
|
||||||
let auth = auth_ident();
|
let auth = auth_ident();
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
use reqwest::RequestBuilder;
|
impl super::#ident {
|
||||||
use serde::Deserialize;
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use super::#ident;
|
|
||||||
|
|
||||||
impl #ident {
|
|
||||||
/// Creates an authenticated client.
|
/// Creates an authenticated client.
|
||||||
/// base_url is the url to the qbittorrent instance, i.e. http://localhost:8080
|
/// base_url is the url to the qbittorrent instance, i.e. http://localhost:8080
|
||||||
pub async fn login(
|
pub async fn login(
|
||||||
|
@ -61,7 +55,7 @@ pub fn generate_skeleton(ident: &syn::Ident) -> proc_macro2::TokenStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::enum_variant_names)]
|
#[allow(clippy::enum_variant_names)]
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[error("failed to parse auth cookie")]
|
#[error("failed to parse auth cookie")]
|
||||||
AuthCookieParseError,
|
AuthCookieParseError,
|
||||||
|
@ -81,7 +75,7 @@ pub fn generate_skeleton(ident: &syn::Ident) -> proc_macro2::TokenStream {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl #auth {
|
impl #auth {
|
||||||
fn authenticated_client(&self, url: &str) -> RequestBuilder {
|
fn authenticated_client(&self, url: &str) -> reqwest::RequestBuilder {
|
||||||
let url = format!("{}{}", self.base_url, url);
|
let url = format!("{}{}", self.base_url, url);
|
||||||
let cookie = self.auth_cookie.clone();
|
let cookie = self.auth_cookie.clone();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
ApiMethod {
|
||||||
|
name: "foo",
|
||||||
|
description: None,
|
||||||
|
url: "foo",
|
||||||
|
types: CompositeTypes {
|
||||||
|
composite_types: [
|
||||||
|
Enum(
|
||||||
|
Enum {
|
||||||
|
name: "ScanDirs",
|
||||||
|
values: [
|
||||||
|
EnumValue {
|
||||||
|
description: Some(
|
||||||
|
"Download to the monitored folder",
|
||||||
|
),
|
||||||
|
value: "DownloadToTheMonitoredFolder",
|
||||||
|
original_value: "0",
|
||||||
|
},
|
||||||
|
EnumValue {
|
||||||
|
description: Some(
|
||||||
|
"Download to the default save path",
|
||||||
|
),
|
||||||
|
value: "DownloadToTheDefaultSavePath",
|
||||||
|
original_value: "1",
|
||||||
|
},
|
||||||
|
EnumValue {
|
||||||
|
description: Some(
|
||||||
|
"Download to this path",
|
||||||
|
),
|
||||||
|
value: "\"/path/to/download/to\"",
|
||||||
|
original_value: "\"/path/to/download/to\"",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Name: `foo`
|
||||||
|
|
||||||
|
Possible values of `scan_dirs`:
|
||||||
|
|
||||||
|
Value | Description
|
||||||
|
----------------------------|------------
|
||||||
|
`0` | Download to the monitored folder
|
||||||
|
`1` | Download to the default save path
|
||||||
|
`"/path/to/download/to"` | Download to this path
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
TokenTree {
|
||||||
|
title: None,
|
||||||
|
content: [],
|
||||||
|
children: [
|
||||||
|
TokenTree {
|
||||||
|
title: Some(
|
||||||
|
"Testing",
|
||||||
|
),
|
||||||
|
content: [
|
||||||
|
Text(
|
||||||
|
"",
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Name: `foo`",
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"",
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"Possible values of `scan_dirs`:",
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"",
|
||||||
|
),
|
||||||
|
Table(
|
||||||
|
Table {
|
||||||
|
header: TableRow {
|
||||||
|
raw: "Value | Description",
|
||||||
|
columns: [
|
||||||
|
"Value",
|
||||||
|
"Description",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
split: "----------------------------|------------",
|
||||||
|
rows: [
|
||||||
|
TableRow {
|
||||||
|
raw: "`0` | Download to the monitored folder",
|
||||||
|
columns: [
|
||||||
|
"0",
|
||||||
|
"Download to the monitored folder",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
TableRow {
|
||||||
|
raw: "`1` | Download to the default save path",
|
||||||
|
columns: [
|
||||||
|
"1",
|
||||||
|
"Download to the default save path",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
TableRow {
|
||||||
|
raw: "`\"/path/to/download/to\"` | Download to this path",
|
||||||
|
columns: [
|
||||||
|
"\"/path/to/download/to\"",
|
||||||
|
"Download to this path",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
|
@ -3,98 +3,174 @@ ApiMethod {
|
||||||
description: Some(
|
description: Some(
|
||||||
"The response is a JSON object with the following fields\n\n\n\n\nExample:\n\n```JSON\n{\n \"results\": [\n {\n \"descrLink\": \"http://www.legittorrents.info/index.php?page=torrent-details&id=8d5f512e1acb687029b8d7cc6c5a84dce51d7a41\",\n \"fileName\": \"Ubuntu-10.04-32bit-NeTV.ova\",\n \"fileSize\": -1,\n \"fileUrl\": \"http://www.legittorrents.info/download.php?id=8d5f512e1acb687029b8d7cc6c5a84dce51d7a41&f=Ubuntu-10.04-32bit-NeTV.ova.torrent\",\n \"nbLeechers\": 1,\n \"nbSeeders\": 0,\n \"siteUrl\": \"http://www.legittorrents.info\"\n },\n {\n \"descrLink\": \"http://www.legittorrents.info/index.php?page=torrent-details&id=d5179f53e105dc2c2401bcfaa0c2c4936a6aa475\",\n \"fileName\": \"mangOH-Legato-17_06-Ubuntu-16_04.ova\",\n \"fileSize\": -1,\n \"fileUrl\": \"http://www.legittorrents.info/download.php?id=d5179f53e105dc2c2401bcfaa0c2c4936a6aa475&f=mangOH-Legato-17_06-Ubuntu-16_04.ova.torrent\",\n \"nbLeechers\": 0,\n \"nbSeeders\": 59,\n \"siteUrl\": \"http://www.legittorrents.info\"\n }\n ],\n \"status\": \"Running\",\n \"total\": 2\n}\n```",
|
"The response is a JSON object with the following fields\n\n\n\n\nExample:\n\n```JSON\n{\n \"results\": [\n {\n \"descrLink\": \"http://www.legittorrents.info/index.php?page=torrent-details&id=8d5f512e1acb687029b8d7cc6c5a84dce51d7a41\",\n \"fileName\": \"Ubuntu-10.04-32bit-NeTV.ova\",\n \"fileSize\": -1,\n \"fileUrl\": \"http://www.legittorrents.info/download.php?id=8d5f512e1acb687029b8d7cc6c5a84dce51d7a41&f=Ubuntu-10.04-32bit-NeTV.ova.torrent\",\n \"nbLeechers\": 1,\n \"nbSeeders\": 0,\n \"siteUrl\": \"http://www.legittorrents.info\"\n },\n {\n \"descrLink\": \"http://www.legittorrents.info/index.php?page=torrent-details&id=d5179f53e105dc2c2401bcfaa0c2c4936a6aa475\",\n \"fileName\": \"mangOH-Legato-17_06-Ubuntu-16_04.ova\",\n \"fileSize\": -1,\n \"fileUrl\": \"http://www.legittorrents.info/download.php?id=d5179f53e105dc2c2401bcfaa0c2c4936a6aa475&f=mangOH-Legato-17_06-Ubuntu-16_04.ova.torrent\",\n \"nbLeechers\": 0,\n \"nbSeeders\": 59,\n \"siteUrl\": \"http://www.legittorrents.info\"\n }\n ],\n \"status\": \"Running\",\n \"total\": 2\n}\n```",
|
||||||
),
|
),
|
||||||
parameters: Some(
|
|
||||||
ApiParameters {
|
|
||||||
mandatory: [
|
|
||||||
Number(
|
|
||||||
TypeInfo {
|
|
||||||
name: "id",
|
|
||||||
is_optional: false,
|
|
||||||
is_list: false,
|
|
||||||
description: Some(
|
|
||||||
"ID of the search job",
|
|
||||||
),
|
|
||||||
type_description: None,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
optional: [
|
|
||||||
Number(
|
|
||||||
TypeInfo {
|
|
||||||
name: "limit",
|
|
||||||
is_optional: true,
|
|
||||||
is_list: false,
|
|
||||||
description: Some(
|
|
||||||
"max number of results to return. 0 or negative means no limit",
|
|
||||||
),
|
|
||||||
type_description: None,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Number(
|
|
||||||
TypeInfo {
|
|
||||||
name: "offset",
|
|
||||||
is_optional: true,
|
|
||||||
is_list: false,
|
|
||||||
description: Some(
|
|
||||||
"result to start at. A negative number means count backwards (e.g. -2 returns the 2 most recent results)",
|
|
||||||
),
|
|
||||||
type_description: None,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
return_type: Some(
|
|
||||||
ReturnType {
|
|
||||||
is_list: false,
|
|
||||||
parameters: [
|
|
||||||
ReturnTypeParameter {
|
|
||||||
name: "results",
|
|
||||||
description: "Array of result objects- see table below",
|
|
||||||
return_type: StringArray(
|
|
||||||
TypeInfo {
|
|
||||||
name: "results",
|
|
||||||
is_optional: false,
|
|
||||||
is_list: false,
|
|
||||||
description: Some(
|
|
||||||
"Array of result objects- see table below",
|
|
||||||
),
|
|
||||||
type_description: None,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
ReturnTypeParameter {
|
|
||||||
name: "status",
|
|
||||||
description: "Current status of the search job (either Running or Stopped)",
|
|
||||||
return_type: String(
|
|
||||||
TypeInfo {
|
|
||||||
name: "status",
|
|
||||||
is_optional: false,
|
|
||||||
is_list: false,
|
|
||||||
description: Some(
|
|
||||||
"Current status of the search job (either Running or Stopped)",
|
|
||||||
),
|
|
||||||
type_description: None,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
ReturnTypeParameter {
|
|
||||||
name: "total",
|
|
||||||
description: "Total number of results. If the status is Running this number may continue to increase",
|
|
||||||
return_type: Number(
|
|
||||||
TypeInfo {
|
|
||||||
name: "total",
|
|
||||||
is_optional: false,
|
|
||||||
is_list: false,
|
|
||||||
description: Some(
|
|
||||||
"Total number of results. If the status is Running this number may continue to increase",
|
|
||||||
),
|
|
||||||
type_description: None,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
url: "results",
|
url: "results",
|
||||||
|
types: CompositeTypes {
|
||||||
|
composite_types: [
|
||||||
|
Parameters(
|
||||||
|
TypeWithoutName {
|
||||||
|
types: [
|
||||||
|
Number(
|
||||||
|
TypeInfo {
|
||||||
|
name: "id",
|
||||||
|
is_optional: false,
|
||||||
|
is_list: false,
|
||||||
|
description: Some(
|
||||||
|
"ID of the search job",
|
||||||
|
),
|
||||||
|
type_description: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Number(
|
||||||
|
TypeInfo {
|
||||||
|
name: "limit",
|
||||||
|
is_optional: true,
|
||||||
|
is_list: false,
|
||||||
|
description: Some(
|
||||||
|
"max number of results to return. 0 or negative means no limit",
|
||||||
|
),
|
||||||
|
type_description: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Number(
|
||||||
|
TypeInfo {
|
||||||
|
name: "offset",
|
||||||
|
is_optional: true,
|
||||||
|
is_list: false,
|
||||||
|
description: Some(
|
||||||
|
"result to start at. A negative number means count backwards (e.g. -2 returns the 2 most recent results)",
|
||||||
|
),
|
||||||
|
type_description: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Object(
|
||||||
|
TypeWithName {
|
||||||
|
name: "Result",
|
||||||
|
types: [
|
||||||
|
String(
|
||||||
|
TypeInfo {
|
||||||
|
name: "descrLink",
|
||||||
|
is_optional: false,
|
||||||
|
is_list: false,
|
||||||
|
description: Some(
|
||||||
|
"URL of the torrent's description page",
|
||||||
|
),
|
||||||
|
type_description: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
String(
|
||||||
|
TypeInfo {
|
||||||
|
name: "fileName",
|
||||||
|
is_optional: false,
|
||||||
|
is_list: false,
|
||||||
|
description: Some(
|
||||||
|
"Name of the file",
|
||||||
|
),
|
||||||
|
type_description: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Number(
|
||||||
|
TypeInfo {
|
||||||
|
name: "fileSize",
|
||||||
|
is_optional: false,
|
||||||
|
is_list: false,
|
||||||
|
description: Some(
|
||||||
|
"Size of the file in Bytes",
|
||||||
|
),
|
||||||
|
type_description: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
String(
|
||||||
|
TypeInfo {
|
||||||
|
name: "fileUrl",
|
||||||
|
is_optional: false,
|
||||||
|
is_list: false,
|
||||||
|
description: Some(
|
||||||
|
"Torrent download link (usually either .torrent file or magnet link)",
|
||||||
|
),
|
||||||
|
type_description: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Number(
|
||||||
|
TypeInfo {
|
||||||
|
name: "nbLeechers",
|
||||||
|
is_optional: false,
|
||||||
|
is_list: false,
|
||||||
|
description: Some(
|
||||||
|
"Number of leechers",
|
||||||
|
),
|
||||||
|
type_description: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Number(
|
||||||
|
TypeInfo {
|
||||||
|
name: "nbSeeders",
|
||||||
|
is_optional: false,
|
||||||
|
is_list: false,
|
||||||
|
description: Some(
|
||||||
|
"Number of seeders",
|
||||||
|
),
|
||||||
|
type_description: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
String(
|
||||||
|
TypeInfo {
|
||||||
|
name: "siteUrl",
|
||||||
|
is_optional: false,
|
||||||
|
is_list: false,
|
||||||
|
description: Some(
|
||||||
|
"URL of the torrent site",
|
||||||
|
),
|
||||||
|
type_description: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Response(
|
||||||
|
TypeWithoutName {
|
||||||
|
types: [
|
||||||
|
Object(
|
||||||
|
Object {
|
||||||
|
type_info: TypeInfo {
|
||||||
|
name: "results",
|
||||||
|
is_optional: false,
|
||||||
|
is_list: false,
|
||||||
|
description: Some(
|
||||||
|
"Array of result objects- see table below",
|
||||||
|
),
|
||||||
|
type_description: None,
|
||||||
|
},
|
||||||
|
ref_type: "Result",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
String(
|
||||||
|
TypeInfo {
|
||||||
|
name: "status",
|
||||||
|
is_optional: false,
|
||||||
|
is_list: false,
|
||||||
|
description: Some(
|
||||||
|
"Current status of the search job (either Running or Stopped)",
|
||||||
|
),
|
||||||
|
type_description: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Number(
|
||||||
|
TypeInfo {
|
||||||
|
name: "total",
|
||||||
|
is_optional: false,
|
||||||
|
is_list: false,
|
||||||
|
description: Some(
|
||||||
|
"Total number of results. If the status is Running this number may continue to increase",
|
||||||
|
),
|
||||||
|
type_description: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
}
|
}
|
|
@ -1,18 +1,75 @@
|
||||||
mod description;
|
mod description;
|
||||||
mod return_type;
|
// mod return_type;
|
||||||
mod url;
|
mod url;
|
||||||
|
|
||||||
use crate::{md_parser, types};
|
use crate::{md_parser, types};
|
||||||
pub use return_type::ReturnType;
|
use case::CaseExt;
|
||||||
use std::collections::HashMap;
|
use regex::Regex;
|
||||||
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ApiMethod {
|
pub struct ApiMethod {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub description: Option<String>,
|
pub description: Option<String>,
|
||||||
pub parameters: Option<ApiParameters>,
|
|
||||||
pub return_type: Option<ReturnType>,
|
|
||||||
pub url: String,
|
pub url: String,
|
||||||
|
pub types: CompositeTypes,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct CompositeTypes {
|
||||||
|
pub composite_types: Vec<CompositeType>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompositeTypes {
|
||||||
|
pub fn new(tables: &Tables) -> Self {
|
||||||
|
Self {
|
||||||
|
composite_types: tables.get_all_tables_as_types(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parameters(&self) -> Option<&Vec<types::Type>> {
|
||||||
|
self.composite_types.iter().find_map(|type_| match type_ {
|
||||||
|
CompositeType::Parameters(p) => Some(&p.types),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn optional_parameters(&self) -> Option<Vec<&types::Type>> {
|
||||||
|
self.parameters()
|
||||||
|
.map(|params| params.iter().filter(|param| param.is_optional()).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mandatory_params(&self) -> Option<Vec<&types::Type>> {
|
||||||
|
self.parameters()
|
||||||
|
.map(|params| params.iter().filter(|param| !param.is_optional()).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn response(&self) -> Option<&Vec<types::Type>> {
|
||||||
|
self.composite_types.iter().find_map(|type_| match type_ {
|
||||||
|
CompositeType::Response(p) => Some(&p.types),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn objects(&self) -> Vec<&TypeWithName> {
|
||||||
|
self.composite_types
|
||||||
|
.iter()
|
||||||
|
.filter_map(|type_| match type_ {
|
||||||
|
CompositeType::Object(p) => Some(p),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enums(&self) -> Vec<&Enum> {
|
||||||
|
self.composite_types
|
||||||
|
.iter()
|
||||||
|
.filter_map(|type_| match type_ {
|
||||||
|
CompositeType::Enum(p) => Some(p),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -21,24 +78,86 @@ pub struct ApiParameters {
|
||||||
pub optional: Vec<types::Type>,
|
pub optional: Vec<types::Type>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ApiParameters {
|
#[derive(Debug)]
|
||||||
fn new(params: Vec<types::Type>) -> Self {
|
pub enum CompositeType {
|
||||||
let (mandatory, optional) = params.into_iter().fold(
|
Enum(Enum),
|
||||||
(vec![], vec![]),
|
Object(TypeWithName),
|
||||||
|(mut mandatory, mut optional), parameter| {
|
Response(TypeWithoutName),
|
||||||
if parameter.get_type_info().is_optional {
|
Parameters(TypeWithoutName),
|
||||||
optional.push(parameter);
|
}
|
||||||
} else {
|
|
||||||
mandatory.push(parameter);
|
|
||||||
}
|
|
||||||
|
|
||||||
(mandatory, optional)
|
#[derive(Debug)]
|
||||||
},
|
pub struct TypeWithName {
|
||||||
);
|
pub name: String,
|
||||||
|
pub types: Vec<types::Type>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TypeWithoutName {
|
||||||
|
pub types: Vec<types::Type>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeWithoutName {
|
||||||
|
pub fn new(types: Vec<types::Type>) -> Self {
|
||||||
|
Self { types }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeWithName {
|
||||||
|
pub fn new(name: &str, types: Vec<types::Type>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
mandatory,
|
name: name.to_string(),
|
||||||
optional,
|
types,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Enum {
|
||||||
|
pub name: String,
|
||||||
|
pub values: Vec<EnumValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct EnumValue {
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub value: String,
|
||||||
|
pub original_value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Enum {
|
||||||
|
fn new(name: &str, table: &md_parser::Table) -> Self {
|
||||||
|
let values = table.rows.iter().map(EnumValue::from).collect();
|
||||||
|
|
||||||
|
Enum {
|
||||||
|
name: name.to_string(),
|
||||||
|
values,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&md_parser::TableRow> for EnumValue {
|
||||||
|
fn from(row: &md_parser::TableRow) -> Self {
|
||||||
|
let description = row.columns.get(1).cloned();
|
||||||
|
let original_value = row.columns[0].clone();
|
||||||
|
let value = if original_value.parse::<i32>().is_ok() {
|
||||||
|
let name = description
|
||||||
|
.clone()
|
||||||
|
.unwrap()
|
||||||
|
.replace(' ', "_")
|
||||||
|
.replace('-', "_")
|
||||||
|
.replace(',', "_");
|
||||||
|
|
||||||
|
let re = Regex::new(r#"\(.*\)"#).unwrap();
|
||||||
|
re.replace_all(&name, "").to_camel()
|
||||||
|
} else {
|
||||||
|
original_value.to_camel()
|
||||||
|
};
|
||||||
|
|
||||||
|
EnumValue {
|
||||||
|
description,
|
||||||
|
value,
|
||||||
|
original_value,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,19 +175,13 @@ impl ApiMethod {
|
||||||
fn new(child: &md_parser::TokenTree, name: &str) -> Self {
|
fn new(child: &md_parser::TokenTree, name: &str) -> Self {
|
||||||
let tables = Tables::from(child);
|
let tables = Tables::from(child);
|
||||||
let method_description = child.parse_method_description();
|
let method_description = child.parse_method_description();
|
||||||
let return_type = child.parse_return_type();
|
|
||||||
// let return_type = tables.return_type().map(|r| ReturnType::new(r));
|
|
||||||
let parameters = tables
|
|
||||||
.get_type_containing("Parameters")
|
|
||||||
.map(ApiParameters::new);
|
|
||||||
let method_url = child.get_method_url();
|
let method_url = child.get_method_url();
|
||||||
|
|
||||||
ApiMethod {
|
ApiMethod {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
description: method_description,
|
description: method_description,
|
||||||
parameters,
|
|
||||||
return_type,
|
|
||||||
url: method_url,
|
url: method_url,
|
||||||
|
types: CompositeTypes::new(&tables),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,7 +199,7 @@ impl md_parser::TokenTree {
|
||||||
|
|
||||||
impl<'a> From<&'a md_parser::TokenTree> for Tables<'a> {
|
impl<'a> From<&'a md_parser::TokenTree> for Tables<'a> {
|
||||||
fn from(token_tree: &'a md_parser::TokenTree) -> Self {
|
fn from(token_tree: &'a md_parser::TokenTree) -> Self {
|
||||||
let mut tables = HashMap::new();
|
let mut tables = BTreeMap::new();
|
||||||
let mut prev_prev: Option<&md_parser::MdContent> = None;
|
let mut prev_prev: Option<&md_parser::MdContent> = None;
|
||||||
let mut prev: Option<&md_parser::MdContent> = None;
|
let mut prev: Option<&md_parser::MdContent> = None;
|
||||||
|
|
||||||
|
@ -110,28 +223,80 @@ impl<'a> From<&'a md_parser::TokenTree> for Tables<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Tables<'a> {
|
pub struct Tables<'a> {
|
||||||
tables: HashMap<String, &'a md_parser::Table>,
|
tables: BTreeMap<String, &'a md_parser::Table>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl md_parser::Table {
|
||||||
|
fn to_enum(&self, input_name: &str) -> Option<CompositeType> {
|
||||||
|
let re = Regex::new(r"^Possible values of `(\w+)`$").unwrap();
|
||||||
|
|
||||||
|
if !re.is_match(input_name) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(CompositeType::Enum(Enum::new(
|
||||||
|
&Self::regex_to_name(&re, input_name),
|
||||||
|
self,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_object(&self, input_name: &str) -> Option<CompositeType> {
|
||||||
|
let re = Regex::new(r"^(\w+) object$").unwrap();
|
||||||
|
|
||||||
|
if !re.is_match(input_name) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(CompositeType::Object(TypeWithName::new(
|
||||||
|
&Self::regex_to_name(&re, input_name),
|
||||||
|
self.to_types(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_response(&self, input_name: &str) -> Option<CompositeType> {
|
||||||
|
if !input_name.starts_with("The response is a") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(CompositeType::Response(TypeWithoutName::new(
|
||||||
|
self.to_types(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_parameters(&self, input_name: &str) -> Option<CompositeType> {
|
||||||
|
if !input_name.starts_with("Parameters") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(CompositeType::Parameters(TypeWithoutName::new(
|
||||||
|
self.to_types(),
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn to_composite_type(&self, input_name: &str) -> Option<CompositeType> {
|
||||||
|
self.to_enum(input_name)
|
||||||
|
.or_else(|| self.to_response(input_name))
|
||||||
|
.or_else(|| self.to_object(input_name))
|
||||||
|
.or_else(|| self.to_parameters(input_name))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn regex_to_name(re: &Regex, input_name: &str) -> String {
|
||||||
|
re.captures(input_name)
|
||||||
|
.unwrap()
|
||||||
|
.get(1)
|
||||||
|
.unwrap()
|
||||||
|
.as_str()
|
||||||
|
.to_string()
|
||||||
|
.to_camel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Tables<'a> {
|
impl<'a> Tables<'a> {
|
||||||
fn get_type_containing(&self, name: &str) -> Option<Vec<types::Type>> {
|
fn get_all_tables_as_types(&self) -> Vec<CompositeType> {
|
||||||
self.get_type_containing_as_table(name)
|
|
||||||
.map(|table| table.to_types())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_type_containing_as_table(&self, name: &str) -> Option<&md_parser::Table> {
|
|
||||||
self.get_all_type_containing_as_table(name)
|
|
||||||
.iter()
|
|
||||||
.map(|(_, table)| *table)
|
|
||||||
.find(|_| true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_all_type_containing_as_table(&self, name: &str) -> HashMap<String, &md_parser::Table> {
|
|
||||||
self.tables
|
self.tables
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(key, _)| key.contains(name))
|
.flat_map(|(k, v)| v.to_composite_type(k))
|
||||||
.map(|(k, table)| (k.clone(), *table))
|
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -207,8 +372,8 @@ mod tests {
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
let input = include_str!(concat!(TEST_DIR!(), "/", $test_file, ".md"));
|
let input = include_str!(concat!(TEST_DIR!(), "/", $test_file, ".md"));
|
||||||
let tree = ApiMethod::try_new(input);
|
let tree = TokenTreeFactory::create(input);
|
||||||
let api_method = parse_api_method(&tree.children[0]).unwrap();
|
let api_method = ApiMethod::try_new(&tree.children[0]).unwrap();
|
||||||
|
|
||||||
let tree_as_str = format!("{tree:#?}");
|
let tree_as_str = format!("{tree:#?}");
|
||||||
let api_method_as_str = format!("{api_method:#?}");
|
let api_method_as_str = format!("{api_method:#?}");
|
||||||
|
@ -242,4 +407,9 @@ mod tests {
|
||||||
fn search_result() {
|
fn search_result() {
|
||||||
run_test!("search_result");
|
run_test!("search_result");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn enum_test() {
|
||||||
|
run_test!("enum");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use case::CaseExt;
|
||||||
|
use regex::RegexBuilder;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TypeDescriptions {
|
pub struct TypeDescriptions {
|
||||||
pub value: String,
|
pub value: String,
|
||||||
|
@ -39,15 +42,22 @@ impl TypeInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct TypeWithRef {
|
pub struct Object {
|
||||||
pub type_info: TypeInfo,
|
pub type_info: TypeInfo,
|
||||||
pub ref_type: String,
|
pub ref_type: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ComplexObject {
|
pub struct Enum {
|
||||||
pub type_info: TypeInfo,
|
pub type_info: TypeInfo,
|
||||||
pub fields: Vec<Type>,
|
pub values: Vec<EnumValue>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct EnumValue {
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub key: String,
|
||||||
|
pub value: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const OPTIONAL: &str = "_optional_";
|
pub const OPTIONAL: &str = "_optional_";
|
||||||
|
@ -59,8 +69,7 @@ pub enum Type {
|
||||||
Bool(TypeInfo),
|
Bool(TypeInfo),
|
||||||
String(TypeInfo),
|
String(TypeInfo),
|
||||||
StringArray(TypeInfo),
|
StringArray(TypeInfo),
|
||||||
Object(TypeInfo),
|
Object(Object),
|
||||||
// ComplexObject(ComplexObject),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Type {
|
impl Type {
|
||||||
|
@ -72,7 +81,6 @@ impl Type {
|
||||||
Type::String(_) => "String".into(),
|
Type::String(_) => "String".into(),
|
||||||
Type::StringArray(_) => "String".into(),
|
Type::StringArray(_) => "String".into(),
|
||||||
Type::Object(_) => "String".into(),
|
Type::Object(_) => "String".into(),
|
||||||
// Type::ComplexObject(_) => panic!("Not implemented for ComplexObject"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,7 +96,6 @@ impl Type {
|
||||||
Type::String(_) => "str".into(),
|
Type::String(_) => "str".into(),
|
||||||
Type::StringArray(_) => "&[str]".into(),
|
Type::StringArray(_) => "&[str]".into(),
|
||||||
Type::Object(_) => "str".into(),
|
Type::Object(_) => "str".into(),
|
||||||
// Type::ComplexObject(_) => panic!("Not implemented for ComplexObject"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +103,10 @@ impl Type {
|
||||||
matches!(self, Type::String(_) | Type::Object(_))
|
matches!(self, Type::String(_) | Type::Object(_))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_optional(&self) -> bool {
|
||||||
|
self.get_type_info().is_optional
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_type_info(&self) -> &TypeInfo {
|
pub fn get_type_info(&self) -> &TypeInfo {
|
||||||
match self {
|
match self {
|
||||||
Type::Number(t) => t,
|
Type::Number(t) => t,
|
||||||
|
@ -103,8 +114,7 @@ impl Type {
|
||||||
Type::Bool(t) => t,
|
Type::Bool(t) => t,
|
||||||
Type::String(t) => t,
|
Type::String(t) => t,
|
||||||
Type::StringArray(t) => t,
|
Type::StringArray(t) => t,
|
||||||
Type::Object(t) => t,
|
Type::Object(t) => &t.type_info,
|
||||||
// Type::ComplexObject(ComplexObject { type_info, .. }) => type_info,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,46 +142,54 @@ impl Type {
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let create_object_type = |name: &str| {
|
||||||
|
Some(Type::Object(Object {
|
||||||
|
type_info: create_type_info(),
|
||||||
|
ref_type: name.to_camel(),
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
|
||||||
match type_as_str {
|
match type_as_str {
|
||||||
"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())),
|
||||||
"array" => Some(Type::StringArray(create_type_info())),
|
"array" => description
|
||||||
// "array" => description
|
.extract_type()
|
||||||
// .clone()
|
.and_then(|t| create_object_type(&t))
|
||||||
// .and_then(|ref desc| get_ref_type(desc))
|
.or_else(|| Some(Type::StringArray(create_type_info()))),
|
||||||
// .map(|ref_type| {
|
|
||||||
// Type::ObjectArray(TypeWithRef {
|
|
||||||
// type_info: create_type_info(),
|
|
||||||
// ref_type,
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
// .or_else(|| Some(Type::StringArray(create_type_info()))),
|
|
||||||
"object" => Some(Type::Object(create_type_info())),
|
|
||||||
"float" => Some(Type::Float(create_type_info())),
|
"float" => Some(Type::Float(create_type_info())),
|
||||||
_ => None,
|
name => create_object_type(name),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn get_ref_type(desc: &str) -> Option<String> {
|
trait ExtractType {
|
||||||
// let re = RegexBuilder::new(r".*array of (\w+)\s?.*")
|
fn extract_type(&self) -> Option<String>;
|
||||||
// .case_insensitive(true)
|
}
|
||||||
// .build()
|
|
||||||
// .unwrap();
|
|
||||||
|
|
||||||
// re.captures(desc)
|
impl ExtractType for Option<String> {
|
||||||
// .and_then(|captures| captures.get(1))
|
fn extract_type(&self) -> Option<String> {
|
||||||
// .map(|m| m.as_str().to_owned())
|
self.as_ref().and_then(|t| {
|
||||||
// }
|
let re = RegexBuilder::new(r".*Array of (\w+) objects.*")
|
||||||
|
.case_insensitive(true)
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let cap = re.captures(t)?;
|
||||||
|
|
||||||
|
cap.get(1).map(|m| m.as_str().to_camel())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
// use super::*;
|
use super::*;
|
||||||
|
|
||||||
// #[test]
|
#[test]
|
||||||
// fn should_parse_object_array() {
|
fn test_regex() {
|
||||||
// let ref_type = get_ref_type("Array of result objects- see table below");
|
let input = Some("Array of result objects- see table below".to_string());
|
||||||
// assert_eq!("result", ref_type.unwrap());
|
let res = input.extract_type();
|
||||||
// }
|
assert_eq!(res.unwrap(), "Result");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,10 +11,11 @@ struct Api {}
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
let api = Api::login(BASE_URL, USERNAME, PASSWORD).await?;
|
let api = Api::login(BASE_URL, USERNAME, PASSWORD).await?;
|
||||||
|
|
||||||
let _ = api.search().plugins().await?;
|
|
||||||
let _ = api.search().results(1).send().await?;
|
|
||||||
let _ = api.search().delete(1).await?;
|
|
||||||
let _ = api.search().install_plugin("https://raw.githubusercontent.com/qbittorrent/search-plugins/master/nova3/engines/legittorrents.py").await?;
|
let _ = api.search().install_plugin("https://raw.githubusercontent.com/qbittorrent/search-plugins/master/nova3/engines/legittorrents.py").await?;
|
||||||
|
let plugins = api.search().plugins().await?;
|
||||||
|
eprintln!("{:?}", plugins);
|
||||||
|
// let _ = api.search().results(1).send().await?;
|
||||||
|
// let _ = api.search().delete(1).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user