bitbelay_report/
builder.rs

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