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