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