1use serde::{Deserialize, Deserializer, Serialize};
2#[cfg(feature = "macro")]
3pub use serde_tristate_macros::serde_tristate;
4
5#[derive(Default)]
26pub enum Tristate<T> {
27 Value(T),
28 None,
29 #[default]
30 Undefined,
31}
32
33impl<T> Tristate<T> {
34 pub fn is_undefined(&self) -> bool {
35 matches!(self, Tristate::Undefined)
36 }
37
38 pub fn is_none(&self) -> bool {
39 matches!(self, Tristate::None)
40 }
41
42 pub fn is_value(&self) -> bool {
43 matches!(self, Tristate::Value(_))
44 }
45}
46
47impl<T> From<T> for Tristate<T> {
48 fn from(v: T) -> Self {
49 Tristate::Value(v)
50 }
51}
52
53impl<T> From<Option<T>> for Tristate<T> {
54 fn from(opt: Option<T>) -> Self {
55 match opt {
56 Some(v) => Tristate::Value(v),
57 None => Tristate::None,
58 }
59 }
60}
61
62impl<T> From<Option<Option<T>>> for Tristate<T> {
63 fn from(opt: Option<Option<T>>) -> Self {
64 match opt {
65 None => Tristate::Undefined,
66 Some(None) => Tristate::None,
67 Some(Some(v)) => Tristate::Value(v),
68 }
69 }
70}
71
72impl<T> From<Tristate<T>> for Option<Option<T>> {
73 fn from(val: Tristate<T>) -> Self {
74 match val {
75 Tristate::Undefined => None,
76 Tristate::None => Some(None),
77 Tristate::Value(v) => Some(Some(v)),
78 }
79 }
80}
81
82impl<T> Tristate<T> {
83 pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Tristate<U> {
85 match self {
86 Tristate::Value(v) => Tristate::Value(f(v)),
87 Tristate::None => Tristate::None,
88 Tristate::Undefined => Tristate::Undefined,
89 }
90 }
91
92 pub fn and_then<U, F: FnOnce(T) -> Tristate<U>>(self, f: F) -> Tristate<U> {
94 match self {
95 Tristate::Value(v) => f(v),
96 Tristate::None => Tristate::None,
97 Tristate::Undefined => Tristate::Undefined,
98 }
99 }
100
101 pub fn unwrap_or(self, default: T) -> T {
103 match self {
104 Tristate::Value(v) => v,
105 _ => default,
106 }
107 }
108
109 pub fn unwrap_or_else<F: FnOnce() -> T>(self, f: F) -> T {
111 match self {
112 Tristate::Value(v) => v,
113 _ => f(),
114 }
115 }
116}
117
118impl<T> Tristate<T> {
119 pub fn apply_to_tristate(self, target: &mut T) {
122 if let Tristate::Value(v) = self {
123 *target = v;
124 }
125 }
126
127 pub fn apply_to_option(self, target: &mut Option<T>) {
130 match self {
131 Tristate::Value(v) => *target = Some(v),
132 Tristate::None => *target = None,
133 Tristate::Undefined => {}
134 }
135 }
136}
137
138impl<T: Serialize> Serialize for Tristate<T> {
139 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
140 match self {
141 Tristate::Value(v) => v.serialize(serializer),
142 Tristate::None => serializer.serialize_none(),
143 Tristate::Undefined => serializer.serialize_none(),
144 }
145 }
146}
147
148impl<'de, T: Deserialize<'de>> Deserialize<'de> for Tristate<T> {
149 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
150 Option::<T>::deserialize(deserializer).map(|opt| match opt {
151 Some(v) => Tristate::Value(v),
152 None => Tristate::None,
153 })
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160 use serde::{Deserialize, Serialize};
161
162 #[serde_tristate]
163 #[derive(Serialize, Deserialize)]
164 struct Dto {
165 name: Tristate<String>,
166 age: Tristate<u32>,
167 }
168
169 #[test]
170 fn serialize_value() {
171 let dto = Dto {
172 name: Tristate::Value("Alice".into()),
173 age: Tristate::Undefined,
174 };
175 assert_eq!(serde_json::to_string(&dto).unwrap(), r#"{"name":"Alice"}"#);
176 }
177
178 #[test]
179 fn serialize_none() {
180 let dto = Dto {
181 name: Tristate::None,
182 age: Tristate::Undefined,
183 };
184 assert_eq!(serde_json::to_string(&dto).unwrap(), r#"{"name":null}"#);
185 }
186
187 #[test]
188 fn serialize_undefined_skipped() {
189 let dto = Dto {
190 name: Tristate::Undefined,
191 age: Tristate::Undefined,
192 };
193 assert_eq!(serde_json::to_string(&dto).unwrap(), r#"{}"#);
194 }
195
196 #[test]
197 fn deserialize_value() {
198 let dto: Dto = serde_json::from_str(r#"{"name":"Bob","age":42}"#).unwrap();
199 assert!(matches!(dto.name, Tristate::Value(s) if s == "Bob"));
200 assert!(matches!(dto.age, Tristate::Value(42)));
201 }
202
203 #[test]
204 fn deserialize_null_as_none() {
205 let dto: Dto = serde_json::from_str(r#"{"name":null}"#).unwrap();
206 assert!(matches!(dto.name, Tristate::None));
207 assert!(matches!(dto.age, Tristate::Undefined));
208 }
209
210 #[test]
211 fn deserialize_absent_as_undefined() {
212 let dto: Dto = serde_json::from_str(r#"{}"#).unwrap();
213 assert!(matches!(dto.name, Tristate::Undefined));
214 assert!(matches!(dto.age, Tristate::Undefined));
215 }
216
217 #[serde_tristate]
218 #[derive(Serialize, Deserialize)]
219 #[serde(tag = "kind")]
220 enum Event {
221 Update {
222 name: Tristate<String>,
223 age: Tristate<u32>,
224 },
225 Delete,
226 }
227
228 #[test]
229 fn enum_serialize_value() {
230 let e = Event::Update {
231 name: Tristate::Value("Carol".into()),
232 age: Tristate::Undefined,
233 };
234 assert_eq!(
235 serde_json::to_string(&e).unwrap(),
236 r#"{"kind":"Update","name":"Carol"}"#
237 );
238 }
239
240 #[test]
241 fn enum_serialize_all_undefined() {
242 let e = Event::Update {
243 name: Tristate::Undefined,
244 age: Tristate::Undefined,
245 };
246 assert_eq!(serde_json::to_string(&e).unwrap(), r#"{"kind":"Update"}"#);
247 }
248
249 #[test]
250 fn enum_deserialize_value() {
251 let e: Event = serde_json::from_str(r#"{"kind":"Update","name":"Dave"}"#).unwrap();
252 assert!(
253 matches!(e, Event::Update { name: Tristate::Value(s), age: Tristate::Undefined } if s == "Dave")
254 );
255 }
256
257 #[test]
258 fn from_value() {
259 let p: Tristate<i32> = 42.into();
260 assert!(matches!(p, Tristate::Value(42)));
261 }
262
263 #[test]
264 fn from_some() {
265 let p: Tristate<i32> = Some(42).into();
266 assert!(matches!(p, Tristate::Value(42)));
267 }
268
269 #[test]
270 fn from_option_none() {
271 let p: Tristate<i32> = Option::<i32>::None.into();
272 assert!(matches!(p, Tristate::None));
273 }
274
275 #[test]
276 fn into_option_value() {
277 assert_eq!(
278 Into::<Option<Option<i32>>>::into(Tristate::Value(1)),
279 Some(Some(1))
280 );
281 }
282
283 #[test]
284 fn into_option_none() {
285 assert_eq!(
286 Into::<Option<Option<i32>>>::into(Tristate::<i32>::None),
287 Some(Option::None)
288 );
289 }
290
291 #[test]
292 fn into_option_undefined() {
293 assert_eq!(
294 Into::<Option<Option<i32>>>::into(Tristate::<i32>::Undefined),
295 Option::<Option<i32>>::None
296 );
297 }
298
299 #[test]
300 fn from_option_fn_some_some() {
301 assert!(matches!(Tristate::from(Some(Some(1))), Tristate::Value(1)));
302 }
303
304 #[test]
305 fn from_option_fn_some_none() {
306 assert!(matches!(
307 Tristate::<i32>::from(Some(Option::None)),
308 Tristate::None
309 ));
310 }
311
312 #[test]
313 fn from_option_fn_outer_none() {
314 assert!(matches!(
315 Tristate::<i32>::from(Option::<Option<i32>>::None),
316 Tristate::Undefined
317 ));
318 }
319
320 #[test]
321 fn from_option_roundtrip() {
322 let cases: [Tristate<i32>; 3] = [Tristate::Value(42), Tristate::None, Tristate::Undefined];
323 for t in cases {
324 let opt: Option<Option<i32>> = t.into();
325 assert!(matches!(Tristate::<i32>::from(opt), _));
326 }
327 }
328
329 #[test]
330 fn map_value() {
331 assert!(matches!(
332 Tristate::Value(2).map(|x| x * 3),
333 Tristate::Value(6)
334 ));
335 }
336
337 #[test]
338 fn map_none_passthrough() {
339 assert!(matches!(
340 Tristate::<i32>::None.map(|x| x * 3),
341 Tristate::None
342 ));
343 }
344
345 #[test]
346 fn and_then_value() {
347 let p = Tristate::Value(5).and_then(|x| {
348 if x > 3 {
349 Tristate::Value(x)
350 } else {
351 Tristate::None
352 }
353 });
354 assert!(matches!(p, Tristate::Value(5)));
355 }
356
357 #[test]
358 fn unwrap_or_value() {
359 assert_eq!(Tristate::Value(7).unwrap_or(0), 7);
360 }
361
362 #[test]
363 fn unwrap_or_undefined() {
364 assert_eq!(Tristate::<i32>::Undefined.unwrap_or(0), 0);
365 }
366
367 #[test]
368 fn apply_to_required_sets_value() {
369 let mut target = String::from("old");
370 Tristate::Value("new".to_string()).apply_to_tristate(&mut target);
371 assert_eq!(target, "new");
372 }
373
374 #[test]
375 fn apply_to_required_undefined_noop() {
376 let mut target = String::from("old");
377 Tristate::<String>::Undefined.apply_to_tristate(&mut target);
378 assert_eq!(target, "old");
379 }
380
381 #[test]
382 fn apply_to_option_sets_some() {
383 let mut target: Option<String> = Option::None;
384 Tristate::Value("hi".to_string()).apply_to_option(&mut target);
385 assert_eq!(target, Some("hi".to_string()));
386 }
387
388 #[test]
389 fn apply_to_option_none_clears() {
390 let mut target: Option<String> = Some("bye".to_string());
391 Tristate::<String>::None.apply_to_option(&mut target);
392 assert_eq!(target, Option::None);
393 }
394
395 #[test]
396 fn apply_to_option_undefined_noop() {
397 let mut target: Option<String> = Some("keep".to_string());
398 Tristate::<String>::Undefined.apply_to_option(&mut target);
399 assert_eq!(target, Some("keep".to_string()));
400 }
401}