Skip to main content

rstest_bdd/context/
mod.rs

1//! Step execution context, fixture access, and step return overrides.
2//! `StepContext` stores named fixture references plus a map of last-seen step
3//! results keyed by fixture name. Returned values must be `'static` so they can
4//! be boxed. When exactly one fixture matches a returned type, its name records
5//! the override (last write wins); ambiguous matches leave fixtures untouched.
6
7use std::any::{Any, TypeId};
8use std::cell::{Ref, RefCell, RefMut};
9use std::collections::HashMap;
10
11/// Context passed to step functions containing references to requested fixtures.
12///
13/// This is constructed by the `#[scenario]` macro for each step invocation. Use
14/// [`insert_owned`](Self::insert_owned) when a fixture should be shared
15/// mutably across steps; step functions may then request `&mut T` and mutate
16/// world state without resorting to interior mutability wrappers.
17///
18/// # Examples
19///
20/// ```
21/// use rstest_bdd::StepContext;
22///
23/// let mut ctx = StepContext::default();
24/// let value = 42;
25/// ctx.insert("my_fixture", &value);
26/// let owned = StepContext::owned_cell(String::from("hi"));
27/// ctx.insert_owned::<String>("owned", &owned);
28///
29/// let retrieved: Option<&i32> = ctx.get("my_fixture");
30/// assert_eq!(retrieved, Some(&42));
31/// {
32///     let mut suffix = ctx.borrow_mut::<String>("owned").expect("owned fixture");
33///     suffix.value_mut().push('!');
34/// }
35/// drop(ctx);
36/// let owned = owned.into_inner();
37/// let owned: String = *owned
38///     .downcast::<String>()
39///     .expect("fixture should downcast to String");
40/// assert_eq!(owned, "hi!");
41/// ```
42#[derive(Default)]
43pub struct StepContext<'a> {
44    fixtures: HashMap<&'static str, FixtureEntry<'a>>,
45    values: HashMap<&'static str, Box<dyn Any>>,
46}
47
48struct FixtureEntry<'a> {
49    kind: FixtureKind<'a>,
50    type_id: TypeId,
51}
52
53enum FixtureKind<'a> {
54    Shared(&'a dyn Any),
55    Mutable(&'a RefCell<Box<dyn Any>>),
56}
57
58impl<'a> StepContext<'a> {
59    /// Create an owned fixture cell for use with [`insert_owned`](Self::insert_owned).
60    ///
61    /// This helper boxes the provided value and erases its concrete type so it
62    /// can back a mutable fixture. Callers must retain the returned cell for as
63    /// long as the context references it.
64    #[must_use]
65    pub fn owned_cell<T: Any>(value: T) -> RefCell<Box<dyn Any>> {
66        RefCell::new(Box::new(value))
67    }
68
69    /// Insert a fixture reference by name.
70    pub fn insert<T: Any>(&mut self, name: &'static str, value: &'a T) {
71        self.fixtures.insert(name, FixtureEntry::shared(value));
72    }
73
74    /// Insert a fixture backed by a `RefCell<Box<dyn Any>>`, enabling mutable borrows.
75    ///
76    /// A runtime type check ensures the stored value matches the requested `T`
77    /// so mismatches are surfaced immediately instead of silently failing at
78    /// borrow time.
79    ///
80    /// # Panics
81    ///
82    /// Panics when the provided cell does not currently store a value of type
83    /// `T`, because continuing would render the fixture un-borrowable at run
84    /// time.
85    pub fn insert_owned<T: Any>(&mut self, name: &'static str, cell: &'a RefCell<Box<dyn Any>>) {
86        let guard = cell.borrow();
87        let actual = guard.as_ref().type_id();
88        assert!(
89            actual == TypeId::of::<T>(),
90            "insert_owned: stored value type ({actual:?}) does not match requested {:?} for fixture '{name}'",
91            TypeId::of::<T>()
92        );
93        self.fixtures.insert(name, FixtureEntry::owned::<T>(cell));
94    }
95
96    /// Retrieve a fixture reference by name and type.
97    ///
98    /// Values returned from prior `#[when]` steps override fixtures of the same
99    /// type when that type is unique among fixtures. This enables a functional
100    /// style where step return values feed into later assertions without having
101    /// to define ad-hoc fixtures.
102    #[must_use]
103    pub fn get<T: Any>(&'a self, name: &str) -> Option<&'a T> {
104        if let Some(val) = self.values.get(name) {
105            return val.downcast_ref::<T>();
106        }
107        match self.fixtures.get(name)?.kind {
108            FixtureKind::Shared(value) => value.downcast_ref::<T>(),
109            FixtureKind::Mutable(_) => None,
110        }
111    }
112
113    /// Borrow a fixture by name, keeping the guard alive until dropped.
114    pub fn borrow_ref<'b, T: Any>(&'b self, name: &str) -> Option<FixtureRef<'b, T>>
115    where
116        'a: 'b,
117    {
118        if let Some(val) = self.values.get(name) {
119            return val.downcast_ref::<T>().map(FixtureRef::Shared);
120        }
121        self.fixtures.get(name)?.borrow_ref::<T>()
122    }
123
124    /// Borrow a fixture mutably by name.
125    ///
126    /// # Panics
127    ///
128    /// The underlying fixtures use `RefCell` for interior mutability. Attempting
129    /// to borrow the same fixture mutably while an existing mutable guard is
130    /// alive will panic via `RefCell::borrow_mut`. Callers must drop guards
131    /// before requesting another mutable borrow of the same fixture.
132    pub fn borrow_mut<'b, T: Any>(&'b mut self, name: &str) -> Option<FixtureRefMut<'b, T>>
133    where
134        'a: 'b,
135    {
136        if let Some(val) = self.values.get_mut(name) {
137            return val.downcast_mut::<T>().map(FixtureRefMut::Override);
138        }
139        self.fixtures.get(name)?.borrow_mut::<T>()
140    }
141
142    /// Returns an iterator over the names of all available fixtures.
143    ///
144    /// This method is useful for diagnostic purposes, such as generating error
145    /// messages that list which fixtures are available when a required fixture
146    /// is missing.
147    ///
148    /// # Examples
149    ///
150    /// ```
151    /// use rstest_bdd::StepContext;
152    ///
153    /// let mut ctx = StepContext::default();
154    /// let value = 42;
155    /// ctx.insert("my_fixture", &value);
156    ///
157    /// let names: Vec<_> = ctx.available_fixtures().collect();
158    /// assert!(names.contains(&"my_fixture"));
159    /// ```
160    pub fn available_fixtures(&self) -> impl Iterator<Item = &'static str> + '_ {
161        self.fixtures.keys().copied()
162    }
163
164    /// Insert a value produced by a prior step.
165    /// The value overrides a fixture only if exactly one fixture has the same
166    /// type; otherwise it is ignored to avoid ambiguity.
167    ///
168    /// Returns the previous override for that fixture when one existed.
169    pub fn insert_value(&mut self, value: Box<dyn Any>) -> Option<Box<dyn Any>> {
170        let ty = value.as_ref().type_id();
171        let mut matches = self
172            .fixtures
173            .iter()
174            .filter_map(|(&name, entry)| (entry.type_id == ty).then_some(name));
175        let name = matches.next()?;
176        if matches.next().is_some() {
177            let message =
178                crate::localization::message_with_args("step-context-ambiguous-override", |args| {
179                    args.set("type_id", format!("{ty:?}"));
180                });
181            log::warn!("{message}");
182            #[expect(
183                clippy::print_stderr,
184                reason = "surface ambiguous overrides when logging is disabled"
185            )]
186            if !log::log_enabled!(log::Level::Warn) {
187                eprintln!("{message}");
188            }
189            return None;
190        }
191        self.values.insert(name, value)
192    }
193}
194
195impl<'a> FixtureEntry<'a> {
196    fn shared<T: Any>(value: &'a T) -> Self {
197        Self {
198            kind: FixtureKind::Shared(value),
199            type_id: TypeId::of::<T>(),
200        }
201    }
202
203    fn owned<T: Any>(cell: &'a RefCell<Box<dyn Any>>) -> Self {
204        Self {
205            kind: FixtureKind::Mutable(cell),
206            type_id: TypeId::of::<T>(),
207        }
208    }
209
210    fn borrow_ref<T: Any>(&self) -> Option<FixtureRef<'_, T>> {
211        match self.kind {
212            FixtureKind::Shared(value) => {
213                if self.type_id != TypeId::of::<T>() {
214                    return None;
215                }
216                value.downcast_ref::<T>().map(FixtureRef::Shared)
217            }
218            FixtureKind::Mutable(cell) => {
219                if self.type_id != TypeId::of::<T>() {
220                    return None;
221                }
222                let guard = cell.borrow();
223                let mapped = Ref::filter_map(guard, |b| b.downcast_ref::<T>()).ok()?;
224                Some(FixtureRef::Borrowed(mapped))
225            }
226        }
227    }
228
229    fn borrow_mut<T: Any>(&self) -> Option<FixtureRefMut<'_, T>> {
230        if self.type_id != TypeId::of::<T>() {
231            return None;
232        }
233        match self.kind {
234            FixtureKind::Shared(_) => None,
235            FixtureKind::Mutable(cell) => {
236                let guard = cell.borrow_mut();
237                let mapped = RefMut::filter_map(guard, |b| b.downcast_mut::<T>()).ok()?;
238                Some(FixtureRefMut::Borrowed(mapped))
239            }
240        }
241    }
242}
243/// Borrowed fixture reference that keeps any underlying `RefCell` borrow alive
244/// for the duration of a step.
245pub enum FixtureRef<'a, T> {
246    /// Reference bound directly to a shared fixture.
247    Shared(&'a T),
248    /// Borrow guard taken from a backing `RefCell`.
249    Borrowed(Ref<'a, T>),
250}
251
252impl<T> FixtureRef<'_, T> {
253    /// Access the borrowed value as an immutable reference.
254    #[must_use]
255    pub fn value(&self) -> &T {
256        match self {
257            Self::Shared(value) => value,
258            Self::Borrowed(guard) => guard,
259        }
260    }
261}
262
263impl<T> AsRef<T> for FixtureRef<'_, T> {
264    fn as_ref(&self) -> &T {
265        self.value()
266    }
267}
268
269/// Borrowed mutable fixture reference tied to the lifetime of the step borrow.
270pub enum FixtureRefMut<'a, T> {
271    /// Mutable reference produced by a prior step override.
272    Override(&'a mut T),
273    /// Borrow guard obtained from the underlying `RefCell`.
274    Borrowed(RefMut<'a, T>),
275}
276
277impl<T> FixtureRefMut<'_, T> {
278    /// Access the borrowed value mutably.
279    #[must_use]
280    pub fn value_mut(&mut self) -> &mut T {
281        match self {
282            Self::Override(value) => value,
283            Self::Borrowed(guard) => guard,
284        }
285    }
286}
287
288impl<T> AsMut<T> for FixtureRefMut<'_, T> {
289    fn as_mut(&mut self) -> &mut T {
290        self.value_mut()
291    }
292}
293
294#[cfg(test)]
295mod tests;