1use core::fmt;
9
10#[derive(Debug, Clone, Copy, PartialEq, Default)]
15pub enum Value<'a> {
16 #[default]
19 Null,
20 Bool(bool),
22 I64(i64),
24 U64(u64),
26 F64(f64),
30 Str(&'a str),
32 Char(char),
34}
35
36impl<'a> Value<'a> {
37 pub const fn is_null(&self) -> bool {
39 matches!(self, Self::Null)
40 }
41
42 pub const fn variant_name(&self) -> &'static str {
44 match self {
45 Self::Null => "null",
46 Self::Bool(_) => "bool",
47 Self::I64(_) => "i64",
48 Self::U64(_) => "u64",
49 Self::F64(_) => "f64",
50 Self::Str(_) => "str",
51 Self::Char(_) => "char",
52 }
53 }
54}
55
56impl<'a> fmt::Display for Value<'a> {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 match self {
59 Self::Null => f.write_str("null"),
60 Self::Bool(b) => fmt::Display::fmt(b, f),
61 Self::I64(n) => fmt::Display::fmt(n, f),
62 Self::U64(n) => fmt::Display::fmt(n, f),
63 Self::F64(n) => fmt::Display::fmt(n, f),
64 Self::Str(s) => f.write_str(s),
65 Self::Char(c) => fmt::Display::fmt(c, f),
66 }
67 }
68}
69
70impl<'a> From<&'a str> for Value<'a> {
74 fn from(value: &'a str) -> Self {
75 Self::Str(value)
76 }
77}
78
79impl From<bool> for Value<'_> {
80 fn from(value: bool) -> Self {
81 Self::Bool(value)
82 }
83}
84
85impl From<char> for Value<'_> {
86 fn from(value: char) -> Self {
87 Self::Char(value)
88 }
89}
90
91macro_rules! from_int {
92 ($($ty:ty => $variant:ident),* $(,)?) => {
93 $(
94 impl From<$ty> for Value<'_> {
95 fn from(value: $ty) -> Self {
96 #[allow(clippy::cast_lossless)]
97 Self::$variant(value as _)
98 }
99 }
100 )*
101 };
102}
103
104from_int!(
105 i8 => I64, i16 => I64, i32 => I64, i64 => I64, isize => I64,
106 u8 => U64, u16 => U64, u32 => U64, u64 => U64, usize => U64,
107);
108
109impl From<f32> for Value<'_> {
110 fn from(value: f32) -> Self {
111 Self::F64(f64::from(value))
112 }
113}
114
115impl From<f64> for Value<'_> {
116 fn from(value: f64) -> Self {
117 Self::F64(value)
118 }
119}
120
121impl<'a, T> From<Option<T>> for Value<'a>
122where
123 T: Into<Value<'a>>,
124{
125 fn from(value: Option<T>) -> Self {
126 match value {
127 Some(v) => v.into(),
128 None => Self::Null,
129 }
130 }
131}
132
133#[cfg(all(test, feature = "std"))]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn from_str_is_borrowed() {
139 let s = String::from("hello");
140 let v: Value<'_> = s.as_str().into();
141 assert_eq!(v, Value::Str("hello"));
142 }
143
144 #[test]
145 fn integer_conversions() {
146 let v: Value<'_> = 42_i32.into();
147 assert_eq!(v, Value::I64(42));
148 let v: Value<'_> = 42_u32.into();
149 assert_eq!(v, Value::U64(42));
150 let v: Value<'_> = (-1_i64).into();
151 assert_eq!(v, Value::I64(-1));
152 }
153
154 #[test]
155 fn option_into_value() {
156 let some: Value<'_> = Some(7_u32).into();
157 assert_eq!(some, Value::U64(7));
158 let none: Value<'_> = Option::<u32>::None.into();
159 assert!(none.is_null());
160 }
161
162 #[test]
163 fn variant_name_covers_all() {
164 for v in [
165 Value::Null,
166 Value::Bool(true),
167 Value::I64(0),
168 Value::U64(0),
169 Value::F64(0.0),
170 Value::Str(""),
171 Value::Char('a'),
172 ] {
173 assert!(!v.variant_name().is_empty());
174 }
175 }
176
177 #[test]
178 fn display_renders_each_variant() {
179 assert_eq!(Value::Null.to_string(), "null");
180 assert_eq!(Value::Bool(true).to_string(), "true");
181 assert_eq!(Value::I64(-5).to_string(), "-5");
182 assert_eq!(Value::U64(5).to_string(), "5");
183 assert_eq!(Value::Str("x").to_string(), "x");
184 assert_eq!(Value::Char('y').to_string(), "y");
185 }
186}