mod check;
mod emit;
mod model;
mod syntax;
mod util;
use proc_macro::{Delimiter, Literal, Span, TokenStream, TokenTree};
use quote::quote;
use syn::DeriveInput;
#[proc_macro_derive(Validate, attributes(garde))]
pub fn derive_validate(input: TokenStream) -> TokenStream {
let input = syn::parse_macro_input!(input as DeriveInput);
let input = match syntax::parse(input) {
Ok(v) => v,
Err(e) => return e.into_compile_error().into(),
};
let input = match check::check(input) {
Ok(v) => v,
Err(e) => return e.into_compile_error().into(),
};
emit::emit(input).into()
}
#[proc_macro]
pub fn select(input: TokenStream) -> TokenStream {
fn parse_literal_digits_only(lit: Literal) -> syn::Result<String> {
let span = lit.span();
let lit = lit.to_string();
if lit.chars().any(|v| !v.is_ascii_digit()) {
return Err(syn::Error::new(span.into(), "unexpected token"));
}
Ok(lit)
}
fn expect_ident_or_digit(
tokens: &mut impl Iterator<Item = TokenTree>,
) -> syn::Result<(Span, String)> {
let Some(tt) = tokens.next() else {
return Err(syn::Error::new(
Span::call_site().into(),
"incomplete input",
));
};
let span = tt.span();
match tt {
TokenTree::Ident(ident) => Ok((span, ident.to_string())),
TokenTree::Literal(lit) => parse_literal_digits_only(lit).map(|lit| (span, lit)),
_ => Err(syn::Error::new(span.into(), "unexpected token")),
}
}
fn expect_punct(tokens: &mut impl Iterator<Item = TokenTree>, punct: char) -> syn::Result<()> {
let Some(tt) = tokens.next() else {
return Err(syn::Error::new(
Span::call_site().into(),
format!("expected `{punct}`"),
));
};
let span = tt.span();
let TokenTree::Punct(ident) = tt else {
return Err(syn::Error::new(span.into(), format!("expected `{punct}`")));
};
if ident.as_char() != punct {
return Err(syn::Error::new(span.into(), format!("expected `{punct}`")));
}
Ok(())
}
fn expect_end(tokens: &mut impl Iterator<Item = TokenTree>) -> syn::Result<()> {
match tokens.next() {
Some(tt) => Err(syn::Error::new(tt.span().into(), "expected end of input")),
None => Ok(()),
}
}
fn parse_report_ident(
tokens: &mut impl Iterator<Item = TokenTree>,
) -> syn::Result<(Span, String)> {
let ident = expect_ident_or_digit(tokens)?;
expect_punct(tokens, ',')?;
Ok(ident)
}
fn parse_components(tokens: &mut impl Iterator<Item = TokenTree>) -> syn::Result<Vec<String>> {
let mut idents = vec![];
while let Some(tt) = tokens.next() {
match tt {
TokenTree::Group(group) if group.delimiter() == Delimiter::Bracket => {
let mut inner = group.stream().into_iter();
idents.push(expect_ident_or_digit(&mut inner)?.1.to_string());
expect_end(&mut inner)?;
}
TokenTree::Literal(lit) if idents.is_empty() => {
idents.push(parse_literal_digits_only(lit)?)
}
TokenTree::Ident(ident) if idents.is_empty() => idents.push(ident.to_string()),
TokenTree::Punct(punct) if punct.as_char() == '.' => {
idents.push(expect_ident_or_digit(tokens)?.1.to_string());
}
other => return Err(syn::Error::new(other.span().into(), "unexpected token")),
}
}
Ok(idents)
}
let mut tokens = input.into_iter();
let report_ident: proc_macro2::Ident = match parse_report_ident(&mut tokens) {
Ok((span, report_ident)) => proc_macro2::Ident::new(&report_ident.to_string(), span.into()),
Err(e) => return e.into_compile_error().into(),
};
let components = match parse_components(&mut tokens) {
Ok(components) => components,
Err(e) => return e.into_compile_error().into(),
};
quote! {{
let report = &#report_ident;
let needle = [#(#components),*];
report.iter()
.filter(move |(path, _)| {
if needle.len() > path.len() {
return false
}
let mut path = path.__iter().rev().map(|(_, v)| v.as_str());
for left in needle.iter().copied() {
match path.next() {
Some(right) => if left != right { return false },
None => return false,
}
}
true
})
.map(|(_, error)| error)
}}
.into()
}