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