apt_edsp/
bool.rs

1use std::fmt::{Display, Formatter};
2use std::str::FromStr;
3
4use serde::{Deserialize, Serialize};
5
6use super::util::TryFromStringVisitor;
7
8/// A [`bool`] wrapper type that serializes `true` and `false` to `"yes"` and `"no"` respectively.
9/// [`Bool`] is generic over its default value `D`.
10///
11/// See [`Bool::serialize`] and [`Bool::deserialize`] for how the default value `D` is used to
12/// serialize and deserialize empty/`None` values.
13///
14/// # Examples
15/// ```
16/// # use apt_edsp::Bool;
17/// assert_eq!("yes", Bool::<false>::YES.as_str());
18/// assert_eq!(Bool::<false>::NO, "no".parse().unwrap());
19/// assert_eq!(Bool(true), Bool::<true>::default());
20/// ```
21#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
22pub struct Bool<const D: bool = false>(pub bool);
23
24impl<const D: bool> Bool<D> {
25    /// Equivalent to `true` or `"yes"`.
26    pub const YES: Self = Bool(true);
27
28    /// Equivalent to `false` or `"no`".
29    pub const NO: Self = Bool(false);
30
31    /// Returns the [`Bool`] corresponding to `"yes"`.
32    pub const fn yes() -> Self {
33        Self::YES
34    }
35
36    /// Returns the [`Bool`] corresponding to `"no"`.
37    pub const fn no() -> Self {
38        Self::NO
39    }
40
41    /// Returns the string literal representation (`"yes"` or `"no"`) for this boolean value.
42    pub const fn as_str(&self) -> &'static str {
43        if self.0 {
44            "yes"
45        } else {
46            "no"
47        }
48    }
49}
50
51impl<const D: bool> Default for Bool<D> {
52    /// Uses the const generic parameter `D` as the default value and returns `Bool(D)`.
53    fn default() -> Self {
54        Self(D)
55    }
56}
57
58impl<const D: bool> From<bool> for Bool<D> {
59    fn from(value: bool) -> Self {
60        Self(value)
61    }
62}
63
64impl<const D: bool> From<Bool<D>> for bool {
65    fn from(value: Bool<D>) -> Self {
66        value.0
67    }
68}
69
70impl<const D: bool> From<Bool<D>> for &'static str {
71    fn from(value: Bool<D>) -> Self {
72        value.as_str()
73    }
74}
75
76impl<const D: bool> FromStr for Bool<D> {
77    type Err = &'static str;
78
79    /// Returns `Bool(true)` if `"yes"`, `Bool(false)` if `"no"`, otherwise returns [`Err`].
80    fn from_str(s: &str) -> Result<Self, Self::Err> {
81        match s {
82            "yes" => Ok(Self::YES),
83            "no" => Ok(Self::NO),
84            _ => Err("expected \"yes\" or \"no\""),
85        }
86    }
87}
88
89impl<const D: bool> TryFrom<&str> for Bool<D> {
90    type Error = <Self as FromStr>::Err;
91
92    fn try_from(value: &str) -> Result<Self, Self::Error> {
93        value.parse()
94    }
95}
96
97impl<const D: bool> TryFrom<String> for Bool<D> {
98    type Error = <Self as FromStr>::Err;
99
100    fn try_from(value: String) -> Result<Self, Self::Error> {
101        value.parse()
102    }
103}
104
105impl<const D: bool> Display for Bool<D> {
106    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
107        write!(f, "{}", self.as_str())
108    }
109}
110
111impl<const D: bool> Serialize for Bool<D> {
112    /// Serializes [`Bool::<D>::default()`] to `None`, otherwise serializes the
113    /// [string representation](Self::as_str).
114    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
115        if self.0 != D {
116            serializer.collect_str(self)
117        } else {
118            serializer.serialize_none()
119        }
120    }
121}
122
123impl<'de, const DEFAULT: bool> Deserialize<'de> for Bool<DEFAULT> {
124    /// Deserializes an empty value to [`Bool::<DEFAULT>::default()`], otherwise deserializes
125    /// [from the string representation](Self::from_str).
126    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
127        struct Visitor<const DEFAULT: bool>;
128
129        impl<'de, const DEFAULT: bool> serde::de::Visitor<'de> for Visitor<DEFAULT> {
130            type Value = Bool<DEFAULT>;
131
132            fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
133                formatter.write_str("yes or no, or nothing")
134            }
135
136            #[inline]
137            fn visit_none<E: serde::de::Error>(self) -> Result<Self::Value, E> {
138                Ok(Bool::default())
139            }
140
141            #[inline]
142            fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
143            where
144                D: serde::Deserializer<'de>,
145            {
146                deserializer.deserialize_str(TryFromStringVisitor::new())
147            }
148
149            #[inline]
150            fn visit_unit<E: serde::de::Error>(self) -> Result<Self::Value, E> {
151                Ok(Bool::default())
152            }
153        }
154
155        deserializer.deserialize_option(Visitor::<DEFAULT>)
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use indoc::indoc;
162
163    use crate::test_util::serde_test;
164
165    use super::*;
166
167    type BoolDefaultFalse = Bool<false>;
168
169    #[test]
170    fn consts() {
171        assert_eq!(BoolDefaultFalse::YES, Bool(true));
172        assert_eq!(BoolDefaultFalse::NO, Bool(false));
173
174        assert_eq!(BoolDefaultFalse::YES, true.into());
175        assert_eq!(BoolDefaultFalse::NO, false.into());
176
177        assert_eq!(BoolDefaultFalse::yes(), BoolDefaultFalse::YES);
178        assert_eq!(BoolDefaultFalse::no(), BoolDefaultFalse::NO);
179    }
180
181    #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
182    struct Test {
183        foo: Bool<false>,
184        bar: Bool<true>,
185    }
186
187    serde_test! {
188        serde: {
189            indoc! {"
190                foo: yes
191                bar: no
192            "} => Test {
193                foo: Bool::YES,
194                bar: Bool::NO,
195            },
196            indoc! {"
197                foo: yes
198            "} => Test {
199                foo: Bool::YES,
200                bar: Bool::YES,
201            },
202            indoc! {"
203                bar: no
204            "} => Test {
205                foo: Bool::NO,
206                bar: Bool::NO,
207            },
208            "" => Test {
209                foo: Bool::NO,
210                bar: Bool::YES,
211            },
212        }
213    }
214}