Skip to main content

bitbelay_report/section/test/
builder.rs

1//! A builder for a [`Test`];
2
3use nonempty::NonEmpty;
4
5use crate::section::test::Module;
6use crate::section::Test;
7
8/// An error when a required field is missing.
9#[derive(Debug)]
10pub enum MissingError {
11    /// No title was provided to the [`Builder`].
12    Title,
13
14    /// No description was provided to the [`Builder`].
15    Description,
16
17    /// No modules were provided to the [`Builder`].
18    Modules,
19}
20
21impl std::fmt::Display for MissingError {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        match self {
24            MissingError::Title => write!(f, "title"),
25            MissingError::Description => write!(f, "description"),
26            MissingError::Modules => write!(f, "modules"),
27        }
28    }
29}
30
31impl std::error::Error for MissingError {}
32
33/// An error when multiple values are provided for a singular field.
34#[derive(Debug)]
35pub enum MultipleError {
36    /// Multiple titles were provided to the [`Builder`].
37    Title,
38
39    /// Multiple descriptions were provided to the [`Builder`].
40    Description,
41}
42
43impl std::fmt::Display for MultipleError {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        match self {
46            MultipleError::Title => write!(f, "title"),
47            MultipleError::Description => write!(f, "description"),
48        }
49    }
50}
51
52impl std::error::Error for MultipleError {}
53
54/// An error related to a [`Builder`].
55#[derive(Debug)]
56pub enum Error {
57    /// A required field was missing from the [`Builder`].
58    Missing(MissingError),
59
60    /// Multiple values were provided for a singular field in the [`Builder`].
61    Multiple(MultipleError),
62}
63
64impl std::fmt::Display for Error {
65    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66        match self {
67            Error::Missing(err) => write!(f, "missing error: {}", err),
68            Error::Multiple(err) => write!(f, "multiple error: {}", err),
69        }
70    }
71}
72
73impl std::error::Error for Error {}
74
75/// A [`Result`](std::result::Result) with an [`Error`].
76type Result<T> = std::result::Result<T, Error>;
77
78/// A builder for a [`Test`].
79#[derive(Debug, Default)]
80pub struct Builder {
81    /// The title.
82    title: Option<String>,
83
84    /// The descriptions.
85    description: Option<String>,
86
87    /// The modules of the test.
88    modules: Option<NonEmpty<Module>>,
89}
90
91impl Builder {
92    /// Sets the title for the [`Builder`].
93    ///
94    /// # Examples
95    ///
96    /// ```
97    /// use bitbelay_report::section::test;
98    /// use bitbelay_report::section::test::module::Result;
99    /// use bitbelay_report::section::test::Module;
100    /// use bitbelay_report::Builder;
101    ///
102    /// let result = test::Builder::default()
103    ///     .title("Foo")?
104    ///     .description("Bar")?
105    ///     .push_module(Module::new(Result::Inconclusive, "Baz", None, None))
106    ///     .try_build()?;
107    ///
108    /// assert_eq!(result.title(), "Foo");
109    ///
110    /// # Ok::<(), Box<dyn std::error::Error>>(())
111    /// ```
112    pub fn title(mut self, title: impl Into<String>) -> Result<Self> {
113        let title = title.into();
114
115        if self.title.is_some() {
116            return Err(Error::Multiple(MultipleError::Title));
117        }
118
119        self.title = Some(title);
120        Ok(self)
121    }
122
123    /// Sets the description for the [`Builder`].
124    ///
125    /// # Examples
126    ///
127    /// ```
128    /// use bitbelay_report::section::test;
129    /// use bitbelay_report::section::test::module::Result;
130    /// use bitbelay_report::section::test::Module;
131    /// use bitbelay_report::Builder;
132    ///
133    /// let result = test::Builder::default()
134    ///     .title("Foo")?
135    ///     .description("Bar")?
136    ///     .push_module(Module::new(Result::Inconclusive, "Baz", None, None))
137    ///     .try_build()?;
138    ///
139    /// assert_eq!(result.description(), "Bar");
140    ///
141    /// # Ok::<(), Box<dyn std::error::Error>>(())
142    /// ```
143    pub fn description(mut self, description: impl Into<String>) -> Result<Self> {
144        let description = description.into();
145
146        if self.description.is_some() {
147            return Err(Error::Multiple(MultipleError::Description));
148        }
149
150        self.description = Some(description);
151        Ok(self)
152    }
153
154    /// Pushes a [`Module`] into the [`Builder`].
155    ///
156    /// # Examples
157    ///
158    /// ```
159    /// use bitbelay_report::section::test;
160    /// use bitbelay_report::section::test::module::Result;
161    /// use bitbelay_report::section::test::Module;
162    /// use bitbelay_report::Builder;
163    ///
164    /// let module = Module::new(Result::Inconclusive, "Baz", None, None);
165    /// let result = test::Builder::default()
166    ///     .title("Foo")?
167    ///     .description("Bar")?
168    ///     .push_module(module.clone())
169    ///     .try_build()?;
170    ///
171    /// assert_eq!(result.modules().len(), 1);
172    /// assert_eq!(result.modules().first(), &module);
173    ///
174    /// # Ok::<(), Box<dyn std::error::Error>>(())
175    /// ```
176    pub fn push_module(mut self, module: Module) -> Self {
177        let modules = match self.modules {
178            Some(mut modules) => {
179                modules.push(module);
180                modules
181            }
182            None => NonEmpty::new(module),
183        };
184
185        self.modules = Some(modules);
186        self
187    }
188
189    /// Consumes `self` and attempts to build a [`Test`].
190    ///
191    /// # Examples
192    ///
193    /// ```
194    /// use bitbelay_report::section::test;
195    /// use bitbelay_report::section::test::module::Result;
196    /// use bitbelay_report::section::test::Module;
197    /// use bitbelay_report::Builder;
198    ///
199    /// let module = Module::new(Result::Inconclusive, "Baz", None, None);
200    /// let result = test::Builder::default()
201    ///     .title("Foo")?
202    ///     .description("Bar")?
203    ///     .push_module(module.clone())
204    ///     .try_build()?;
205    ///
206    /// assert_eq!(result.title(), "Foo");
207    /// assert_eq!(result.description(), "Bar");
208    /// assert_eq!(result.modules().len(), 1);
209    /// assert_eq!(result.modules().first(), &module);
210    ///
211    /// # Ok::<(), Box<dyn std::error::Error>>(())
212    /// ```
213    pub fn try_build(self) -> Result<Test> {
214        let title = self.title.ok_or(Error::Missing(MissingError::Title))?;
215
216        let description = self
217            .description
218            .ok_or(Error::Missing(MissingError::Description))?;
219
220        let modules = self.modules.ok_or(Error::Missing(MissingError::Modules))?;
221
222        Ok(Test {
223            title,
224            description,
225            modules,
226        })
227    }
228}