1use 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
17struct 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
43struct 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
61struct 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#[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#[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#[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#[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#[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#[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 pub datalog_parameters: HashSet<String>,
203 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 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#[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 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 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 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#[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 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 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 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#[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 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 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 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#[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 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 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 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}