rstest_bdd_macros/lib.rs
1//! Attribute macros enabling Behaviour-Driven testing with `rstest`.
2//!
3//! # Feature flags
4//! - `compile-time-validation`: registers steps at compile time and attaches
5//! spans for diagnostics.
6//! - `strict-compile-time-validation`: escalates missing or ambiguous steps to
7//! compile errors; implies `compile-time-validation`.
8//!
9//! Both features are disabled by default.
10#![cfg_attr(docsrs, feature(doc_cfg))]
11
12mod codegen;
13mod datatable;
14mod macros;
15mod parsing;
16mod pattern;
17mod scenario_state;
18mod step_args;
19mod step_keyword;
20mod utils;
21mod validation;
22
23pub(crate) use step_keyword::StepKeyword;
24
25use proc_macro::TokenStream;
26use std::panic::UnwindSafe;
27
28use proc_macro_error::entry_point;
29use proc_macro_error::proc_macro_error;
30
31/// Run a procedural macro while mapping panics into `proc_macro_error`
32/// diagnostics.
33///
34/// The supplied closure should return the generated tokens. Any `abort!` or
35/// emitted `Diagnostic` is forwarded to the compiler, matching the behaviour of
36/// the `#[proc_macro_error]` attribute without tripping the workspace
37/// `missing_docs` lint.
38///
39/// # Examples
40/// ```ignore
41/// use proc_macro::TokenStream;
42///
43/// fn expand(tokens: TokenStream) -> TokenStream { tokens }
44///
45/// let input = TokenStream::new();
46/// run_with_macro_errors(|| expand(input));
47/// ```
48fn run_with_macro_errors<F>(expand: F) -> TokenStream
49where
50 F: FnOnce() -> TokenStream + UnwindSafe,
51{
52 entry_point(expand, false)
53}
54
55/// Attribute macro registering a step definition for the `Given` keyword.
56///
57/// # Examples
58/// ```ignore
59/// use rstest_bdd_macros::given;
60///
61/// #[given("a configured database")]
62/// fn a_configured_database() {}
63/// ```
64#[proc_macro_attribute]
65pub fn given(attr: TokenStream, item: TokenStream) -> TokenStream {
66 run_with_macro_errors(|| macros::given(attr, item))
67}
68
69/// Attribute macro registering a step definition for the `When` keyword.
70///
71/// # Examples
72/// ```ignore
73/// use rstest_bdd_macros::when;
74///
75/// #[when("the user logs in")]
76/// fn the_user_logs_in() {}
77/// ```
78#[proc_macro_attribute]
79pub fn when(attr: TokenStream, item: TokenStream) -> TokenStream {
80 run_with_macro_errors(|| macros::when(attr, item))
81}
82
83/// Attribute macro registering a step definition for the `Then` keyword.
84///
85/// # Examples
86/// ```ignore
87/// use rstest_bdd_macros::then;
88///
89/// #[then("a success message is shown")]
90/// fn a_success_message_is_shown() {}
91/// ```
92#[proc_macro_attribute]
93pub fn then(attr: TokenStream, item: TokenStream) -> TokenStream {
94 run_with_macro_errors(|| macros::then(attr, item))
95}
96
97/// Attribute macro binding a test function to a single Gherkin scenario.
98///
99/// Selector semantics:
100/// - Supply either `index = N` (zero-based) or `name = "Scenario title"` to
101/// disambiguate when the feature defines multiple scenarios.
102/// - When omitted, the macro targets the first scenario in the feature file.
103///
104/// Tag filtering:
105/// - Provide `tags = "expr"` to keep only scenarios whose tag sets satisfy the
106/// expression before applying selectors.
107/// - Expressions accept case-sensitive tag names combined with `not`, `and`,
108/// and `or`, following the precedence `not` > `and` > `or`. Parentheses may
109/// be used to override the default binding.
110///
111/// Example:
112/// ```ignore
113/// use rstest_bdd_macros::scenario;
114///
115/// #[scenario(
116/// "tests/features/filtering.feature",
117/// tags = "@fast and not (@wip or @flaky)"
118/// )]
119/// fn fast_stable_cases() {}
120/// ```
121#[proc_macro_attribute]
122pub fn scenario(attr: TokenStream, item: TokenStream) -> TokenStream {
123 run_with_macro_errors(|| macros::scenario(attr, item))
124}
125
126/// Derive `ScenarioState` for a type that shares context across steps.
127///
128/// # Examples
129/// ```ignore
130/// use rstest_bdd_macros::ScenarioState;
131///
132/// #[derive(ScenarioState)]
133/// struct SharedState {
134/// pub counter: u32,
135/// }
136/// ```
137#[proc_macro_error]
138#[proc_macro_derive(ScenarioState)]
139pub fn derive_scenario_state(input: TokenStream) -> TokenStream {
140 scenario_state::derive(input)
141}
142
143/// Derive [`StepArgs`](rstest_bdd::step_args::StepArgs) for a struct whose
144/// fields map to pattern placeholders.
145#[proc_macro_error]
146#[proc_macro_derive(StepArgs)]
147pub fn derive_step_args(input: TokenStream) -> TokenStream {
148 step_args::derive(input)
149}
150
151/// Discover all `.feature` files under the given directory and generate one
152/// test per Gherkin `Scenario`.
153///
154/// Path semantics:
155/// - The `dir` argument must be a string literal.
156/// - It is resolved relative to `CARGO_MANIFEST_DIR` at macro-expansion time.
157///
158/// Expansion:
159/// - Emits a module named after `dir` (sanitized) containing one test function
160/// per discovered scenario.
161/// - Each generated test executes the matched steps via the registered
162/// `#[given]`, `#[when]`, and `#[then]` functions.
163///
164/// Example:
165/// ```rust,ignore
166/// use rstest_bdd_macros::{given, when, then, scenarios};
167///
168/// # #[given("a precondition")] fn precondition() {}
169/// # #[when("an action occurs")] fn action() {}
170/// # #[then("events are recorded")] fn events_recorded() {}
171/// scenarios!("tests/features/auto");
172/// ```
173///
174/// Errors:
175/// - Emits a compile error if the directory does not exist, contains no
176/// `.feature` files, or if parsing fails.
177#[proc_macro]
178pub fn scenarios(input: TokenStream) -> TokenStream {
179 run_with_macro_errors(|| macros::scenarios(input))
180}
181
182/// Derive `DataTableRow` for structs that should parse Gherkin rows.
183///
184/// The macro honours field-level overrides via `#[datatable(...)]` attributes
185/// documented in the user guide.
186#[proc_macro_derive(DataTableRow, attributes(datatable))]
187pub fn derive_data_table_row(input: TokenStream) -> TokenStream {
188 run_with_macro_errors(|| datatable::derive_data_table_row(input))
189}
190
191/// Derive `DataTable` for tuple structs wrapping collections of rows.
192///
193/// The macro supports optional mapping hooks and row type inference as
194/// described in the user guide.
195#[proc_macro_derive(DataTable, attributes(datatable))]
196pub fn derive_data_table(input: TokenStream) -> TokenStream {
197 run_with_macro_errors(|| datatable::derive_data_table(input))
198}