1use core::fmt;
4
5#[derive(Debug, Clone, PartialEq)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8pub enum Value {
9 Number(f64),
11 Text(String),
13 Bool(bool),
15}
16
17impl Value {
18 #[must_use]
20 pub fn is_truthy(&self) -> bool {
21 match self {
22 Self::Bool(v) => *v,
23 Self::Number(v) => *v != 0.0,
24 Self::Text(v) => !v.is_empty(),
25 }
26 }
27}
28
29impl fmt::Display for Value {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 match self {
32 Self::Number(v) => {
33 #[allow(clippy::cast_possible_truncation)]
35 if v.fract() == 0.0 && v.abs() < 1e15 {
36 write!(f, "{}", *v as i64)
37 } else {
38 write!(f, "{v}")
39 }
40 }
41 Self::Text(v) => f.write_str(v),
42 Self::Bool(v) => write!(f, "{v}"),
43 }
44 }
45}
46
47impl From<f64> for Value {
48 fn from(v: f64) -> Self {
49 Self::Number(v)
50 }
51}
52
53impl From<bool> for Value {
54 fn from(v: bool) -> Self {
55 Self::Bool(v)
56 }
57}
58
59impl From<String> for Value {
60 fn from(v: String) -> Self {
61 Self::Text(v)
62 }
63}
64
65impl From<&str> for Value {
66 fn from(v: &str) -> Self {
67 Self::Text(v.to_owned())
68 }
69}
70
71#[cfg(test)]
72mod tests {
73 use super::*;
74
75 #[test]
76 fn display_integer_number() {
77 assert_eq!(Value::Number(3.0).to_string(), "3");
78 }
79
80 #[test]
81 fn display_fractional_number() {
82 assert_eq!(Value::Number(3.5).to_string(), "3.5");
83 }
84
85 #[test]
86 fn truthy_coercion() {
87 assert!(Value::Bool(true).is_truthy());
88 assert!(!Value::Bool(false).is_truthy());
89 assert!(Value::Number(1.0).is_truthy());
90 assert!(!Value::Number(0.0).is_truthy());
91 assert!(Value::Text("hi".into()).is_truthy());
92 assert!(!Value::Text(String::new()).is_truthy());
93 }
94
95 #[test]
96 fn display_very_large_integer_uses_float_formatting() {
97 let s = Value::Number(1e16).to_string();
99 assert!(s.contains('e') || s.len() >= 15, "got {s}");
100 }
101
102 #[test]
103 fn display_bool() {
104 assert_eq!(Value::Bool(true).to_string(), "true");
105 }
106
107 #[test]
108 fn from_conversions() {
109 assert_eq!(Value::from(3.5_f64), Value::Number(3.5));
110 assert_eq!(Value::from(true), Value::Bool(true));
111 assert_eq!(Value::from("x".to_owned()), Value::Text("x".into()));
112 assert_eq!(Value::from("y"), Value::Text("y".into()));
113 }
114
115 #[test]
116 fn negative_number_is_truthy() {
117 assert!(Value::Number(-1.0).is_truthy());
118 }
119}