use std::collections::HashMap;
use proc_macro2::{ TokenStream, Span };
use syn::{ Attribute, Ident, Path, PathSegment };
use syn::{ Meta, NestedMeta, MetaNameValue, Lit };
use quote::{ ToTokens, TokenStreamExt };
use crate::error::{ Result, err_msg };
#[derive(Debug, Clone)]
pub struct DocOptions(HashMap<String, (&'static [&'static str], Option<Path>)>);
impl DocOptions {
fn new() -> Self {
let all_options: &[(&str, &[&str])] = &[
(
"count_options",
&["mongodb", "coll", "options", "CountOptions"],
),
(
"distinct_options",
&["mongodb", "coll", "options", "DistinctOptions"],
),
(
"aggregate_options",
&["mongodb", "coll", "options", "AggregateOptions"],
),
(
"query_options",
&["mongodb", "coll", "options", "FindOptions"],
),
(
"insert_options",
&["mongodb", "coll", "options", "InsertManyOptions"],
),
(
"delete_options",
&["mongodb", "common", "WriteConcern"],
),
(
"update_options",
&["mongodb", "common", "WriteConcern"],
),
(
"upsert_options",
&["mongodb", "common", "WriteConcern"],
),
(
"find_and_update_options",
&["mongodb", "coll", "options", "FindOneAndUpdateOptions"],
),
];
let hm = all_options
.iter()
.map(|&(fn_name, type_path_components)| {
(fn_name.into(), (type_path_components, None))
})
.collect();
DocOptions(hm)
}
pub fn from_attributes(attrs: &[Attribute]) -> Result<Self> {
let mut options = DocOptions::new();
let metas = attrs
.iter()
.filter_map(Attribute::interpret_meta)
.filter_map(|meta| match meta {
Meta::Word(_) | Meta::NameValue(_) => None,
Meta::List(meta) => if meta.ident == "options" {
Some(meta.nested)
} else {
None
}
})
.next();
if let Some(metas) = metas {
for meta in metas {
match meta {
NestedMeta::Meta(Meta::NameValue(MetaNameValue {
ident,
lit: Lit::Str(path_str),
..
})) => {
let path: Path = path_str.parse()?;
let fn_name = ident.to_string();
match options.0.get_mut(&fn_name) {
Some(&mut (_, ref mut path_ptr)) => {
*path_ptr = Some(path);
}
None => return err_fmt!(
"no option method named `Doc::{}()`", fn_name
)
}
},
_ => return err_msg(
"attribute must have form `#[options(fn_name = \"path\", ...)]`"
)
}
}
}
Ok(options)
}
}
impl ToTokens for DocOptions {
fn to_tokens(&self, tokens: &mut TokenStream) {
for (fn_name, &(type_path_components, ref callee_path)) in &self.0 {
if let Some(ref callee_path) = *callee_path {
fn_to_tokens(fn_name, type_path_components, callee_path, tokens);
}
}
}
}
fn fn_to_tokens(fn_name: &str, type_path_components: &[&str], callee_path: &Path, tokens: &mut TokenStream) {
let fn_name = Ident::new(fn_name, Span::call_site());
let type_path = Path {
leading_colon: Some(Default::default()),
segments: type_path_components
.iter()
.map(|&name| PathSegment {
ident: Ident::new(name, Span::call_site()),
arguments: Default::default(),
})
.collect()
};
tokens.append_all(quote! {
fn #fn_name() -> #type_path {
#callee_path()
}
});
}