use doc_comment::doc_comment_from;
use quote::{ToTokens, Tokens};
use syn;
use Bindings;
use Block;
use BuilderPattern;
use Initializer;
use DEFAULT_STRUCT_NAME;
#[derive(Debug)]
pub struct BuildMethod<'a> {
pub enabled: bool,
pub ident: &'a syn::Ident,
pub visibility: syn::Visibility,
pub pattern: BuilderPattern,
pub target_ty: &'a syn::Ident,
pub target_ty_generics: Option<syn::TypeGenerics<'a>>,
pub initializers: Vec<Tokens>,
pub doc_comment: Option<syn::Attribute>,
pub bindings: Bindings,
pub default_struct: Option<Block>,
pub validate_fn: Option<&'a syn::Path>,
}
impl<'a> ToTokens for BuildMethod<'a> {
fn to_tokens(&self, tokens: &mut Tokens) {
let ident = &self.ident;
let vis = &self.visibility;
let target_ty = &self.target_ty;
let target_ty_generics = &self.target_ty_generics;
let initializers = &self.initializers;
let self_param = match self.pattern {
BuilderPattern::Owned => quote!(self),
BuilderPattern::Mutable | BuilderPattern::Immutable => quote!(&self),
};
let doc_comment = &self.doc_comment;
let default_struct = self.default_struct.as_ref().map(|default_expr| {
let ident = syn::Ident::from(DEFAULT_STRUCT_NAME);
quote!(let #ident: #target_ty #target_ty_generics = #default_expr;)
});
let validate_fn = self.validate_fn.as_ref().map(|vfn| quote!(#vfn(&self)?;));
let result = self.bindings.result_ty();
let string = self.bindings.string_ty();
if self.enabled {
trace!("Deriving build method `{}`.", self.ident.as_ref());
tokens.append_all(quote!(
#doc_comment
#vis fn #ident(#self_param)
-> #result<#target_ty #target_ty_generics, #string>
{
#validate_fn
#default_struct
Ok(#target_ty {
#(#initializers)*
})
}
))
} else {
trace!("Skipping build method.");
}
}
}
impl<'a> BuildMethod<'a> {
pub fn doc_comment(&mut self, s: String) -> &mut Self {
self.doc_comment = Some(doc_comment_from(s));
self
}
pub fn push_initializer(&mut self, init: Initializer) -> &mut Self {
self.initializers.push(quote!(#init));
self
}
}
#[doc(hidden)]
#[macro_export]
macro_rules! default_build_method {
() => {
BuildMethod {
enabled: true,
ident: &syn::Ident::from("build"),
visibility: syn::parse_str("pub").unwrap(),
pattern: BuilderPattern::Mutable,
target_ty: &syn::Ident::from("Foo"),
target_ty_generics: None,
initializers: vec![quote!(foo: self.foo,)],
doc_comment: None,
bindings: Default::default(),
default_struct: None,
validate_fn: None,
}
};
}
#[cfg(test)]
mod tests {
#[allow(unused_imports)]
use super::*;
#[test]
fn std() {
let build_method = default_build_method!();
assert_eq!(
quote!(#build_method),
quote!(
pub fn build(&self) -> ::std::result::Result<Foo, ::std::string::String> {
Ok(Foo {
foo: self.foo,
})
}
)
);
}
#[test]
fn no_std() {
let mut build_method = default_build_method!();
build_method.bindings.no_std = true;
assert_eq!(
quote!(#build_method),
quote!(
pub fn build(&self) -> ::core::result::Result<Foo, ::alloc::string::String> {
Ok(Foo {
foo: self.foo,
})
}
)
);
}
#[test]
fn default_struct() {
let mut build_method = default_build_method!();
build_method.default_struct = Some("Default::default()".parse().unwrap());
assert_eq!(
quote!(#build_method),
quote!(
pub fn build(&self) -> ::std::result::Result<Foo, ::std::string::String> {
let __default: Foo = {Default::default()};
Ok(Foo {
foo: self.foo,
})
}
)
);
}
#[test]
fn skip() {
let mut build_method = default_build_method!();
build_method.enabled = false;
build_method.enabled = false;
assert_eq!(quote!(#build_method), quote!());
}
#[test]
fn rename() {
let ident = syn::Ident::from("finish");
let mut build_method: BuildMethod = default_build_method!();
build_method.ident = &ident;
assert_eq!(
quote!(#build_method),
quote!(
pub fn finish(&self) -> ::std::result::Result<Foo, ::std::string::String> {
Ok(Foo {
foo: self.foo,
})
}
)
)
}
#[test]
fn validation() {
let validate_path: syn::Path = syn::parse_str("IpsumBuilder::validate")
.expect("Statically-entered path should be valid");
let mut build_method: BuildMethod = default_build_method!();
build_method.validate_fn = Some(&validate_path);
assert_eq!(
quote!(#build_method),
quote!(
pub fn build(&self) -> ::std::result::Result<Foo, ::std::string::String> {
IpsumBuilder::validate(&self)?;
Ok(Foo {
foo: self.foo,
})
}
)
);
}
}