1extern crate self as rstest_bdd;
6
7pub mod config;
8pub mod execution;
9mod macros;
10mod skip;
11
12#[must_use]
22pub fn greet() -> &'static str {
23 "Hello from rstest-bdd!"
24}
25
26#[cfg(feature = "diagnostics")]
27use ctor::ctor;
28pub use i18n_embed::fluent::FluentLanguageLoader;
29pub use inventory::{iter, submit};
30
31pub mod async_step;
32mod context;
33pub mod datatable;
34pub mod localization;
35pub mod panic_support;
36mod pattern;
37mod placeholder;
38mod registry;
39pub mod reporting;
40mod skip_helpers;
41pub mod state;
42pub mod step_args;
43mod types;
44
45#[cfg(feature = "test-support")]
46pub mod test_support;
47
48pub use context::{FixtureRef, FixtureRefMut, StepContext};
49pub use localization::{
50 LocalizationError, Localizations, current_languages, install_localization_loader,
51 select_localizations,
52};
53pub use pattern::StepPattern;
54pub use placeholder::extract_placeholders;
55#[cfg(feature = "diagnostics")]
56pub use registry::dump_registry;
57pub use registry::record_bypassed_steps;
58pub use registry::record_bypassed_steps_with_tags;
59pub use registry::{
60 Step, duplicate_steps, find_step, find_step_async, find_step_async_with_mode,
61 find_step_with_metadata, find_step_with_mode, lookup_step, lookup_step_async,
62 lookup_step_async_with_mode, unused_steps,
63};
64
65#[must_use]
67pub const fn diagnostics_enabled() -> bool {
68 cfg!(feature = "diagnostics")
69}
70#[doc(hidden)]
71pub use panic_support::catch_unwind_future as __rstest_bdd_catch_unwind_future;
72#[doc(hidden)]
73pub use skip::{
74 ScopeKind as __rstest_bdd_scope_kind, SkipRequest, StepScopeGuard as __rstest_bdd_scope_guard,
75 enter_scope as __rstest_bdd_enter_scope,
76 request_current_skip as __rstest_bdd_request_current_skip,
77};
78#[doc(hidden)]
79pub use skip_helpers::{
80 __rstest_bdd_assert_scenario_detail_flag, __rstest_bdd_assert_scenario_detail_message_absent,
81 __rstest_bdd_assert_scenario_detail_message_contains,
82 __rstest_bdd_assert_step_skipped_message_absent,
83 __rstest_bdd_assert_step_skipped_message_contains, __rstest_bdd_expect_skip_flag,
84 __rstest_bdd_expect_skip_message_absent, __rstest_bdd_expect_skip_message_contains,
85 __rstest_bdd_unwrap_step_skipped,
86};
87pub use state::{ScenarioState, Slot};
88pub use step_args::{StepArgs, StepArgsError};
89pub use types::{
90 AsyncStepFn, PatternStr, PlaceholderError, PlaceholderSyntaxError, StepCtx, StepDoc,
91 StepExecution, StepExecutionMode, StepFn, StepFuture, StepKeyword, StepKeywordParseError,
92 StepPatternError, StepTable, StepText, StepTextRef, UnsupportedStepType,
93};
94
95pub use execution::{ExecutionError, MissingFixturesDetails};
96
97#[cfg(feature = "diagnostics")]
98#[ctor]
99fn dump_steps() {
100 if std::env::var_os("RSTEST_BDD_DUMP_STEPS").is_some()
102 && std::env::args().any(|a| a == "--dump-steps")
103 {
104 reporting::run_dump_seeds();
105 #[expect(
106 clippy::print_stdout,
107 clippy::print_stderr,
108 reason = "registry dump is written to standard streams"
109 )]
110 {
111 match dump_registry() {
112 Ok(json) => println!("{json}"),
113 Err(e) => eprintln!("failed to serialize step registry: {e}"),
114 }
115 }
116 std::process::exit(0);
117 }
118}
119
120pub use panic_support::panic_message;
121
122#[doc(hidden)]
123#[must_use]
124pub fn __rstest_bdd_payload_from_value<T: std::any::Any>(
125 value: T,
126) -> Option<Box<dyn std::any::Any>> {
127 if std::any::TypeId::of::<T>() == std::any::TypeId::of::<()>() {
128 None
129 } else {
130 Some(Box::new(value) as Box<dyn std::any::Any>)
131 }
132}
133
134#[derive(Debug, Clone, PartialEq, Eq)]
138#[non_exhaustive]
139pub enum StepError {
140 MissingFixture {
142 name: String,
144 ty: String,
146 step: String,
148 },
149 ExecutionError {
151 pattern: String,
153 function: String,
155 message: String,
157 },
158 PanicError {
160 pattern: String,
162 function: String,
164 message: String,
166 },
167}
168
169macro_rules! step_error_message {
172 (
173 $self:expr,
174 $loader:expr,
175 $( $variant:ident { $( $field:ident ),* } => $id:literal ),+ $(,)?
176 ) => {{
177 match $self {
178 $(
179 Self::$variant { $( $field ),* } => {
180 $crate::localization::message_with_loader($loader, $id, |args| {
181 $( args.set(stringify!($field), $field.clone()); )*
182 })
183 }
184 ),+
185 }
186 }};
187}
188
189impl StepError {
190 #[must_use]
215 pub fn format_with_loader(&self, loader: &FluentLanguageLoader) -> String {
216 step_error_message!(
217 self,
218 loader,
219 MissingFixture { name, ty, step } => "step-error-missing-fixture",
220 ExecutionError { pattern, function, message } => "step-error-execution",
221 PanicError { pattern, function, message } => "step-error-panic",
222 )
223 }
224}
225
226impl std::fmt::Display for StepError {
227 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
228 let message = localization::with_loader(|loader| self.format_with_loader(loader));
229 f.write_str(&message)
230 }
231}
232
233impl std::error::Error for StepError {}
234
235pub type StepResult<T, E = StepError> = Result<T, E>;