#![crate_type = "proc-macro"]
#![recursion_limit="128"]
#![doc(html_root_url = "https://docs.rs/avocado_derive/0.6.0")]
#![deny(missing_debug_implementations, missing_copy_implementations,
trivial_casts, trivial_numeric_casts,
unsafe_code,
unstable_features,
anonymous_parameters, bare_trait_objects,
variant_size_differences,
unused_import_braces, unused_qualifications,
/* missing_docs (https://github.com/rust-lang/rust/issues/42008) */)]
#![allow(clippy::single_match, clippy::match_same_arms, clippy::match_ref_pats,
clippy::clone_on_ref_ptr, clippy::needless_pass_by_value)]
#![deny(clippy::wrong_pub_self_convention, clippy::used_underscore_binding,
clippy::stutter, clippy::similar_names, clippy::pub_enum_variant_names,
clippy::missing_docs_in_private_items,
clippy::non_ascii_literal, clippy::unicode_not_nfc,
clippy::result_unwrap_used, clippy::option_unwrap_used,
clippy::option_map_unwrap_or_else, clippy::option_map_unwrap_or, clippy::filter_map,
clippy::shadow_unrelated, clippy::shadow_reuse, clippy::shadow_same,
clippy::int_plus_one, clippy::string_add_assign, clippy::if_not_else,
clippy::invalid_upcast_comparisons,
clippy::cast_precision_loss,
clippy::cast_possible_wrap, clippy::cast_possible_truncation,
clippy::mutex_integer, clippy::mut_mut, clippy::items_after_statements,
clippy::print_stdout, clippy::mem_forget, clippy::maybe_infinite_iter)]
#[macro_use]
extern crate quote;
#[macro_use]
extern crate syn;
extern crate proc_macro;
extern crate proc_macro2;
#[macro_use]
mod error;
mod meta;
mod attr;
mod case;
mod index;
mod option;
use proc_macro::TokenStream;
use proc_macro2::Span;
use syn::{
DeriveInput, Data, Generics, Fields, Ident,
Type, Attribute, TypePath, Path, PathSegment,
};
use self::{
meta::*,
case::RenameRule,
index::Spec,
option::DocOptions,
error::{ Error, Result, err_msg },
};
#[proc_macro_derive(Doc, attributes(avocado, index, id_type, options))]
pub fn derive_avocado_doc(input: TokenStream) -> TokenStream {
impl_avocado_doc(input).unwrap_or_else(|error| panic!("{}", error))
}
fn impl_avocado_doc(input: TokenStream) -> Result<TokenStream> {
let parsed_ast: DeriveInput = syn::parse(input)?;
let ty = parsed_ast.ident;
let generics = parsed_ast.generics;
let ty_name = serde_renamed_ident(&parsed_ast.attrs, ty.to_string())?;
let (impl_gen, ty_gen, where_cls) = generics.split_for_impl();
let id_ty = raw_id_type(&parsed_ast.attrs)?;
let indexes = Spec::from_attributes(&parsed_ast.attrs)?;
let options = DocOptions::from_attributes(&parsed_ast.attrs)?;
let index_count = indexes.len();
ensure_only_lifetime_params(&generics)?;
match parsed_ast.data {
Data::Struct(s) => {
let id_name = name_of_id_field(s.fields, &parsed_ast.attrs)?;
let ast = quote! {
impl #impl_gen ::avocado::doc::Doc for #ty #ty_gen #where_cls {
const NAME: &'static str = #ty_name;
type Id = #id_ty;
fn id(&self) -> ::std::option::Option<&::avocado::uid::Uid<Self>> {
::std::convert::From::from(&self.#id_name)
}
fn set_id(&mut self, id: ::avocado::uid::Uid<Self>) {
self.#id_name = ::std::convert::From::from(id);
}
fn indexes() -> ::std::vec::Vec<::avocado::prelude::IndexModel> {
let mut index_vector = ::std::vec::Vec::with_capacity(#index_count);
#(index_vector.push(#indexes);)*
index_vector
}
#options
}
};
Ok(ast.into())
},
_ => err_msg(
"only a `struct` can be a top-level `Doc`; consider wrapping this type in a struct"
),
}
}
fn serde_renamed_ident(attrs: &[Attribute], ident: String) -> Result<String> {
serde_name_value(attrs, "rename")?
.as_ref()
.map_or(Ok(ident), value_as_str)
}
fn field_is_always_skipped(attrs: &[Attribute]) -> Result<bool> {
Ok(
has_serde_word(attrs, "skip")? || (
has_serde_word(attrs, "skip_serializing")?
&&
has_serde_word(attrs, "skip_deserializing")?
)
)
}
fn raw_id_type(attrs: &[Attribute]) -> Result<Type> {
literal_value_for_name(attrs, "id_type")
.map(|maybe_ty| maybe_ty.unwrap_or_else(|| {
Type::Path(TypePath {
qself: None,
path: Path {
leading_colon: Some(Default::default()),
segments: ["avocado", "prelude", "ObjectId"]
.iter()
.map(|&name| PathSegment {
ident: Ident::new(name, Span::call_site()),
arguments: Default::default(),
})
.collect()
},
})
}))
}
fn name_of_id_field(fields: Fields, attrs: &[Attribute]) -> Result<Ident> {
let named = match fields {
Fields::Named(fields) => fields.named,
_ => return err_msg("a `Doc` must be a struct with named fields"),
};
let rename_attr = serde_name_value(attrs, "rename_all")?;
let rename_rule: Option<RenameRule> = match rename_attr {
None => None,
Some(kv) => Some(value_as_str(&kv)?.parse()?)
};
let mut id_name = None;
for field in named {
if field_is_always_skipped(&field.attrs)? {
continue;
}
let ident = match field.ident {
Some(ident) => ident,
None => continue,
};
let rename_all_ident = rename_rule.map_or_else(
|| ident.to_string(),
|rule| rule.apply_to_field(ident.to_string()),
);
let field_name = serde_renamed_ident(&field.attrs, rename_all_ident)?;
if field_name == "_id" {
if id_name.is_some() {
return err_msg("more than one fields serialize as `_id`");
} else {
id_name = Some(ident);
}
}
}
id_name.ok_or_else(
|| Error::new("a `Doc` must contain a field serialized as `_id`")
)
}
fn ensure_only_lifetime_params(generics: &Generics) -> Result<()> {
let make_error = |param_type| err_fmt!(
"`Doc` can't be derived for a type that is generic over {} parameters",
param_type
);
if generics.type_params().next().is_some() {
return make_error("type");
}
if generics.const_params().next().is_some() {
return make_error("const");
}
Ok(())
}