1use std::fmt;
2
3use indexmap::IndexMap;
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
22#[non_exhaustive]
23pub enum Value {
24 Null,
26 Bool(bool),
28 Integer(i64),
30 Float(f64),
32 String(String),
34 Array(Vec<Value>),
36 Object(IndexMap<String, Value>),
38}
39
40impl fmt::Display for Value {
41 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42 match self {
43 Value::Null => write!(f, "null"),
44 Value::Bool(b) => write!(f, "{b}"),
45 Value::Integer(n) => write!(f, "{n}"),
46 Value::Float(n) => {
47 if n.fract() == 0.0 && n.is_finite() {
48 write!(f, "{n:.1}")
49 } else {
50 write!(f, "{n}")
51 }
52 }
53 Value::String(s) => write!(f, "{s}"),
54 Value::Array(arr) => {
55 write!(f, "[")?;
56 for (i, v) in arr.iter().enumerate() {
57 if i > 0 {
58 write!(f, ", ")?;
59 }
60 match v {
61 Value::String(s) => write!(f, "\"{s}\"")?,
62 _ => write!(f, "{v}")?,
63 }
64 }
65 write!(f, "]")
66 }
67 Value::Object(map) => {
68 write!(f, "{{")?;
69 for (i, (k, v)) in map.iter().enumerate() {
70 if i > 0 {
71 write!(f, ", ")?;
72 }
73 match v {
74 Value::String(s) => write!(f, "\"{k}\": \"{s}\"")?,
75 _ => write!(f, "\"{k}\": {v}")?,
76 }
77 }
78 write!(f, "}}")
79 }
80 }
81 }
82}
83
84#[allow(dead_code)]
85impl Value {
86 pub fn as_bool(&self) -> Option<bool> {
88 match self {
89 Value::Bool(b) => Some(*b),
90 _ => None,
91 }
92 }
93
94 pub fn as_i64(&self) -> Option<i64> {
96 match self {
97 Value::Integer(n) => Some(*n),
98 _ => None,
99 }
100 }
101
102 pub fn as_f64(&self) -> Option<f64> {
104 match self {
105 Value::Float(f) => Some(*f),
106 Value::Integer(n) => Some(*n as f64),
107 _ => None,
108 }
109 }
110
111 pub fn as_str(&self) -> Option<&str> {
113 match self {
114 Value::String(s) => Some(s),
115 _ => None,
116 }
117 }
118
119 pub fn as_array(&self) -> Option<&Vec<Value>> {
121 match self {
122 Value::Array(a) => Some(a),
123 _ => None,
124 }
125 }
126
127 pub fn as_object(&self) -> Option<&IndexMap<String, Value>> {
129 match self {
130 Value::Object(o) => Some(o),
131 _ => None,
132 }
133 }
134
135 pub fn is_null(&self) -> bool {
137 matches!(self, Value::Null)
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn test_value_accessors() {
147 assert_eq!(Value::Bool(true).as_bool(), Some(true));
148 assert_eq!(Value::Integer(42).as_i64(), Some(42));
149 assert_eq!(Value::Float(3.14).as_f64(), Some(3.14));
150 assert_eq!(Value::Integer(42).as_f64(), Some(42.0));
151 assert_eq!(Value::String("hello".into()).as_str(), Some("hello"));
152 assert!(Value::Null.is_null());
153 assert!(!Value::Bool(false).is_null());
154 }
155
156 #[test]
157 fn test_value_array() {
158 let arr = Value::Array(vec![Value::Integer(1), Value::Integer(2)]);
159 assert_eq!(arr.as_array().unwrap().len(), 2);
160 }
161
162 #[test]
163 fn test_value_object() {
164 let mut map = IndexMap::new();
165 map.insert("key".to_string(), Value::String("value".into()));
166 let obj = Value::Object(map);
167 assert_eq!(
168 obj.as_object().unwrap().get("key"),
169 Some(&Value::String("value".into()))
170 );
171 }
172
173 #[test]
174 fn test_display_primitives() {
175 assert_eq!(Value::Null.to_string(), "null");
176 assert_eq!(Value::Bool(true).to_string(), "true");
177 assert_eq!(Value::Bool(false).to_string(), "false");
178 assert_eq!(Value::Integer(42).to_string(), "42");
179 assert_eq!(Value::Float(3.14).to_string(), "3.14");
180 assert_eq!(Value::Float(1.0).to_string(), "1.0");
181 assert_eq!(Value::String("hello".into()).to_string(), "hello");
182 }
183
184 #[test]
185 fn test_display_array() {
186 let arr = Value::Array(vec![
187 Value::Integer(1),
188 Value::String("two".into()),
189 Value::Bool(true),
190 ]);
191 assert_eq!(arr.to_string(), r#"[1, "two", true]"#);
192 }
193
194 #[test]
195 fn test_display_empty_array() {
196 assert_eq!(Value::Array(vec![]).to_string(), "[]");
197 }
198
199 #[test]
200 fn test_display_object() {
201 let mut map = IndexMap::new();
202 map.insert("name".to_string(), Value::String("dkit".into()));
203 map.insert("version".to_string(), Value::Integer(1));
204 let obj = Value::Object(map);
205 assert_eq!(obj.to_string(), r#"{"name": "dkit", "version": 1}"#);
206 }
207
208 #[test]
209 fn test_display_empty_object() {
210 assert_eq!(Value::Object(IndexMap::new()).to_string(), "{}");
211 }
212
213 #[test]
214 fn test_accessor_returns_none_for_wrong_type() {
215 let v = Value::Integer(42);
216 assert_eq!(v.as_bool(), None);
217 assert_eq!(v.as_str(), None);
218 assert_eq!(v.as_array(), None);
219 assert_eq!(v.as_object(), None);
220 }
221}