#![deny(clippy::all)]
#![deny(rustdoc::all)]
mod builder;
mod metadata;
mod params;
extern crate proc_macro;
#[macro_use]
extern crate syn;
use crate::builder::{StructBuilder, StructField};
use crate::params::ParsedParams;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use std::collections::HashMap;
use syn::{DeriveInput, Ident, LitStr, Visibility};
type Result<T> = std::result::Result<T, syn::Error>;
#[proc_macro_attribute]
pub fn query(args: TokenStream, input: TokenStream) -> TokenStream {
let mut params = HashMap::new();
let arg_parser = syn::meta::parser(|meta| {
let name = match meta.path.get_ident() {
Some(ident) => ident.to_string(),
None => return Err(meta.error("invalid parameter name")),
};
let value: LitStr = meta.value()?.parse()?;
params.insert(name, value);
Ok(())
});
parse_macro_input!(args with arg_parser);
let input = parse_macro_input!(input as DeriveInput);
let tokens = query_real(params, input)
.unwrap_or_else(|err| err.into_compile_error());
tokens.into()
}
fn query_real(
params: HashMap<String, LitStr>,
input: DeriveInput,
) -> Result<TokenStream2> {
let params = ParsedParams::new(params)?;
impl_query(input, ¶ms)
}
fn build_modules(
prefix: &Ident,
visibility: &Visibility,
body_ident: &Ident,
qparams: &ParsedParams,
) -> Result<(Vec<StructBuilder>, String, Ident)> {
let mut structs = vec![];
let fieldname = qparams.get_fieldname()?;
let container = qparams.get_fields()?;
let mut top_fields: Vec<StructField> = vec![];
for top_field in container.top.into_values() {
top_fields.push(top_field.try_into()?);
}
for (subname, fields) in container.sub {
let ident = format_ident!("{}Item{}", prefix, subname);
let mut struct_fields: Vec<StructField> = vec![];
for field in fields.into_values() {
struct_fields.push(field.try_into()?);
}
structs.push(StructBuilder {
ident: ident.clone(),
fields: struct_fields,
visibility: visibility.clone(),
extra_derive: None,
});
top_fields.push(StructField {
name: subname,
type_: quote! { Vec<#ident> },
default: true,
rename: None,
deserialize_with: None,
});
}
let item_ident = format_ident!("{}{}", prefix, "Item");
structs.push(StructBuilder {
ident: item_ident.clone(),
fields: top_fields,
visibility: visibility.clone(),
extra_derive: None,
});
let body_fields = vec![
StructField {
name: fieldname.to_string(),
type_: quote! { Vec<#item_ident> },
default: false,
rename: None,
deserialize_with: None,
},
StructField {
name: "normalized".to_string(),
type_: quote! { Vec<::mwapi_responses::normalize::Normalized> },
default: true,
rename: None,
deserialize_with: None,
},
StructField {
name: "redirects".to_string(),
type_: quote! { Vec<::mwapi_responses::normalize::Redirect> },
default: true,
rename: None,
deserialize_with: None,
},
];
structs.push(StructBuilder {
ident: body_ident.clone(),
fields: body_fields,
visibility: visibility.clone(),
extra_derive: Some(quote! { #[derive(Default)] }),
});
Ok((structs, fieldname, item_ident))
}
fn impl_query(
input: DeriveInput,
qparams: &ParsedParams,
) -> Result<TokenStream2> {
let prefix = &input.ident;
let visiblity = &input.vis;
let body_ident = format_ident!("{}Body", prefix);
let (mut structs, item_fieldname, item_ident) =
build_modules(prefix, visiblity, &body_ident, qparams)?;
let item_fieldname = format_ident!("{}", item_fieldname);
structs.push(StructBuilder {
ident: prefix.clone(),
fields: vec![
StructField {
name: "batchcomplete".to_string(),
type_: quote! { bool },
default: true,
rename: None,
deserialize_with: None,
},
StructField {
name: "continue".to_string(),
type_: quote! { ::std::collections::HashMap<String, String> },
default: true,
rename: Some("continue_".to_string()),
deserialize_with: Some(
"::mwapi_responses::query::deserialize_continue"
.to_string(),
),
},
StructField {
name: "query".to_string(),
type_: quote! { #body_ident },
default: true,
rename: None,
deserialize_with: None,
},
],
visibility: visiblity.clone(),
extra_derive: None,
});
let tokens = quote! {
impl ::mwapi_responses::ApiResponse<#item_ident> for #prefix {
fn params() -> &'static [(&'static str, &'static str)] {
#qparams
}
fn items(&self) -> ::std::slice::Iter<'_, #item_ident> {
self.query.#item_fieldname.iter()
}
fn into_items(self) -> ::std::vec::IntoIter<#item_ident> {
self.query.#item_fieldname.into_iter()
}
fn redirects(&self) -> &[::mwapi_responses::normalize::Redirect] {
&self.query.redirects
}
fn normalized_titles(&self) -> &[::mwapi_responses::normalize::Normalized] {
&self.query.normalized
}
}
#(#structs)*
};
Ok(tokens)
}