1use crate::{
4 data::{DataError, Fingerprint, Validate, ValidationError},
5 emit_error,
6};
7use core::fmt;
8use serde::{
9 Deserialize, Deserializer, Serialize,
10 de::{self, MapAccess, Visitor},
11};
12use serde_with::skip_serializing_none;
13use std::{hash::Hasher, ops::RangeInclusive};
14
15const VALID_SCALE: RangeInclusive<f32> = -1.0..=1.0;
16
17#[skip_serializing_none]
23#[derive(Clone, Debug, PartialEq, Serialize)]
24#[serde(deny_unknown_fields)]
25pub struct Score {
26 scaled: Option<f32>,
27 raw: Option<f32>,
28 min: Option<f32>,
29 max: Option<f32>,
30}
31
32impl<'de> Deserialize<'de> for Score {
41 fn deserialize<D>(des: D) -> Result<Self, D::Error>
42 where
43 D: Deserializer<'de>,
44 {
45 const FIELDS: &[&str] = &["scaled", "raw", "min", "max"];
46
47 #[derive(Deserialize)]
48 #[serde(field_identifier, rename_all = "lowercase")]
49 enum Field {
50 Scaled,
51 Raw,
52 Min,
53 Max,
54 }
55
56 struct ScoreVisitor;
57
58 impl<'de> Visitor<'de> for ScoreVisitor {
59 type Value = Score;
60
61 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
62 formatter.write_str("Score")
63 }
64
65 fn visit_map<V>(self, mut map: V) -> Result<Score, V::Error>
66 where
67 V: MapAccess<'de>,
68 {
69 let mut scaled = None;
70 let mut raw = None;
71 let mut min = None;
72 let mut max = None;
73 while let Some(key) = map.next_key()? {
74 match key {
75 Field::Scaled => {
76 if scaled.is_some() {
77 return Err(de::Error::duplicate_field("scaled"));
78 }
79 let value: f32 = map.next_value()?;
81 if !VALID_SCALE.contains(&value) {
82 return Err(de::Error::custom("scaled is out-of-bounds"));
83 }
84 scaled = Some(value);
85 }
86 Field::Raw => {
87 if raw.is_some() {
88 return Err(de::Error::duplicate_field("raw"));
89 }
90 let value: f32 = map.next_value()?;
91 raw = Some(value);
92 }
93 Field::Min => {
94 if min.is_some() {
95 return Err(de::Error::duplicate_field("min"));
96 }
97 let value: f32 = map.next_value()?;
98 min = Some(value);
99 }
100 Field::Max => {
101 if max.is_some() {
102 return Err(de::Error::duplicate_field("max"));
103 }
104 let value: f32 = map.next_value()?;
105 max = Some(value);
106 }
107 }
108 }
109 if scaled.is_none() && raw.is_none() && min.is_none() && max.is_none() {
111 return Err(de::Error::missing_field("scaled | raw | min | max"));
112 }
113 let lower = min.unwrap_or(f32::MIN);
114 let upper = max.unwrap_or(f32::MAX);
115 if upper < lower {
116 return Err(de::Error::custom("max < min"));
117 }
118 if raw.is_some() && !(lower..upper).contains(raw.as_ref().unwrap()) {
119 return Err(de::Error::custom("raw is out-of-bounds"));
120 }
121 Ok(Score {
122 scaled,
123 raw,
124 min,
125 max,
126 })
127 }
128 }
129
130 des.deserialize_struct("Score", FIELDS, ScoreVisitor)
131 }
132}
133
134impl Score {
135 pub fn builder() -> ScoreBuilder {
137 ScoreBuilder::default()
138 }
139
140 pub fn scaled(&self) -> Option<f32> {
145 self.scaled
146 }
147
148 pub fn raw(&self) -> Option<f32> {
155 self.raw
156 }
157
158 pub fn min(&self) -> Option<f32> {
161 self.min
162 }
163
164 pub fn max(&self) -> Option<f32> {
167 self.max
168 }
169}
170
171impl Fingerprint for Score {
172 fn fingerprint<H: Hasher>(&self, state: &mut H) {
173 if self.scaled.is_some() {
174 state.write(&self.scaled().unwrap().to_le_bytes())
175 }
176 if self.raw.is_some() {
177 state.write(&self.raw().unwrap().to_le_bytes())
178 }
179 if self.min.is_some() {
180 state.write(&self.min().unwrap().to_le_bytes())
181 }
182 if self.max.is_some() {
183 state.write(&self.max().unwrap().to_le_bytes())
184 }
185 }
186}
187
188impl fmt::Display for Score {
189 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
190 let mut vec = vec![];
191
192 if self.scaled.is_some() {
193 vec.push(format!("scaled: {}", self.scaled.as_ref().unwrap()))
194 }
195 if self.raw.is_some() {
196 vec.push(format!("raw: {}", self.raw.as_ref().unwrap()))
197 }
198 if self.min.is_some() {
199 vec.push(format!("min: {}", self.min.as_ref().unwrap()))
200 }
201 if self.max.is_some() {
202 vec.push(format!("max: {}", self.max.as_ref().unwrap()))
203 }
204
205 let res = vec
206 .iter()
207 .map(|x| x.to_string())
208 .collect::<Vec<_>>()
209 .join(", ");
210 write!(f, "Score{{ {res} }}")
211 }
212}
213
214impl Validate for Score {
215 fn validate(&self) -> Vec<ValidationError> {
220 vec![]
221 }
222}
223
224#[derive(Debug, Default)]
226pub struct ScoreBuilder {
227 _scaled: Option<f32>,
228 _raw: Option<f32>,
229 _min: Option<f32>,
230 _max: Option<f32>,
231}
232
233impl ScoreBuilder {
234 pub fn scaled(mut self, val: f32) -> Result<Self, DataError> {
236 if !VALID_SCALE.contains(&val) {
237 emit_error!(DataError::Validation(ValidationError::ConstraintViolation(
238 format!("'scaled' ({val}) is out-of-bounds").into()
239 )))
240 } else {
241 self._scaled = Some(val);
242 Ok(self)
243 }
244 }
245
246 pub fn raw(mut self, val: f32) -> Self {
248 self._raw = Some(val);
249 self
250 }
251
252 pub fn min(mut self, val: f32) -> Self {
254 self._min = Some(val);
255 self
256 }
257
258 pub fn max(mut self, val: f32) -> Self {
260 self._max = Some(val);
261 self
262 }
263
264 pub fn build(self) -> Result<Score, DataError> {
268 if self._scaled.is_none()
269 && self._raw.is_none()
270 && self._min.is_none()
271 && self._max.is_none()
272 {
273 emit_error!(DataError::Validation(ValidationError::ConstraintViolation(
274 "At least one field must be set".into()
275 )))
276 }
277 let min = self._min.unwrap_or(f32::MIN);
279 let max = self._max.unwrap_or(f32::MAX);
280 if max < min {
281 emit_error!(DataError::Validation(ValidationError::ConstraintViolation(
282 "'min', 'max', or both are set but 'max' is less than 'min'".into()
283 )))
284 } else if self._raw.is_some() && !(min..max).contains(self._raw.as_ref().unwrap()) {
285 emit_error!(DataError::Validation(ValidationError::ConstraintViolation(
286 "'raw' is out-of-bounds".into()
287 )))
288 }
289 Ok(Score {
290 scaled: self._scaled,
291 raw: self._raw,
292 min: self._min,
293 max: self._max,
294 })
295 }
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301
302 #[test]
303 fn test_1field_min() {
304 const SCORE: &str = r#"{ }"#;
305
306 let res = serde_json::from_str::<Score>(SCORE);
307 assert!(res.is_err());
308 let msg = res.err().unwrap().to_string();
309 assert!(msg.contains("missing field"));
310 }
311
312 #[test]
313 fn test_dup_field() {
314 const SCORE: &str = r#"{ "scaled": 0.9, "raw": 5, "min": 1, "scaled": 0.2, "max": 10 }"#;
315
316 let res = serde_json::from_str::<Score>(SCORE);
317 assert!(res.is_err());
318 let msg = res.err().unwrap().to_string();
319 assert!(msg.contains("duplicate field"));
320 }
321
322 #[test]
323 fn test_all_good() {
324 const SCORE: &str = r#"{ "scaled": 0.95, "raw": 42, "min": 10.0, "max": 100.0 }"#;
325
326 let res = serde_json::from_str::<Score>(SCORE);
327 assert!(res.is_ok());
328 let score = res.unwrap();
329 assert_eq!(score.scaled.unwrap(), 0.95);
330 assert_eq!(score.raw.unwrap(), 42.0);
331 assert_eq!(score.min.unwrap(), 10.0);
332 assert_eq!(score.max.unwrap(), 100.0);
333 }
334
335 #[test]
336 fn test_scaled_oob() {
337 const SCORE: &str = r#"{ "scaled": 1.1, "raw": 42 }"#;
338
339 let res = serde_json::from_str::<Score>(SCORE);
340 assert!(res.is_err());
341 let msg = res.err().unwrap().to_string();
342 assert!(msg.contains("scaled is out-of-bounds"));
343 }
344
345 #[test]
346 fn test_limits_bad() {
347 const SCORE: &str = r#"{ "scaled": 0.95, "raw": 42, "min": 50.0, "max": 10.0 }"#;
348
349 let res = serde_json::from_str::<Score>(SCORE);
350 assert!(res.is_err());
351 let msg = res.err().unwrap().to_string();
352 assert!(msg.contains("max < min"));
353 }
354
355 #[test]
356 fn test_raw_oob() {
357 const SCORE: &str = r#"{ "scaled": 0.95, "raw": 12.5, "min": 0.0, "max": 10.0 }"#;
358
359 let res = serde_json::from_str::<Score>(SCORE);
360 assert!(res.is_err());
361 let msg = res.err().unwrap().to_string();
362 assert!(msg.contains("raw is out-of-bounds"));
363 }
364
365 #[test]
366 fn test_builder() -> Result<(), DataError> {
367 let r = Score::builder().build();
369 assert!(r.is_err());
370
371 let r = Score::builder().scaled(1.1);
373 assert!(r.is_err());
374
375 let r = Score::builder().scaled(0.8)?.min(10.0).max(0.0).build();
377 assert!(r.is_err());
378
379 let r = Score::builder()
381 .scaled(0.8)?
382 .raw(11.0)
383 .min(0.0)
384 .max(10.0)
385 .build();
386 assert!(r.is_err());
387
388 let score = Score::builder()
390 .scaled(0.8)?
391 .raw(5.0)
392 .min(0.0)
393 .max(10.0)
394 .build()?;
395 assert_eq!(score.scaled.unwrap(), 0.8);
396 assert_eq!(score.raw.unwrap(), 5.0);
397 assert_eq!(score.min.unwrap(), 0.0);
398 assert_eq!(score.max.unwrap(), 10.0);
399
400 Ok(())
401 }
402}