Line data Source code
1 : //! A serde::Deserialize type for percentages.
2 : //!
3 : //! See [`Percent`] for details.
4 :
5 : use serde::{Deserialize, Serialize};
6 :
7 : /// If the value is not an integer between 0 and 100,
8 : /// deserialization fails with a descriptive error.
9 2 : #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
10 : #[serde(transparent)]
11 : pub struct Percent(#[serde(deserialize_with = "deserialize_pct_0_to_100")] u8);
12 :
13 : impl Percent {
14 34 : pub const fn new(pct: u8) -> Option<Self> {
15 34 : if pct <= 100 {
16 34 : Some(Percent(pct))
17 : } else {
18 0 : None
19 : }
20 34 : }
21 :
22 85 : pub fn get(&self) -> u8 {
23 85 : self.0
24 85 : }
25 : }
26 :
27 7 : fn deserialize_pct_0_to_100<'de, D>(deserializer: D) -> Result<u8, D::Error>
28 7 : where
29 7 : D: serde::de::Deserializer<'de>,
30 7 : {
31 7 : let v: u8 = serde::de::Deserialize::deserialize(deserializer)?;
32 3 : if v > 100 {
33 1 : return Err(serde::de::Error::custom(
34 1 : "must be an integer between 0 and 100",
35 1 : ));
36 2 : }
37 2 : Ok(v)
38 7 : }
39 :
40 : #[cfg(test)]
41 : mod tests {
42 : use super::Percent;
43 :
44 19 : #[derive(serde::Deserialize, serde::Serialize, Debug, PartialEq, Eq)]
45 : struct Foo {
46 : bar: Percent,
47 : }
48 :
49 : #[test]
50 1 : fn basics() {
51 1 : let input = r#"{ "bar": 50 }"#;
52 1 : let foo: Foo = serde_json::from_str(input).unwrap();
53 1 : assert_eq!(foo.bar.get(), 50);
54 1 : }
55 : #[test]
56 1 : fn null_handling() {
57 1 : let input = r#"{ "bar": null }"#;
58 1 : let res: Result<Foo, _> = serde_json::from_str(input);
59 1 : assert!(res.is_err());
60 1 : }
61 : #[test]
62 1 : fn zero() {
63 1 : let input = r#"{ "bar": 0 }"#;
64 1 : let foo: Foo = serde_json::from_str(input).unwrap();
65 1 : assert_eq!(foo.bar.get(), 0);
66 1 : }
67 : #[test]
68 1 : fn out_of_range_above() {
69 1 : let input = r#"{ "bar": 101 }"#;
70 1 : let res: Result<Foo, _> = serde_json::from_str(input);
71 1 : assert!(res.is_err());
72 1 : }
73 : #[test]
74 1 : fn out_of_range_below() {
75 1 : let input = r#"{ "bar": -1 }"#;
76 1 : let res: Result<Foo, _> = serde_json::from_str(input);
77 1 : assert!(res.is_err());
78 1 : }
79 : #[test]
80 1 : fn float() {
81 1 : let input = r#"{ "bar": 50.5 }"#;
82 1 : let res: Result<Foo, _> = serde_json::from_str(input);
83 1 : assert!(res.is_err());
84 1 : }
85 : #[test]
86 1 : fn string() {
87 1 : let input = r#"{ "bar": "50 %" }"#;
88 1 : let res: Result<Foo, _> = serde_json::from_str(input);
89 1 : assert!(res.is_err());
90 1 : }
91 : }
|