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}