1#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::expect_used))]
2use serde::{Deserialize, Serialize};
13use std::fmt;
14use std::str::FromStr;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
47pub enum Dialect {
48 #[default]
52 Normative,
53
54 ZeroTolerant,
58
59 OneTolerant,
64}
65
66impl FromStr for Dialect {
67 type Err = String;
68
69 #[inline]
70 fn from_str(s: &str) -> Result<Self, Self::Err> {
71 match s.trim() {
72 "n" | "N" => Ok(Self::Normative),
73 "0" => Ok(Self::ZeroTolerant),
74 "1" => Ok(Self::OneTolerant),
75 _ => Err(format!(
76 "Invalid dialect '{s}'. Valid values are: 'n' (normative), '0' (zero-tolerant), '1' (one-tolerant)"
77 )),
78 }
79 }
80}
81
82impl fmt::Display for Dialect {
83 #[inline]
84 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85 match self {
86 Self::Normative => write!(f, "n"),
87 Self::ZeroTolerant => write!(f, "0"),
88 Self::OneTolerant => write!(f, "1"),
89 }
90 }
91}
92
93#[inline]
134#[must_use]
135pub fn effective_min_count(dialect: Dialect, declared_min_count: u32) -> u32 {
136 match dialect {
137 Dialect::Normative => declared_min_count,
138 Dialect::ZeroTolerant => 0,
139 Dialect::OneTolerant => declared_min_count.max(1),
140 }
141}
142
143#[cfg(test)]
144#[allow(clippy::unwrap_used)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_dialect_default() {
150 assert_eq!(Dialect::default(), Dialect::Normative);
151 }
152
153 #[test]
154 fn test_dialect_from_str() {
155 assert_eq!(Dialect::from_str("n").unwrap(), Dialect::Normative);
157 assert_eq!(Dialect::from_str("N").unwrap(), Dialect::Normative);
158
159 assert_eq!(Dialect::from_str("0").unwrap(), Dialect::ZeroTolerant);
161
162 assert_eq!(Dialect::from_str("1").unwrap(), Dialect::OneTolerant);
164
165 assert!(Dialect::from_str("x").is_err());
167 assert!(Dialect::from_str("normative").is_err());
168 assert!(Dialect::from_str("").is_err());
169 }
170
171 #[test]
172 fn test_dialect_display() {
173 assert_eq!(Dialect::Normative.to_string(), "n");
174 assert_eq!(Dialect::ZeroTolerant.to_string(), "0");
175 assert_eq!(Dialect::OneTolerant.to_string(), "1");
176 }
177
178 #[test]
179 fn test_effective_min_count_normative() {
180 assert_eq!(effective_min_count(Dialect::Normative, 0), 0);
182 assert_eq!(effective_min_count(Dialect::Normative, 1), 1);
183 assert_eq!(effective_min_count(Dialect::Normative, 5), 5);
184 assert_eq!(effective_min_count(Dialect::Normative, 100), 100);
185 }
186
187 #[test]
188 fn test_effective_min_count_zero_tolerant() {
189 assert_eq!(effective_min_count(Dialect::ZeroTolerant, 0), 0);
191 assert_eq!(effective_min_count(Dialect::ZeroTolerant, 1), 0);
192 assert_eq!(effective_min_count(Dialect::ZeroTolerant, 5), 0);
193 assert_eq!(effective_min_count(Dialect::ZeroTolerant, 100), 0);
194 }
195
196 #[test]
197 fn test_effective_min_count_one_tolerant() {
198 assert_eq!(effective_min_count(Dialect::OneTolerant, 0), 1);
200 assert_eq!(effective_min_count(Dialect::OneTolerant, 1), 1);
201 assert_eq!(effective_min_count(Dialect::OneTolerant, 5), 5);
202 assert_eq!(effective_min_count(Dialect::OneTolerant, 100), 100);
203 }
204
205 #[test]
206 fn test_dialect_roundtrip() {
207 let dialects = [
209 Dialect::Normative,
210 Dialect::ZeroTolerant,
211 Dialect::OneTolerant,
212 ];
213 for dialect in dialects {
214 let s = dialect.to_string();
215 let parsed = Dialect::from_str(&s).unwrap();
216 assert_eq!(parsed, dialect);
217 }
218 }
219
220 #[test]
223 fn test_dialect_debug_format() {
224 assert!(format!("{:?}", Dialect::Normative).contains("Normative"));
225 assert!(format!("{:?}", Dialect::ZeroTolerant).contains("ZeroTolerant"));
226 assert!(format!("{:?}", Dialect::OneTolerant).contains("OneTolerant"));
227 }
228
229 #[test]
230 fn test_dialect_clone_preserves_value() {
231 let d = Dialect::ZeroTolerant;
232 let cloned = d;
233 assert_eq!(d, cloned);
234 }
235
236 #[test]
237 fn test_dialect_eq_different_variants() {
238 assert_ne!(Dialect::Normative, Dialect::ZeroTolerant);
239 assert_ne!(Dialect::ZeroTolerant, Dialect::OneTolerant);
240 assert_ne!(Dialect::OneTolerant, Dialect::Normative);
241 }
242
243 #[test]
244 fn test_dialect_hash_consistency() {
245 use std::collections::HashSet;
246 let mut set = HashSet::new();
247 set.insert(Dialect::Normative);
248 set.insert(Dialect::ZeroTolerant);
249 set.insert(Dialect::OneTolerant);
250 assert_eq!(set.len(), 3);
251 set.insert(Dialect::Normative);
253 assert_eq!(set.len(), 3);
254 }
255
256 #[test]
257 fn test_dialect_serde_roundtrip_all_variants() {
258 let variants = [
259 Dialect::Normative,
260 Dialect::ZeroTolerant,
261 Dialect::OneTolerant,
262 ];
263 for dialect in variants {
264 let json = serde_json::to_string(&dialect).unwrap();
265 let deserialized: Dialect = serde_json::from_str(&json).unwrap();
266 assert_eq!(
267 dialect, deserialized,
268 "Serde roundtrip failed for {dialect}"
269 );
270 }
271 }
272
273 #[test]
274 fn test_dialect_from_str_with_whitespace() {
275 assert_eq!(Dialect::from_str(" n ").unwrap(), Dialect::Normative);
277 assert_eq!(Dialect::from_str(" 0 ").unwrap(), Dialect::ZeroTolerant);
278 assert_eq!(Dialect::from_str(" 1 ").unwrap(), Dialect::OneTolerant);
279 }
280
281 #[test]
282 fn test_dialect_from_str_error_message_content() {
283 let err = Dialect::from_str("invalid").unwrap_err();
284 assert!(err.contains("Invalid dialect"));
285 assert!(err.contains("'invalid'"));
286 assert!(err.contains("normative"));
287 assert!(err.contains("zero-tolerant"));
288 assert!(err.contains("one-tolerant"));
289 }
290
291 #[test]
292 fn test_dialect_from_str_numeric_invalid() {
293 assert!(Dialect::from_str("2").is_err());
294 assert!(Dialect::from_str("3").is_err());
295 assert!(Dialect::from_str("-1").is_err());
296 }
297
298 #[test]
299 fn test_effective_min_count_normative_u32_max() {
300 assert_eq!(effective_min_count(Dialect::Normative, u32::MAX), u32::MAX);
301 }
302
303 #[test]
304 fn test_effective_min_count_zero_tolerant_u32_max() {
305 assert_eq!(effective_min_count(Dialect::ZeroTolerant, u32::MAX), 0);
306 }
307
308 #[test]
309 fn test_effective_min_count_one_tolerant_u32_max() {
310 assert_eq!(
311 effective_min_count(Dialect::OneTolerant, u32::MAX),
312 u32::MAX
313 );
314 }
315
316 #[test]
317 fn test_effective_min_count_one_tolerant_zero_clamps_to_one() {
318 assert_eq!(effective_min_count(Dialect::OneTolerant, 0), 1);
320 }
321
322 #[test]
323 fn test_effective_min_count_normative_zero_stays_zero() {
324 assert_eq!(effective_min_count(Dialect::Normative, 0), 0);
326 }
327
328 #[test]
329 fn test_effective_min_count_zero_tolerant_one_becomes_zero() {
330 assert_eq!(effective_min_count(Dialect::ZeroTolerant, 1), 0);
332 }
333}