diff --git a/biscuit-quote/Cargo.toml b/biscuit-quote/Cargo.toml index 3da15970..a7fd9024 100644 --- a/biscuit-quote/Cargo.toml +++ b/biscuit-quote/Cargo.toml @@ -14,7 +14,9 @@ biscuit-parser = { path = "../biscuit-parser", features = ["datalog-macro"], ver proc-macro2 = "1" quote = "1.0.14" syn = { version = "1.0.85", features = ["full", "extra-traits"] } -proc-macro-error2 = "2.0" +manyhow = "0.11" [dev-dependencies] +biscuit-auth = { path = "../biscuit-auth" } hex = "0.4.3" +trybuild = "1.0.116" diff --git a/biscuit-quote/src/lib.rs b/biscuit-quote/src/lib.rs index af0dd74b..5750d3db 100644 --- a/biscuit-quote/src/lib.rs +++ b/biscuit-quote/src/lib.rs @@ -10,7 +10,7 @@ use biscuit_parser::{ parser::{parse_block_source, parse_source}, }; use proc_macro2::{Span, TokenStream}; -use proc_macro_error2::{abort_call_site, proc_macro_error}; +use manyhow::bail; use quote::{quote, ToTokens}; use std::collections::{HashMap, HashSet}; use syn::{ @@ -88,112 +88,112 @@ impl Parse for ParsedMerge { /// Create a `BlockBuilder` from a datalog string and optional parameters. /// The datalog string is parsed at compile time and replaced by manual /// block building. +#[manyhow::manyhow] #[proc_macro] -#[proc_macro_error] -pub fn block(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn block(input: proc_macro::TokenStream) -> manyhow::Result { let ParsedCreateNew { datalog, parameters, - } = syn::parse_macro_input!(input as ParsedCreateNew); + } = syn::parse::(input).map_err(|e| manyhow::error_message!("{}", e))?; 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())); + .map_err(|e| manyhow::error_message!("{}", e))?; - builder.into_token_stream().into() + Ok(builder.into_token_stream().into()) } /// Merge facts, rules, and checks into a `BlockBuilder` from a datalog /// string and optional parameters. The datalog string is parsed at compile time /// and replaced by manual block building. +#[manyhow::manyhow] #[proc_macro] -#[proc_macro_error] -pub fn block_merge(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn block_merge(input: proc_macro::TokenStream) -> manyhow::Result { let ParsedMerge { target, datalog, parameters, - } = syn::parse_macro_input!(input as ParsedMerge); + } = syn::parse::(input).map_err(|e| manyhow::error_message!("{}", e))?; let ty = syn::parse_quote!(::biscuit_auth::builder::BlockBuilder); let builder = Builder::block_source(ty, Some(target), datalog, parameters) - .unwrap_or_else(|e| abort_call_site!(e.to_string())); + .map_err(|e| manyhow::error_message!("{}", e))?; - builder.into_token_stream().into() + Ok(builder.into_token_stream().into()) } /// Create an `Authorizer` from a datalog string and optional parameters. /// The datalog string is parsed at compile time and replaced by manual /// block building. +#[manyhow::manyhow] #[proc_macro] -#[proc_macro_error] -pub fn authorizer(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn authorizer(input: proc_macro::TokenStream) -> manyhow::Result { let ParsedCreateNew { datalog, parameters, - } = syn::parse_macro_input!(input as ParsedCreateNew); + } = syn::parse::(input).map_err(|e| manyhow::error_message!("{}", e))?; let ty = syn::parse_quote!(::biscuit_auth::builder::AuthorizerBuilder); let builder = Builder::source(ty, None, datalog, parameters) - .unwrap_or_else(|e| abort_call_site!(e.to_string())); + .map_err(|e| manyhow::error_message!("{}", e))?; - builder.into_token_stream().into() + Ok(builder.into_token_stream().into()) } /// Merge facts, rules, checks, and policies into an `Authorizer` from a datalog /// string and optional parameters. The datalog string is parsed at compile time /// and replaced by manual block building. +#[manyhow::manyhow] #[proc_macro] -#[proc_macro_error] -pub fn authorizer_merge(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn authorizer_merge(input: proc_macro::TokenStream) -> manyhow::Result { let ParsedMerge { target, datalog, parameters, - } = syn::parse_macro_input!(input as ParsedMerge); + } = syn::parse::(input).map_err(|e| manyhow::error_message!("{}", e))?; let ty = syn::parse_quote!(::biscuit_auth::builder::AuthorizerBuilder); let builder = Builder::source(ty, Some(target), datalog, parameters) - .unwrap_or_else(|e| abort_call_site!(e.to_string())); + .map_err(|e| manyhow::error_message!("{}", e))?; - builder.into_token_stream().into() + Ok(builder.into_token_stream().into()) } /// Create an `BiscuitBuilder` from a datalog string and optional parameters. /// The datalog string is parsed at compile time and replaced by manual /// block building. +#[manyhow::manyhow] #[proc_macro] -#[proc_macro_error] -pub fn biscuit(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn biscuit(input: proc_macro::TokenStream) -> manyhow::Result { let ParsedCreateNew { datalog, parameters, - } = syn::parse_macro_input!(input as ParsedCreateNew); + } = syn::parse::(input).map_err(|e| manyhow::error_message!("{}", e))?; let ty = syn::parse_quote!(::biscuit_auth::builder::BiscuitBuilder); let builder = Builder::block_source(ty, None, datalog, parameters) - .unwrap_or_else(|e| abort_call_site!(e.to_string())); + .map_err(|e| manyhow::error_message!("{}", e))?; - builder.into_token_stream().into() + Ok(builder.into_token_stream().into()) } /// Merge facts, rules, and checks into a `BiscuitBuilder` from a datalog /// string and optional parameters. The datalog string is parsed at compile time /// and replaced by manual block building. +#[manyhow::manyhow] #[proc_macro] -#[proc_macro_error] -pub fn biscuit_merge(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn biscuit_merge(input: proc_macro::TokenStream) -> manyhow::Result { let ParsedMerge { target, datalog, parameters, - } = syn::parse_macro_input!(input as ParsedMerge); + } = syn::parse::(input).map_err(|e| manyhow::error_message!("{}", e))?; let ty = syn::parse_quote!(::biscuit_auth::builder::BiscuitBuilder); let builder = Builder::block_source(ty, Some(target), datalog, parameters) - .unwrap_or_else(|e| abort_call_site!(e.to_string())); + .map_err(|e| manyhow::error_message!("{}", e))?; - builder.into_token_stream().into() + Ok(builder.into_token_stream().into()) } #[derive(Clone, Debug)] @@ -556,13 +556,13 @@ 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 /// builder calls. +#[manyhow::manyhow] #[proc_macro] -#[proc_macro_error] -pub fn rule(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn rule(input: proc_macro::TokenStream) -> manyhow::Result { let ParsedCreateNew { datalog, parameters, - } = syn::parse_macro_input!(input as ParsedCreateNew); + } = syn::parse::(input).map_err(|e| manyhow::error_message!("{}", e))?; // here we reuse the machinery made for managing parameter substitution // for whole blocks. Of course, we're only interested in a single rule @@ -570,16 +570,16 @@ pub fn rule(input: proc_macro::TokenStream) -> proc_macro::TokenStream { // 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())); + .map_err(|e| manyhow::error_message!("{}", e))?; let mut rule_item = if let Some(r) = builder.rules.first() { if builder.rules.len() == 1 && builder.facts.is_empty() && builder.checks.is_empty() { Item::rule(r) } else { - abort_call_site!("The rule macro only accepts a single rule as input") + bail!("The rule macro only accepts a single rule as input") } } else { - abort_call_site!("The rule macro only accepts a single rule as input") + bail!("The rule macro only accepts a single rule as input") }; // here we are only interested in returning the rule, not adding it to a @@ -618,25 +618,25 @@ pub fn rule(input: proc_macro::TokenStream) -> proc_macro::TokenStream { } } - (quote! { + Ok((quote! { { #params_quote #rule_item } }) - .into() + .into()) } /// Create a `Fact` from a datalog string and optional parameters. /// The datalog string is parsed at compile time and replaced by manual /// builder calls. +#[manyhow::manyhow] #[proc_macro] -#[proc_macro_error] -pub fn fact(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn fact(input: proc_macro::TokenStream) -> manyhow::Result { let ParsedCreateNew { datalog, parameters, - } = syn::parse_macro_input!(input as ParsedCreateNew); + } = syn::parse::(input).map_err(|e| manyhow::error_message!("{}", e))?; // here we reuse the machinery made for managing parameter substitution // for whole blocks. Of course, we're only interested in a single fact @@ -644,16 +644,16 @@ pub fn fact(input: proc_macro::TokenStream) -> proc_macro::TokenStream { // 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())); + .map_err(|e| manyhow::error_message!("{}", e))?; let mut fact_item = if let Some(f) = builder.facts.first() { if builder.facts.len() == 1 && builder.rules.is_empty() && builder.checks.is_empty() { Item::fact(f) } else { - abort_call_site!("The fact macro only accepts a single fact as input") + bail!("The fact macro only accepts a single fact as input") } } else { - abort_call_site!("The fact macro only accepts a single fact as input") + bail!("The fact macro only accepts a single fact as input") }; // here we are only interested in returning the fact, not adding it to a @@ -686,25 +686,25 @@ pub fn fact(input: proc_macro::TokenStream) -> proc_macro::TokenStream { } } - (quote! { + Ok((quote! { { #params_quote #fact_item } }) - .into() + .into()) } /// Create a `Check` from a datalog string and optional parameters. /// The datalog string is parsed at compile time and replaced by manual /// builder calls. +#[manyhow::manyhow] #[proc_macro] -#[proc_macro_error] -pub fn check(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn check(input: proc_macro::TokenStream) -> manyhow::Result { let ParsedCreateNew { datalog, parameters, - } = syn::parse_macro_input!(input as ParsedCreateNew); + } = syn::parse::(input).map_err(|e| manyhow::error_message!("{}", e))?; // here we reuse the machinery made for managing parameter substitution // for whole blocks. Of course, we're only interested in a single check @@ -712,16 +712,16 @@ pub fn check(input: proc_macro::TokenStream) -> proc_macro::TokenStream { // 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())); + .map_err(|e| manyhow::error_message!("{}", e))?; let mut check_item = if let Some(c) = builder.checks.first() { if builder.checks.len() == 1 && builder.facts.is_empty() && builder.rules.is_empty() { Item::check(c) } else { - abort_call_site!("The check macro only accepts a single check as input") + bail!("The check macro only accepts a single check as input") } } else { - abort_call_site!("The check macro only accepts a single check as input") + bail!("The check macro only accepts a single check as input") }; // here we are only interested in returning the check, not adding it to a @@ -760,25 +760,25 @@ pub fn check(input: proc_macro::TokenStream) -> proc_macro::TokenStream { } } - (quote! { + Ok((quote! { { #params_quote #check_item } }) - .into() + .into()) } /// Create a `Policy` from a datalog string and optional parameters. /// The datalog string is parsed at compile time and replaced by manual /// builder calls. +#[manyhow::manyhow] #[proc_macro] -#[proc_macro_error] -pub fn policy(input: proc_macro::TokenStream) -> proc_macro::TokenStream { +pub fn policy(input: proc_macro::TokenStream) -> manyhow::Result { let ParsedCreateNew { datalog, parameters, - } = syn::parse_macro_input!(input as ParsedCreateNew); + } = syn::parse::(input).map_err(|e| manyhow::error_message!("{}", e))?; // here we reuse the machinery made for managing parameter substitution // for whole blocks. Of course, we're only interested in a single policy @@ -786,7 +786,7 @@ pub fn policy(input: proc_macro::TokenStream) -> proc_macro::TokenStream { // affect runtime performance. let ty = syn::parse_quote!(::biscuit_auth::Authorizer); let builder = Builder::source(ty, None, datalog, parameters) - .unwrap_or_else(|e| abort_call_site!(e.to_string())); + .map_err(|e| manyhow::error_message!("{}", e))?; let mut policy_item = if let Some(p) = builder.policies.first() { if builder.policies.len() == 1 @@ -796,10 +796,10 @@ pub fn policy(input: proc_macro::TokenStream) -> proc_macro::TokenStream { { Item::policy(p) } else { - abort_call_site!("The policy macro only accepts a single policy as input") + bail!("The policy macro only accepts a single policy as input") } } else { - abort_call_site!("The policy macro only accepts a single policy as input") + bail!("The policy macro only accepts a single policy as input") }; // here we are only interested in returning the policy, not adding it to a @@ -838,11 +838,11 @@ pub fn policy(input: proc_macro::TokenStream) -> proc_macro::TokenStream { } } - (quote! { + Ok((quote! { { #params_quote #policy_item } }) - .into() + .into()) } diff --git a/biscuit-quote/tests/error_message.rs b/biscuit-quote/tests/error_message.rs new file mode 100644 index 00000000..44ed8161 --- /dev/null +++ b/biscuit-quote/tests/error_message.rs @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2019 Geoffroy Couprie and Contributors to the Eclipse Foundation. + * SPDX-License-Identifier: Apache-2.0 +*/ +//! Test for compilation error messages. +//! Compile each file in tests/error_message/ and check that error messages haven't changed. +//! +//! run with `TRYBUILD=overwrite cargo test` to update the .stderr files containing expected error messages + +#[test] +fn test_error_msg () { + let t = trybuild::TestCases::new(); + t.compile_fail("tests/error_message/*.rs"); +} diff --git a/biscuit-quote/tests/error_message/authorizer_macro.rs b/biscuit-quote/tests/error_message/authorizer_macro.rs new file mode 100644 index 00000000..f68d9210 --- /dev/null +++ b/biscuit-quote/tests/error_message/authorizer_macro.rs @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2019 Geoffroy Couprie and Contributors to the Eclipse Foundation. + * SPDX-License-Identifier: Apache-2.0 + */ +//! Triggers compilation errors on the authorizer! proc macro. + +use biscuit_quote::authorizer; + +fn main() { + // empty biscuit, no error + let _ = authorizer!(""); + + // a valid content that doesn't trigger any error + let _ = authorizer!(r#" + can_view($file) <- right($file, "read"); + allow if file($f), operation($op), right($f, $op); + "#); + + // syntax error, missing < in <- arrow + let _ = authorizer!(r#" + can_view($file) - right($file, "read"); + allow if file($f), operation($op), right($f, $op); + "#); + + // unbound variable + let _ = authorizer!(r#" + can_view($file, $unused) <- right($file, "read"); + allow if file($f), operation($op), right($f, $op); + "#); +} \ No newline at end of file diff --git a/biscuit-quote/tests/error_message/authorizer_macro.stderr b/biscuit-quote/tests/error_message/authorizer_macro.stderr new file mode 100644 index 00000000..2636c1df --- /dev/null +++ b/biscuit-quote/tests/error_message/authorizer_macro.stderr @@ -0,0 +1,23 @@ +error: datalog parsing error: ParseErrors { errors: [ParseError { input: "$file", message: Some("variables are not allowed in facts") }] } + --> tests/error_message/authorizer_macro.rs:20:13 + | +20 | let _ = authorizer!(r#" + | _____________^ +21 | | can_view($file) - right($file, "read"); +22 | | allow if file($f), operation($op), right($f, $op); +23 | | "#); + | |_______^ + | + = note: this error originates in the macro `authorizer` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: datalog parsing error: ParseErrors { errors: [ParseError { input: "\n can_view($file, $unused) <- right($file, \"read\")", message: Some("the rule contains variables that are not bound by predicates in the rule's body: $unused") }] } + --> tests/error_message/authorizer_macro.rs:26:13 + | +26 | let _ = authorizer!(r#" + | _____________^ +27 | | can_view($file, $unused) <- right($file, "read"); +28 | | allow if file($f), operation($op), right($f, $op); +29 | | "#); + | |_______^ + | + = note: this error originates in the macro `authorizer` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/biscuit-quote/tests/error_message/biscuit_macro.rs b/biscuit-quote/tests/error_message/biscuit_macro.rs new file mode 100644 index 00000000..e301723a --- /dev/null +++ b/biscuit-quote/tests/error_message/biscuit_macro.rs @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2019 Geoffroy Couprie and Contributors to the Eclipse Foundation. + * SPDX-License-Identifier: Apache-2.0 + */ +//! Triggers compilation errors on the biscuit! proc macro. + +use biscuit_quote::biscuit; + +fn main() { + // empty biscuit, no error + let _ = biscuit!(""); + + // a valid content that doesn't trigger any arror + let _ = biscuit!(r#" + can_view("/file1"); + can_view("/file2"); + file("/file1"); + "#); + + // parsing error + let _ = biscuit!(r#" + can_view("/file1"); + typo can_view("/file2"); + file("/file1"); + "#); + + // parsing error, missing semicolon + let _ = biscuit!(r#" + can_view("/file1") + can_view("/file2"); + "#); + + let _ = biscuit!(r#" + can_view($file) <- right($file, "read"); + allow if file($f), operation($op), right($f, $op); + "#); +} \ No newline at end of file diff --git a/biscuit-quote/tests/error_message/biscuit_macro.stderr b/biscuit-quote/tests/error_message/biscuit_macro.stderr new file mode 100644 index 00000000..4a210d2a --- /dev/null +++ b/biscuit-quote/tests/error_message/biscuit_macro.stderr @@ -0,0 +1,36 @@ +error: datalog parsing error: ParseErrors { errors: [ParseError { input: "typo can_view(\"/file2\")", message: None }] } + --> tests/error_message/biscuit_macro.rs:21:13 + | +21 | let _ = biscuit!(r#" + | _____________^ +22 | | can_view("/file1"); +23 | | typo can_view("/file2"); +24 | | file("/file1"); +25 | | "#); + | |_______^ + | + = note: this error originates in the macro `biscuit` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: datalog parsing error: ParseErrors { errors: [ParseError { input: "can_view(\"/file1\")\n can_view(\"/file2\")", message: None }, ParseError { input: "", message: None }] } + --> tests/error_message/biscuit_macro.rs:28:13 + | +28 | let _ = biscuit!(r#" + | _____________^ +29 | | can_view("/file1") +30 | | can_view("/file2"); +31 | | "#); + | |_______^ + | + = note: this error originates in the macro `biscuit` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: datalog parsing error: ParseErrors { errors: [ParseError { input: "allow if file($f), operation($op), right($f, $op)", message: None }, ParseError { input: "", message: None }] } + --> tests/error_message/biscuit_macro.rs:33:13 + | +33 | let _ = biscuit!(r#" + | _____________^ +34 | | can_view($file) <- right($file, "read"); +35 | | allow if file($f), operation($op), right($f, $op); +36 | | "#); + | |_______^ + | + = note: this error originates in the macro `biscuit` (in Nightly builds, run with -Z macro-backtrace for more info)