bitbelay_suites/chi_squared/
suite.rs

1//! Builder for a [`Suite`].
2
3use std::hash::BuildHasher;
4use std::num::NonZeroUsize;
5
6use crate::chi_squared::Suite;
7
8/// The default number of buckets to use when none are provided.
9const DEFAULT_BUCKETS: usize = 256;
10
11/// An error when a required field is missing.
12#[derive(Debug)]
13pub enum MissingError {
14    /// No build hasher was provided to the [`Builder`].
15    BuildHasher,
16}
17
18impl std::fmt::Display for MissingError {
19    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20        match self {
21            MissingError::BuildHasher => write!(f, "build hasher"),
22        }
23    }
24}
25
26impl std::error::Error for MissingError {}
27
28/// An error when multiple values are provided for a singular field.
29#[derive(Debug)]
30pub enum MultipleError {
31    /// Multiple build hasher values were provided to the [`Builder`].
32    BuildHasher,
33
34    /// Multiple buckets values were provided to the [`Builder`].
35    Buckets,
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::BuildHasher => write!(f, "build hasher"),
42            MultipleError::Buckets => write!(f, "buckets"),
43        }
44    }
45}
46
47impl std::error::Error for MultipleError {}
48
49/// An error related to a [`Builder`].
50#[derive(Debug)]
51pub enum Error {
52    /// A required field was missing from the [`Builder`].
53    Missing(MissingError),
54
55    /// Multiple values were provided for a singular field in the [`Builder`].
56    Multiple(MultipleError),
57}
58
59impl std::fmt::Display for Error {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        match self {
62            Error::Missing(err) => write!(f, "missing error: {}", err),
63            Error::Multiple(err) => write!(f, "multiple error: {}", err),
64        }
65    }
66}
67
68impl std::error::Error for Error {}
69
70/// A [`Result`](std::result::Result) with an [`Error`].
71type Result<T> = std::result::Result<T, Error>;
72
73/// A builder for a [`Suite`].
74#[derive(Debug)]
75pub struct Builder<'a, H: BuildHasher> {
76    /// The hash function builder.
77    build_hasher: Option<&'a H>,
78
79    /// The number of buckets to use within each test.
80    buckets: Option<NonZeroUsize>,
81}
82
83impl<'a, H: BuildHasher> Default for Builder<'a, H> {
84    fn default() -> Self {
85        Self {
86            build_hasher: Default::default(),
87            buckets: Default::default(),
88        }
89    }
90}
91
92impl<'a, H: BuildHasher> Builder<'a, H> {
93    /// Sets the number of buckets to use for tests within this [`Builder`].
94    ///
95    /// # Examples
96    ///
97    /// ```
98    /// use std::hash::RandomState;
99    /// use std::num::NonZeroUsize;
100    ///
101    /// use bitbelay_suites::chi_squared::suite::Builder;
102    ///
103    /// let hasher = RandomState::new();
104    /// let suite = Builder::default()
105    ///     .buckets(NonZeroUsize::try_from(2048).unwrap())?
106    ///     .build_hasher(&hasher)?
107    ///     .try_build()?;
108    ///
109    /// assert_eq!(suite.buckets().get(), 2048);
110    ///
111    /// # Ok::<(), Box<dyn std::error::Error>>(())
112    /// ```
113    pub fn buckets(mut self, buckets: NonZeroUsize) -> Result<Self> {
114        if self.buckets.is_some() {
115            return Err(Error::Multiple(MultipleError::Buckets));
116        }
117
118        self.buckets = Some(buckets);
119        Ok(self)
120    }
121
122    /// Sets the [`BuildHasher`] for this [`Builder`].
123    ///
124    /// # Examples
125    ///
126    /// ```
127    /// use std::hash::BuildHasher as _;
128    /// use std::hash::RandomState;
129    ///
130    /// use bitbelay_suites::chi_squared::suite::Builder;
131    ///
132    /// let hasher = RandomState::new();
133    /// let suite = Builder::default().build_hasher(&hasher)?.try_build()?;
134    ///
135    /// // Used as a surrogate to test that the [`BuildHasher`]s are the same.
136    /// assert_eq!(suite.build_hasher().hash_one("42"), hasher.hash_one("42"));
137    ///
138    /// # Ok::<(), Box<dyn std::error::Error>>(())
139    /// ```
140    pub fn build_hasher(mut self, build_hasher: &'a H) -> Result<Self> {
141        if self.build_hasher.is_some() {
142            return Err(Error::Multiple(MultipleError::BuildHasher));
143        }
144
145        self.build_hasher = Some(build_hasher);
146        Ok(self)
147    }
148
149    /// Consumes `self` to attempt to build a [`Suite`].
150    ///
151    /// # Examples
152    ///
153    /// ```
154    /// use std::hash::BuildHasher as _;
155    /// use std::hash::RandomState;
156    /// use std::num::NonZeroUsize;
157    ///
158    /// use bitbelay_suites::chi_squared::suite::Builder;
159    ///
160    /// let hasher = RandomState::new();
161    /// let suite = Builder::default()
162    ///     .buckets(NonZeroUsize::try_from(2048).unwrap())?
163    ///     .build_hasher(&hasher)?
164    ///     .try_build()?;
165    ///
166    /// assert_eq!(suite.buckets().get(), 2048);
167    /// // Used as a surrogate to test that the [`BuildHasher`]s are the same.
168    /// assert_eq!(suite.build_hasher().hash_one("42"), hasher.hash_one("42"));
169    ///
170    /// # Ok::<(), Box<dyn std::error::Error>>(())
171    /// ```
172    pub fn try_build(self) -> Result<Suite<'a, H>> {
173        let build_hasher = self
174            .build_hasher
175            .ok_or(Error::Missing(MissingError::BuildHasher))?;
176
177        let buckets = self
178            .buckets
179            // SAFETY: [`DEFAULT_BUCKETS`] is manually crafted to be a non-zero usize.
180            .unwrap_or(NonZeroUsize::try_from(DEFAULT_BUCKETS).unwrap());
181
182        Ok(Suite {
183            build_hasher,
184            tests: Vec::new(),
185            buckets,
186        })
187    }
188}