Skip to content

Commit

Permalink
Merge pull request #143 from biscuit-auth/rule-macro
Browse files Browse the repository at this point in the history
biscuit-quote: add a `rule!` macro
  • Loading branch information
divarvel authored Apr 6, 2023
2 parents 425fa18 + 315f33f commit eddb8b0
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 1 deletion.
1 change: 1 addition & 0 deletions biscuit-quote/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
- support for `biscuit`, `block`, `authorizer` macros
- support for `biscuit_merge`, `block_merge`, `authorizer_merge` macros
- support for referencing in-scope variables in macro invocations
- support for a `rule` macro
79 changes: 79 additions & 0 deletions biscuit-quote/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -641,3 +641,82 @@ impl ToTokens for Builder {
});
}
}

/// Create a `Rule` from a datalog string and optional parameters.
/// The datalog string is parsed at compile time and replaced by manual
/// block building.
///
/// ```rust
/// use biscuit_auth::Biscuit;
/// use biscuit_quote::{rule};
///
/// let b = rule!(
/// r#"is_allowed($operation) <- user({user_id}), right({user_id}, $operation)
/// "#,
/// user_id = "1234"
/// );
/// ```
#[proc_macro]
#[proc_macro_error]
pub fn rule(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let ParsedCreateNew {
datalog,
parameters,
} = syn::parse_macro_input!(input as ParsedCreateNew);

// here we reuse the machinery made for managing parameter substitution
// for whole blocks. Of course, we're only interested in a single rule
// here. The block management happens only at compile-time, so it won't
// affect runtime performance.
let ty = syn::parse_quote!(::biscuit_auth::builder::BlockBuilder);
let builder = Builder::block_source(ty, None, &datalog, parameters)
.unwrap_or_else(|e| abort_call_site!(e.to_string()));

let mut rule_item = if let Some(r) = builder.rules.first() {
if builder.rules.len() == 1 {
Item::rule(&r)
} else {
abort_call_site!("The rule macro only accepts a single rule as input")
}
} else {
abort_call_site!("The rule macro only accepts a single rule as input")
};

// here we are only interested in returning the rule, not adding it to a
// builder, so we override the default behaviour and just return the rule
// instead of calling `add_rule`
rule_item.end = quote! {
__biscuit_auth_item
};

let params_quote = {
let (ident, expr): (Vec<_>, Vec<_>) = builder
.parameters
.iter()
.map(|(name, expr)| {
let ident = Ident::new(&name, Span::call_site());
(ident, expr)
})
.unzip();

// Bind all parameters "in parallel". If this were a sequence of let bindings,
// earlier bindings would affect the scope of later bindings.
quote! {
let (#(#ident),*) = (#(#expr),*);
}
};

for param in &builder.datalog_parameters {
if rule_item.needs_param(param) {
rule_item.add_param(&param, false);
}
}

(quote! {
{
#params_quote
#rule_item
}
})
.into()
}
24 changes: 23 additions & 1 deletion biscuit-quote/tests/simple_test.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use biscuit_auth::builder;
use biscuit_quote::{authorizer, authorizer_merge, biscuit, biscuit_merge, block, block_merge};
use biscuit_quote::{
authorizer, authorizer_merge, biscuit, biscuit_merge, block, block_merge, rule,
};
use std::collections::BTreeSet;

#[test]
Expand Down Expand Up @@ -145,3 +147,23 @@ fn biscuit_macro_trailing_comma() {
"#,
);
}

#[test]
fn rule_macro() {
use biscuit_auth::PublicKey;
let pubkey = PublicKey::from_bytes(
&hex::decode("6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc70f8516583db9db").unwrap(),
)
.unwrap();
let mut term_set = BTreeSet::new();
term_set.insert(builder::int(0i64));
let b = rule!(
r#"rule($0, true) <- fact($0, $1, $2, {my_key}, {term_set}) trusting {pubkey}"#,
my_key = "my_value",
);

assert_eq!(
b.to_string(),
r#"rule($0, true) <- fact($0, $1, $2, "my_value", [0]) trusting ed25519/6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc70f8516583db9db"#,
);
}

0 comments on commit eddb8b0

Please sign in to comment.