use inflections::case::to_lower_case;
use proc_macro2::TokenStream;
use quote::quote;
use regex::Regex;
use synthez::{ParseAttrs, Required, ToTokens};
pub(crate) fn derive(input: TokenStream) -> syn::Result<TokenStream> {
let input = syn::parse2::<syn::DeriveInput>(input)?;
let definition = Definition::try_from(input)?;
Ok(quote! { #definition })
}
#[derive(Debug, Default, ParseAttrs)]
struct Attrs {
#[parse(value)]
regex: Required<syn::LitStr>,
#[parse(value)]
name: Option<syn::LitStr>,
}
#[derive(Debug, ToTokens)]
#[to_tokens(append(impl_parameter))]
struct Definition {
ident: syn::Ident,
generics: syn::Generics,
regex: Regex,
name: String,
}
impl TryFrom<syn::DeriveInput> for Definition {
type Error = syn::Error;
fn try_from(input: syn::DeriveInput) -> syn::Result<Self> {
let attrs: Attrs = Attrs::parse_attrs("param", &input)?;
let regex = Regex::new(&attrs.regex.value()).map_err(|e| {
syn::Error::new(attrs.regex.span(), format!("Invalid regex: {e}"))
})?;
let name = attrs.name.as_ref().map_or_else(
|| to_lower_case(&input.ident.to_string()),
syn::LitStr::value,
);
Ok(Self {
ident: input.ident,
generics: input.generics,
regex,
name,
})
}
}
impl Definition {
#[must_use]
fn impl_parameter(&self) -> TokenStream {
let ty = &self.ident;
let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl();
let (regex, name) = (self.regex.as_str(), &self.name);
quote! {
#[automatically_derived]
impl #impl_gens ::cucumber::Parameter for #ty #ty_gens
#where_clause
{
const REGEX: &'static str = #regex;
const NAME: &'static str = #name;
}
}
}
}
#[cfg(test)]
mod spec {
use quote::quote;
use syn::parse_quote;
#[test]
fn derives_impl() {
let input = parse_quote! {
#[param(regex = "cat|dog", name = "custom")]
struct Parameter;
};
let output = quote! {
#[automatically_derived]
impl ::cucumber::Parameter for Parameter {
const REGEX: &'static str = "cat|dog";
const NAME: &'static str = "custom";
}
};
assert_eq!(
super::derive(input).unwrap().to_string(),
output.to_string(),
);
}
#[test]
fn derives_impl_with_default_name() {
let input = parse_quote! {
#[param(regex = "cat|dog")]
struct Animal;
};
let output = quote! {
#[automatically_derived]
impl ::cucumber::Parameter for Animal {
const REGEX: &'static str = "cat|dog";
const NAME: &'static str = "animal";
}
};
assert_eq!(
super::derive(input).unwrap().to_string(),
output.to_string(),
);
}
#[test]
fn derives_impl_with_capturing_group() {
let input = parse_quote! {
#[param(regex = "(cat)|(dog)")]
struct Animal;
};
let output = quote! {
#[automatically_derived]
impl ::cucumber::Parameter for Animal {
const REGEX: &'static str = "(cat)|(dog)";
const NAME: &'static str = "animal";
}
};
assert_eq!(
super::derive(input).unwrap().to_string(),
output.to_string(),
);
}
#[test]
fn derives_impl_with_generics() {
let input = parse_quote! {
#[param(regex = "cat|dog", name = "custom")]
struct Parameter<T>(T);
};
let output = quote! {
#[automatically_derived]
impl<T> ::cucumber::Parameter for Parameter<T> {
const REGEX: &'static str = "cat|dog";
const NAME: &'static str = "custom";
}
};
assert_eq!(
super::derive(input).unwrap().to_string(),
output.to_string(),
);
}
#[test]
fn derives_impl_with_non_capturing_regex_groups() {
let input = parse_quote! {
#[param(regex = "cat|dog(?:s)?", name = "custom")]
struct Parameter<T>(T);
};
let output = quote! {
#[automatically_derived]
impl<T> ::cucumber::Parameter for Parameter<T> {
const REGEX: &'static str = "cat|dog(?:s)?";
const NAME: &'static str = "custom";
}
};
assert_eq!(
super::derive(input).unwrap().to_string(),
output.to_string(),
);
}
#[test]
fn regex_arg_is_required() {
let input = parse_quote! {
#[param(name = "custom")]
struct Parameter;
};
let err = super::derive(input).unwrap_err();
assert_eq!(
err.to_string(),
"`regex` argument of `#[param]` attribute is expected to be \
present, but is absent",
);
}
#[test]
fn invalid_regex() {
let input = parse_quote! {
#[param(regex = "(cat|dog")]
struct Parameter;
};
let err = super::derive(input).unwrap_err();
assert_eq!(
err.to_string(),
"\
Invalid regex: regex parse error:
(cat|dog
^
error: unclosed group",
);
}
}