nullable_utils/
lib.rs

1// SPDX-FileCopyrightText: 2024 Markus Haug (Korrat)
2//
3// SPDX-License-Identifier: Apache-2.0
4// SPDX-License-Identifier: MIT
5
6//! This crate provides helpers for working with Nullables as described by James Shore in [Testing without Mocks].
7//!
8//! [Testing without Mocks]: https://www.jamesshore.com/v2/projects/nullables/testing-without-mocks
9
10extern crate alloc;
11
12pub use nullable_utils_macros::*;
13
14pub use crate::output::Listener as OutputListener;
15pub use crate::output::Tracker as OutputTracker;
16
17mod output;
18
19/// `NulledResponses` helps with implementing [Configurable Responses] for Nullable types.
20///
21/// [Configurable Responses]: https://www.jamesshore.com/v2/projects/nullables/testing-without-mocks#configurable-responses
22#[derive(Clone, Debug)]
23// TODO exchange this for an implementation using boxed iterators?
24pub enum NulledResponses<T> {
25    /// Repeat a single response forever.
26    Repeating(T),
27    /// Return a sequence of responses, panicking when the sequence runs out.
28    Sequence(alloc::vec::Vec<T>),
29}
30
31impl<T> NulledResponses<T> {
32    /// Backmap can be used to convert between `NulledResponses` for different types.
33    ///
34    /// For example, a high-level infrastructure wrapper can use this to convert its own response configuration into a
35    /// response configuration for an embedded lower-level infrastructure wrapper.
36    pub fn backmap<U>(self, mut mapper: impl FnMut(T) -> U) -> NulledResponses<U> {
37        match self {
38            Self::Repeating(response) => NulledResponses::Repeating(mapper(response)),
39            Self::Sequence(responses) => {
40                NulledResponses::Sequence(responses.into_iter().map(mapper).collect())
41            }
42        }
43    }
44}
45
46impl<T> NulledResponses<T>
47where
48    T: Clone,
49{
50    /// Gets the next response.
51    ///
52    /// # Panics
53    ///
54    /// Panics if this object was configured with a finite sequence of responses and that sequence has been exhausted.
55    pub fn get(&mut self) -> T {
56        match self {
57            Self::Repeating(response) => response.clone(),
58            Self::Sequence(responses) => responses.pop().expect("not enough responses configured"),
59        }
60    }
61}
62
63impl<T, const N: usize> From<[T; N]> for NulledResponses<T> {
64    fn from(value: [T; N]) -> Self {
65        Self::from(Vec::from(value))
66    }
67}
68
69impl<T> From<&[T]> for NulledResponses<T>
70where
71    T: Clone,
72{
73    fn from(value: &[T]) -> Self {
74        Self::from(Vec::from(value))
75    }
76}
77
78impl<T> From<T> for NulledResponses<T>
79where
80    T: Clone,
81{
82    fn from(value: T) -> Self {
83        Self::Repeating(value)
84    }
85}
86
87impl<T> From<Vec<T>> for NulledResponses<T> {
88    fn from(mut value: Vec<T>) -> Self {
89        value.reverse();
90        Self::Sequence(value)
91    }
92}