garde_derive/
lib.rs

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}