1mod check;
2mod emit;
3mod model;
4mod syntax;
5mod util;
6
7use proc_macro::{Delimiter, Literal, Span, TokenStream, TokenTree};
8use quote::quote;
9use syn::DeriveInput;
10
11#[proc_macro_derive(Validate, attributes(garde))]
12pub fn derive_validate(input: TokenStream) -> TokenStream {
13 let input = syn::parse_macro_input!(input as DeriveInput);
14 let input = match syntax::parse(input) {
15 Ok(v) => v,
16 Err(e) => return e.into_compile_error().into(),
17 };
18 let input = match check::check(input) {
19 Ok(v) => v,
20 Err(e) => return e.into_compile_error().into(),
21 };
22 emit::emit(input).into()
23}
24
25#[proc_macro]
26pub fn select(input: TokenStream) -> TokenStream {
27 fn parse_literal_digits_only(lit: Literal) -> syn::Result<String> {
28 let span = lit.span();
29 let lit = lit.to_string();
30 if lit.chars().any(|v| !v.is_ascii_digit()) {
31 return Err(syn::Error::new(span.into(), "unexpected token"));
32 }
33 Ok(lit)
34 }
35
36 fn expect_ident_or_digit(
37 tokens: &mut impl Iterator<Item = TokenTree>,
38 ) -> syn::Result<(Span, String)> {
39 let Some(tt) = tokens.next() else {
40 return Err(syn::Error::new(
41 Span::call_site().into(),
42 "incomplete input",
43 ));
44 };
45 let span = tt.span();
46 match tt {
47 TokenTree::Ident(ident) => Ok((span, ident.to_string())),
48 TokenTree::Literal(lit) => parse_literal_digits_only(lit).map(|lit| (span, lit)),
49 _ => Err(syn::Error::new(span.into(), "unexpected token")),
50 }
51 }
52
53 fn expect_punct(tokens: &mut impl Iterator<Item = TokenTree>, punct: char) -> syn::Result<()> {
54 let Some(tt) = tokens.next() else {
55 return Err(syn::Error::new(
56 Span::call_site().into(),
57 format!("expected `{punct}`"),
58 ));
59 };
60 let span = tt.span();
61 let TokenTree::Punct(ident) = tt else {
62 return Err(syn::Error::new(span.into(), format!("expected `{punct}`")));
63 };
64 if ident.as_char() != punct {
65 return Err(syn::Error::new(span.into(), format!("expected `{punct}`")));
66 }
67 Ok(())
68 }
69
70 fn expect_end(tokens: &mut impl Iterator<Item = TokenTree>) -> syn::Result<()> {
71 match tokens.next() {
72 Some(tt) => Err(syn::Error::new(tt.span().into(), "expected end of input")),
73 None => Ok(()),
74 }
75 }
76
77 fn parse_report_ident(
78 tokens: &mut impl Iterator<Item = TokenTree>,
79 ) -> syn::Result<(Span, String)> {
80 let ident = expect_ident_or_digit(tokens)?;
81 expect_punct(tokens, ',')?;
82 Ok(ident)
83 }
84
85 fn parse_components(tokens: &mut impl Iterator<Item = TokenTree>) -> syn::Result<Vec<String>> {
86 let mut idents = vec![];
87 while let Some(tt) = tokens.next() {
88 match tt {
89 TokenTree::Group(group) if group.delimiter() == Delimiter::Bracket => {
90 let mut inner = group.stream().into_iter();
91 idents.push(expect_ident_or_digit(&mut inner)?.1.to_string());
92 expect_end(&mut inner)?;
93 }
94 TokenTree::Literal(lit) if idents.is_empty() => {
95 idents.push(parse_literal_digits_only(lit)?)
96 }
97 TokenTree::Ident(ident) if idents.is_empty() => idents.push(ident.to_string()),
98 TokenTree::Punct(punct) if punct.as_char() == '.' => {
99 idents.push(expect_ident_or_digit(tokens)?.1.to_string());
100 }
101 other => return Err(syn::Error::new(other.span().into(), "unexpected token")),
102 }
103 }
104
105 Ok(idents)
106 }
107
108 let mut tokens = input.into_iter();
109 let report_ident: proc_macro2::Ident = match parse_report_ident(&mut tokens) {
110 Ok((span, report_ident)) => proc_macro2::Ident::new(&report_ident.to_string(), span.into()),
111 Err(e) => return e.into_compile_error().into(),
112 };
113 let components = match parse_components(&mut tokens) {
114 Ok(components) => components,
115 Err(e) => return e.into_compile_error().into(),
116 };
117
118 quote! {{
119 let report = &#report_ident;
120 let needle = [#(#components),*];
121 report.iter()
122 .filter(move |(path, _)| {
123 if needle.len() > path.len() {
124 return false
125 }
126 let mut path = path.__iter().rev().map(|(_, v)| v.as_str());
127 for left in needle.iter().copied() {
128 match path.next() {
129 Some(right) => if left != right { return false },
130 None => return false,
131 }
132 }
133 true
134 })
135 .map(|(_, error)| error)
136 }}
137 .into()
138}