biscuit_quote/
lib.rs

1//! Procedural macros to build biscuit-auth tokens and authorizers
2
3use biscuit_parser::{
4    builder::{Check, Fact, Policy, Rule},
5    error,
6    parser::{parse_block_source, parse_source},
7};
8use proc_macro2::{Span, TokenStream};
9use proc_macro_error::{abort_call_site, proc_macro_error};
10use quote::{quote, ToTokens};
11use std::collections::{HashMap, HashSet};
12use syn::{
13    parse::{self, Parse, ParseStream},
14    Expr, Ident, LitStr, Token, TypePath,
15};
16
17// parses ", foo = bar, baz = quux", including the leading comma
18struct ParsedParameters {
19    parameters: HashMap<String, Expr>,
20}
21
22impl Parse for ParsedParameters {
23    fn parse(input: ParseStream) -> parse::Result<Self> {
24        let mut parameters = HashMap::new();
25
26        while input.peek(Token![,]) {
27            let _: Token![,] = input.parse()?;
28            if input.is_empty() {
29                break;
30            }
31
32            let key: Ident = input.parse()?;
33            let _: Token![=] = input.parse()?;
34            let value: Expr = input.parse()?;
35
36            parameters.insert(key.to_string(), value);
37        }
38
39        Ok(Self { parameters })
40    }
41}
42
43// parses "\"...\", foo = bar, baz = quux"
44struct ParsedCreateNew {
45    datalog: String,
46    parameters: HashMap<String, Expr>,
47}
48
49impl Parse for ParsedCreateNew {
50    fn parse(input: ParseStream) -> parse::Result<Self> {
51        let datalog = input.parse::<LitStr>()?.value();
52        let parameters = input.parse::<ParsedParameters>()?;
53
54        Ok(Self {
55            datalog,
56            parameters: parameters.parameters,
57        })
58    }
59}
60
61// parses "&mut b, \"...\", foo = bar, baz = quux"
62struct ParsedMerge {
63    target: Expr,
64    datalog: String,
65    parameters: HashMap<String, Expr>,
66}
67
68impl Parse for ParsedMerge {
69    fn parse(input: ParseStream) -> parse::Result<Self> {
70        let target = input.parse::<Expr>()?;
71        let _: Token![,] = input.parse()?;
72
73        let datalog = input.parse::<LitStr>()?.value();
74        let parameters = input.parse::<ParsedParameters>()?;
75
76        Ok(Self {
77            target,
78            datalog,
79            parameters: parameters.parameters,
80        })
81    }
82}
83
84/// Create a `BlockBuilder` from a datalog string and optional parameters.
85/// The datalog string is parsed at compile time and replaced by manual
86/// block building.
87#[proc_macro]
88#[proc_macro_error]
89pub fn block(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
90    let ParsedCreateNew {
91        datalog,
92        parameters,
93    } = syn::parse_macro_input!(input as ParsedCreateNew);
94
95    let ty = syn::parse_quote!(::biscuit_auth::builder::BlockBuilder);
96    let builder = Builder::block_source(ty, None, datalog, parameters)
97        .unwrap_or_else(|e| abort_call_site!(e.to_string()));
98
99    builder.into_token_stream().into()
100}
101
102/// Merge facts, rules, and checks into a `BlockBuilder` from a datalog
103/// string and optional parameters. The datalog string is parsed at compile time
104/// and replaced by manual block building.
105#[proc_macro]
106#[proc_macro_error]
107pub fn block_merge(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
108    let ParsedMerge {
109        target,
110        datalog,
111        parameters,
112    } = syn::parse_macro_input!(input as ParsedMerge);
113
114    let ty = syn::parse_quote!(::biscuit_auth::builder::BlockBuilder);
115    let builder = Builder::block_source(ty, Some(target), datalog, parameters)
116        .unwrap_or_else(|e| abort_call_site!(e.to_string()));
117
118    builder.into_token_stream().into()
119}
120
121/// Create an `Authorizer` from a datalog string and optional parameters.
122/// The datalog string is parsed at compile time and replaced by manual
123/// block building.
124#[proc_macro]
125#[proc_macro_error]
126pub fn authorizer(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
127    let ParsedCreateNew {
128        datalog,
129        parameters,
130    } = syn::parse_macro_input!(input as ParsedCreateNew);
131
132    let ty = syn::parse_quote!(::biscuit_auth::Authorizer);
133    let builder = Builder::source(ty, None, datalog, parameters)
134        .unwrap_or_else(|e| abort_call_site!(e.to_string()));
135
136    builder.into_token_stream().into()
137}
138
139/// Merge facts, rules, checks, and policies into an `Authorizer` from a datalog
140/// string and optional parameters. The datalog string is parsed at compile time
141/// and replaced by manual block building.
142#[proc_macro]
143#[proc_macro_error]
144pub fn authorizer_merge(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
145    let ParsedMerge {
146        target,
147        datalog,
148        parameters,
149    } = syn::parse_macro_input!(input as ParsedMerge);
150
151    let ty = syn::parse_quote!(::biscuit_auth::Authorizer);
152    let builder = Builder::source(ty, Some(target), datalog, parameters)
153        .unwrap_or_else(|e| abort_call_site!(e.to_string()));
154
155    builder.into_token_stream().into()
156}
157
158/// Create an `BiscuitBuilder` from a datalog string and optional parameters.
159/// The datalog string is parsed at compile time and replaced by manual
160/// block building.
161#[proc_macro]
162#[proc_macro_error]
163pub fn biscuit(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
164    let ParsedCreateNew {
165        datalog,
166        parameters,
167    } = syn::parse_macro_input!(input as ParsedCreateNew);
168
169    let ty = syn::parse_quote!(::biscuit_auth::builder::BiscuitBuilder);
170    let builder = Builder::block_source(ty, None, datalog, parameters)
171        .unwrap_or_else(|e| abort_call_site!(e.to_string()));
172
173    builder.into_token_stream().into()
174}
175
176/// Merge facts, rules, and checks into a `BiscuitBuilder` from a datalog
177/// string and optional parameters. The datalog string is parsed at compile time
178/// and replaced by manual block building.
179#[proc_macro]
180#[proc_macro_error]
181pub fn biscuit_merge(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
182    let ParsedMerge {
183        target,
184        datalog,
185        parameters,
186    } = syn::parse_macro_input!(input as ParsedMerge);
187
188    let ty = syn::parse_quote!(::biscuit_auth::builder::BiscuitBuilder);
189    let builder = Builder::block_source(ty, Some(target), datalog, parameters)
190        .unwrap_or_else(|e| abort_call_site!(e.to_string()));
191
192    builder.into_token_stream().into()
193}
194
195#[derive(Clone, Debug)]
196struct Builder {
197    pub builder_type: TypePath,
198    pub target: Option<Expr>,
199    pub parameters: HashMap<String, Expr>,
200
201    // parameters used in the datalog source
202    pub datalog_parameters: HashSet<String>,
203    // parameters provided to the macro
204    pub macro_parameters: HashSet<String>,
205
206    pub facts: Vec<Fact>,
207    pub rules: Vec<Rule>,
208    pub checks: Vec<Check>,
209    pub policies: Vec<Policy>,
210}
211
212impl Builder {
213    fn new(
214        builder_type: TypePath,
215        target: Option<Expr>,
216        parameters: HashMap<String, Expr>,
217    ) -> Self {
218        let macro_parameters = parameters.keys().cloned().collect();
219
220        Self {
221            builder_type,
222            target,
223            parameters,
224
225            datalog_parameters: HashSet::new(),
226            macro_parameters,
227
228            facts: Vec::new(),
229            rules: Vec::new(),
230            checks: Vec::new(),
231            policies: Vec::new(),
232        }
233    }
234
235    fn block_source<T: AsRef<str>>(
236        builder_type: TypePath,
237        target: Option<Expr>,
238        source: T,
239        parameters: HashMap<String, Expr>,
240    ) -> Result<Builder, error::LanguageError> {
241        let mut builder = Builder::new(builder_type, target, parameters);
242        let source = parse_block_source(source.as_ref())?;
243
244        builder.facts(source.facts.into_iter().map(|(_name, fact)| fact));
245        builder.rules(source.rules.into_iter().map(|(_name, rule)| rule));
246        builder.checks(source.checks.into_iter().map(|(_name, check)| check));
247
248        builder.validate()?;
249        Ok(builder)
250    }
251
252    fn source<T: AsRef<str>>(
253        builder_type: TypePath,
254        target: Option<Expr>,
255        source: T,
256        parameters: HashMap<String, Expr>,
257    ) -> Result<Builder, error::LanguageError> {
258        let mut builder = Builder::new(builder_type, target, parameters);
259        let source = parse_source(source.as_ref())?;
260
261        builder.facts(source.facts.into_iter().map(|(_name, fact)| fact));
262        builder.rules(source.rules.into_iter().map(|(_name, rule)| rule));
263        builder.checks(source.checks.into_iter().map(|(_name, check)| check));
264        builder.policies(source.policies.into_iter().map(|(_name, policy)| policy));
265
266        builder.validate()?;
267        Ok(builder)
268    }
269
270    fn facts(&mut self, facts: impl Iterator<Item = Fact>) {
271        for fact in facts {
272            if let Some(parameters) = &fact.parameters {
273                self.datalog_parameters.extend(parameters.keys().cloned());
274            }
275            self.facts.push(fact);
276        }
277    }
278
279    fn rule_parameters(&mut self, rule: &Rule) {
280        if let Some(parameters) = &rule.parameters {
281            self.datalog_parameters.extend(parameters.keys().cloned());
282        }
283
284        if let Some(parameters) = &rule.scope_parameters {
285            self.datalog_parameters.extend(parameters.keys().cloned());
286        }
287    }
288
289    fn rules(&mut self, rules: impl Iterator<Item = Rule>) {
290        for rule in rules {
291            self.rule_parameters(&rule);
292            self.rules.push(rule);
293        }
294    }
295
296    fn checks(&mut self, checks: impl Iterator<Item = Check>) {
297        for check in checks {
298            for rule in check.queries.iter() {
299                self.rule_parameters(rule);
300            }
301            self.checks.push(check);
302        }
303    }
304
305    fn policies(&mut self, policies: impl Iterator<Item = Policy>) {
306        for policy in policies {
307            for rule in policy.queries.iter() {
308                self.rule_parameters(rule);
309            }
310            self.policies.push(policy);
311        }
312    }
313
314    fn validate(&self) -> Result<(), error::LanguageError> {
315        if self.macro_parameters.is_subset(&self.datalog_parameters) {
316            Ok(())
317        } else {
318            let unused_parameters: Vec<String> = self
319                .macro_parameters
320                .difference(&self.datalog_parameters)
321                .cloned()
322                .collect();
323            Err(error::LanguageError::Parameters {
324                missing_parameters: Vec::new(),
325                unused_parameters,
326            })
327        }
328    }
329}
330
331struct Item {
332    parameters: HashSet<String>,
333    start: TokenStream,
334    middle: TokenStream,
335    end: TokenStream,
336}
337
338impl Item {
339    fn fact(fact: &Fact) -> Self {
340        Self {
341            parameters: fact
342                .parameters
343                .iter()
344                .flatten()
345                .map(|(name, _)| name.to_owned())
346                .collect(),
347            start: quote! {
348                let mut __biscuit_auth_item = #fact;
349            },
350            middle: TokenStream::new(),
351            end: quote! {
352                __biscuit_auth_builder.add_fact(__biscuit_auth_item).unwrap();
353            },
354        }
355    }
356    fn rule(rule: &Rule) -> Self {
357        Self {
358            parameters: Item::rule_params(rule).collect(),
359            start: quote! {
360                let mut __biscuit_auth_item = #rule;
361            },
362            middle: TokenStream::new(),
363            end: quote! {
364                __biscuit_auth_builder.add_rule(__biscuit_auth_item).unwrap();
365            },
366        }
367    }
368
369    fn check(check: &Check) -> Self {
370        Self {
371            parameters: check.queries.iter().flat_map(Item::rule_params).collect(),
372            start: quote! {
373                let mut __biscuit_auth_item = #check;
374            },
375            middle: TokenStream::new(),
376            end: quote! {
377                __biscuit_auth_builder.add_check(__biscuit_auth_item).unwrap();
378            },
379        }
380    }
381
382    fn policy(policy: &Policy) -> Self {
383        Self {
384            parameters: policy.queries.iter().flat_map(Item::rule_params).collect(),
385            start: quote! {
386                let mut __biscuit_auth_item = #policy;
387            },
388            middle: TokenStream::new(),
389            end: quote! {
390                __biscuit_auth_builder.add_policy(__biscuit_auth_item).unwrap();
391            },
392        }
393    }
394
395    fn rule_params(rule: &Rule) -> impl Iterator<Item = String> + '_ {
396        rule.parameters
397            .iter()
398            .flatten()
399            .map(|(name, _)| name.as_ref())
400            .chain(
401                rule.scope_parameters
402                    .iter()
403                    .flatten()
404                    .map(|(name, _)| name.as_ref()),
405            )
406            .map(str::to_owned)
407    }
408
409    fn needs_param(&self, name: &str) -> bool {
410        self.parameters.contains(name)
411    }
412
413    fn add_param(&mut self, name: &str, clone: bool) {
414        let ident = Ident::new(name, Span::call_site());
415
416        let expr = if clone {
417            quote! { ::core::clone::Clone::clone(&#ident) }
418        } else {
419            quote! { #ident }
420        };
421
422        self.middle.extend(quote! {
423            __biscuit_auth_item.set_macro_param(#name, #expr).unwrap();
424        });
425    }
426}
427
428impl ToTokens for Item {
429    fn to_tokens(&self, tokens: &mut TokenStream) {
430        tokens.extend(self.start.clone());
431        tokens.extend(self.middle.clone());
432        tokens.extend(self.end.clone());
433    }
434}
435
436impl ToTokens for Builder {
437    fn to_tokens(&self, tokens: &mut TokenStream) {
438        let params_quote = {
439            let (ident, expr): (Vec<_>, Vec<_>) = self
440                .parameters
441                .iter()
442                .map(|(name, expr)| {
443                    let ident = Ident::new(name, Span::call_site());
444                    (ident, expr)
445                })
446                .unzip();
447
448            // Bind all parameters "in parallel". If this were a sequence of let bindings,
449            // earlier bindings would affect the scope of later bindings.
450            quote! {
451                let (#(#ident),*) = (#(#expr),*);
452            }
453        };
454
455        let mut items = self
456            .facts
457            .iter()
458            .map(Item::fact)
459            .chain(self.rules.iter().map(Item::rule))
460            .chain(self.checks.iter().map(Item::check))
461            .chain(self.policies.iter().map(Item::policy))
462            .collect::<Vec<_>>();
463
464        for param in &self.datalog_parameters {
465            let mut items = items.iter_mut().filter(|i| i.needs_param(param)).peekable();
466
467            loop {
468                match (items.next(), items.peek()) {
469                    (Some(cur), Some(_next)) => cur.add_param(param, true),
470                    (Some(cur), None) => cur.add_param(param, false),
471                    (None, _) => break,
472                }
473            }
474        }
475
476        let builder_type = &self.builder_type;
477        let builder_quote = if let Some(target) = &self.target {
478            quote! {
479                let __biscuit_auth_builder: &mut #builder_type = #target;
480            }
481        } else {
482            quote! {
483                let mut __biscuit_auth_builder = <#builder_type>::new();
484            }
485        };
486
487        tokens.extend(quote! {
488            {
489                #builder_quote
490                #params_quote
491                #(#items)*
492                __biscuit_auth_builder
493            }
494        });
495    }
496}
497
498/// Create a `Rule` from a datalog string and optional parameters.
499/// The datalog string is parsed at compile time and replaced by manual
500/// builder calls.
501#[proc_macro]
502#[proc_macro_error]
503pub fn rule(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
504    let ParsedCreateNew {
505        datalog,
506        parameters,
507    } = syn::parse_macro_input!(input as ParsedCreateNew);
508
509    // here we reuse the machinery made for managing parameter substitution
510    // for whole blocks. Of course, we're only interested in a single rule
511    // here. The block management happens only at compile-time, so it won't
512    // affect runtime performance.
513    let ty = syn::parse_quote!(::biscuit_auth::builder::BlockBuilder);
514    let builder = Builder::block_source(ty, None, &datalog, parameters)
515        .unwrap_or_else(|e| abort_call_site!(e.to_string()));
516
517    let mut rule_item = if let Some(r) = builder.rules.first() {
518        if builder.rules.len() == 1 && builder.facts.is_empty() && builder.checks.is_empty() {
519            Item::rule(r)
520        } else {
521            abort_call_site!("The rule macro only accepts a single rule as input")
522        }
523    } else {
524        abort_call_site!("The rule macro only accepts a single rule as input")
525    };
526
527    // here we are only interested in returning the rule, not adding it to a
528    // builder, so we override the default behaviour and just return the rule
529    // instead of calling `add_rule`
530    rule_item.end = quote! {
531      __biscuit_auth_item
532    };
533
534    let params_quote = {
535        let (ident, expr): (Vec<_>, Vec<_>) = builder
536            .parameters
537            .iter()
538            .map(|(name, expr)| {
539                let ident = Ident::new(name, Span::call_site());
540                (ident, expr)
541            })
542            .unzip();
543
544        // Bind all parameters "in parallel". If this were a sequence of let bindings,
545        // earlier bindings would affect the scope of later bindings.
546        quote! {
547            let (#(#ident),*) = (#(#expr),*);
548        }
549    };
550
551    for param in &builder.datalog_parameters {
552        if rule_item.needs_param(param) {
553            rule_item.add_param(param, false);
554        }
555    }
556
557    (quote! {
558        {
559            #params_quote
560            #rule_item
561        }
562    })
563    .into()
564}
565
566/// Create a `Fact` from a datalog string and optional parameters.
567/// The datalog string is parsed at compile time and replaced by manual
568/// builder calls.
569#[proc_macro]
570#[proc_macro_error]
571pub fn fact(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
572    let ParsedCreateNew {
573        datalog,
574        parameters,
575    } = syn::parse_macro_input!(input as ParsedCreateNew);
576
577    // here we reuse the machinery made for managing parameter substitution
578    // for whole blocks. Of course, we're only interested in a single fact
579    // here. The block management happens only at compile-time, so it won't
580    // affect runtime performance.
581    let ty = syn::parse_quote!(::biscuit_auth::builder::BlockBuilder);
582    let builder = Builder::block_source(ty, None, &datalog, parameters)
583        .unwrap_or_else(|e| abort_call_site!(e.to_string()));
584
585    let mut fact_item = if let Some(f) = builder.facts.first() {
586        if builder.facts.len() == 1 && builder.rules.is_empty() && builder.checks.is_empty() {
587            Item::fact(f)
588        } else {
589            abort_call_site!("The fact macro only accepts a single fact as input")
590        }
591    } else {
592        abort_call_site!("The fact macro only accepts a single fact as input")
593    };
594
595    // here we are only interested in returning the fact, not adding it to a
596    // builder, so we override the default behaviour and just return the fact
597    // instead of calling `add_fact`
598    fact_item.end = quote! {
599      __biscuit_auth_item
600    };
601
602    let params_quote = {
603        let (ident, expr): (Vec<_>, Vec<_>) = builder
604            .parameters
605            .iter()
606            .map(|(name, expr)| {
607                let ident = Ident::new(name, Span::call_site());
608                (ident, expr)
609            })
610            .unzip();
611
612        // Bind all parameters "in parallel". If this were a sequence of let bindings,
613        // earlier bindings would affect the scope of later bindings.
614        quote! {
615            let (#(#ident),*) = (#(#expr),*);
616        }
617    };
618
619    for param in &builder.datalog_parameters {
620        if fact_item.needs_param(param) {
621            fact_item.add_param(param, false);
622        }
623    }
624
625    (quote! {
626        {
627            #params_quote
628            #fact_item
629        }
630    })
631    .into()
632}
633
634/// Create a `Check` from a datalog string and optional parameters.
635/// The datalog string is parsed at compile time and replaced by manual
636/// builder calls.
637#[proc_macro]
638#[proc_macro_error]
639pub fn check(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
640    let ParsedCreateNew {
641        datalog,
642        parameters,
643    } = syn::parse_macro_input!(input as ParsedCreateNew);
644
645    // here we reuse the machinery made for managing parameter substitution
646    // for whole blocks. Of course, we're only interested in a single check
647    // here. The block management happens only at compile-time, so it won't
648    // affect runtime performance.
649    let ty = syn::parse_quote!(::biscuit_auth::builder::BlockBuilder);
650    let builder = Builder::block_source(ty, None, &datalog, parameters)
651        .unwrap_or_else(|e| abort_call_site!(e.to_string()));
652
653    let mut check_item = if let Some(c) = builder.checks.first() {
654        if builder.checks.len() == 1 && builder.facts.is_empty() && builder.rules.is_empty() {
655            Item::check(c)
656        } else {
657            abort_call_site!("The check macro only accepts a single check as input")
658        }
659    } else {
660        abort_call_site!("The check macro only accepts a single check as input")
661    };
662
663    // here we are only interested in returning the check, not adding it to a
664    // builder, so we override the default behaviour and just return the check
665    // instead of calling `add_check`
666    check_item.end = quote! {
667      __biscuit_auth_item
668    };
669
670    let params_quote = {
671        let (ident, expr): (Vec<_>, Vec<_>) = builder
672            .parameters
673            .iter()
674            .map(|(name, expr)| {
675                let ident = Ident::new(name, Span::call_site());
676                (ident, expr)
677            })
678            .unzip();
679
680        // Bind all parameters "in parallel". If this were a sequence of let bindings,
681        // earlier bindings would affect the scope of later bindings.
682        quote! {
683            let (#(#ident),*) = (#(#expr),*);
684        }
685    };
686
687    for param in &builder.datalog_parameters {
688        if check_item.needs_param(param) {
689            check_item.add_param(param, false);
690        }
691    }
692
693    (quote! {
694        {
695            #params_quote
696            #check_item
697        }
698    })
699    .into()
700}
701
702/// Create a `Policy` from a datalog string and optional parameters.
703/// The datalog string is parsed at compile time and replaced by manual
704/// builder calls.
705#[proc_macro]
706#[proc_macro_error]
707pub fn policy(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
708    let ParsedCreateNew {
709        datalog,
710        parameters,
711    } = syn::parse_macro_input!(input as ParsedCreateNew);
712
713    // here we reuse the machinery made for managing parameter substitution
714    // for whole blocks. Of course, we're only interested in a single policy
715    // here. The block management happens only at compile-time, so it won't
716    // affect runtime performance.
717    let ty = syn::parse_quote!(::biscuit_auth::Authorizer);
718    let builder = Builder::source(ty, None, &datalog, parameters)
719        .unwrap_or_else(|e| abort_call_site!(e.to_string()));
720
721    let mut policy_item = if let Some(p) = builder.policies.first() {
722        if builder.policies.len() == 1
723            && builder.facts.is_empty()
724            && builder.rules.is_empty()
725            && builder.checks.is_empty()
726        {
727            Item::policy(p)
728        } else {
729            abort_call_site!("The policy macro only accepts a single policy as input")
730        }
731    } else {
732        abort_call_site!("The policy macro only accepts a single policy as input")
733    };
734
735    // here we are only interested in returning the policy, not adding it to a
736    // builder, so we override the default behaviour and just return the policy
737    // instead of calling `add_policy`
738    policy_item.end = quote! {
739      __biscuit_auth_item
740    };
741
742    let params_quote = {
743        let (ident, expr): (Vec<_>, Vec<_>) = builder
744            .parameters
745            .iter()
746            .map(|(name, expr)| {
747                let ident = Ident::new(name, Span::call_site());
748                (ident, expr)
749            })
750            .unzip();
751
752        // Bind all parameters "in parallel". If this were a sequence of let bindings,
753        // earlier bindings would affect the scope of later bindings.
754        quote! {
755            let (#(#ident),*) = (#(#expr),*);
756        }
757    };
758
759    for param in &builder.datalog_parameters {
760        if policy_item.needs_param(param) {
761            policy_item.add_param(param, false);
762        }
763    }
764
765    (quote! {
766        {
767            #params_quote
768            #policy_item
769        }
770    })
771    .into()
772}