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}