1use std::collections::HashMap;
6
7#[derive(Debug, Clone, Default)]
9pub struct Document {
10 pub blocks: Vec<Block>,
11}
12
13#[derive(Debug, Clone)]
15pub struct Block {
16 pub name: String,
18 pub labels: Vec<String>,
20 pub blocks: Vec<Block>,
22 pub attributes: HashMap<String, Value>,
24}
25
26#[derive(Debug, Clone, PartialEq)]
28pub enum Value {
29 String(String),
31 Number(f64),
33 Bool(bool),
35 List(Vec<Value>),
37 Object(Vec<(String, Value)>),
39 Null,
41 Call(String, Vec<Value>),
43}
44
45impl Value {
46 pub fn as_str(&self) -> Option<&str> {
48 match self {
49 Value::String(s) => Some(s),
50 _ => None,
51 }
52 }
53
54 pub fn as_number(&self) -> Option<f64> {
56 match self {
57 Value::Number(n) => Some(*n),
58 _ => None,
59 }
60 }
61
62 pub fn as_bool(&self) -> Option<bool> {
64 match self {
65 Value::Bool(b) => Some(*b),
66 _ => None,
67 }
68 }
69
70 pub fn is_null(&self) -> bool {
72 matches!(self, Value::Null)
73 }
74
75 pub fn is_string(&self) -> bool {
77 matches!(self, Value::String(_))
78 }
79}
80
81impl std::fmt::Display for Value {
82 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
83 match self {
84 Value::String(s) => write!(f, "{}", s),
85 Value::Number(n) => {
86 if n.fract() == 0.0 {
87 write!(f, "{}", *n as i64)
88 } else {
89 write!(f, "{}", n)
90 }
91 }
92 Value::Bool(b) => write!(f, "{}", b),
93 Value::List(items) => {
94 let items_str: Vec<String> = items.iter().map(|v| v.to_string()).collect();
95 write!(f, "[{}]", items_str.join(", "))
96 }
97 Value::Object(pairs) => {
98 let pairs_str: Vec<String> = pairs
99 .iter()
100 .map(|(k, v)| format!("{} = {}", k, v))
101 .collect();
102 write!(f, "{{{}}}", pairs_str.join(", "))
103 }
104 Value::Null => write!(f, "null"),
105 Value::Call(name, args) => {
106 let args_str: Vec<String> = args.iter().map(|v| v.to_string()).collect();
107 write!(f, "{}({})", name, args_str.join(", "))
108 }
109 }
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn test_value_as_str() {
119 assert_eq!(Value::String("hello".to_string()).as_str(), Some("hello"));
120 assert_eq!(Value::Number(42.0).as_str(), None);
121 assert_eq!(Value::Bool(true).as_str(), None);
122 assert_eq!(Value::Null.as_str(), None);
123 }
124
125 #[test]
126 fn test_value_as_number() {
127 assert_eq!(Value::Number(42.0).as_number(), Some(42.0));
128 assert_eq!(Value::String("hello".to_string()).as_number(), None);
129 assert_eq!(Value::Bool(true).as_number(), None);
130 assert_eq!(Value::Null.as_number(), None);
131 }
132
133 #[test]
134 fn test_value_as_bool() {
135 assert_eq!(Value::Bool(true).as_bool(), Some(true));
136 assert_eq!(Value::Bool(false).as_bool(), Some(false));
137 assert_eq!(Value::String("hello".to_string()).as_bool(), None);
138 assert_eq!(Value::Number(42.0).as_bool(), None);
139 }
140
141 #[test]
142 fn test_value_is_null() {
143 assert!(Value::Null.is_null());
144 assert!(!Value::String("hello".to_string()).is_null());
145 assert!(!Value::Number(42.0).is_null());
146 assert!(!Value::Bool(true).is_null());
147 }
148
149 #[test]
150 fn test_value_is_string() {
151 assert!(Value::String("hello".to_string()).is_string());
152 assert!(!Value::Number(42.0).is_string());
153 assert!(!Value::Bool(true).is_string());
154 assert!(!Value::Null.is_string());
155 }
156
157 #[test]
158 fn test_value_display() {
159 assert_eq!(Value::String("hello".to_string()).to_string(), "hello");
160 assert_eq!(Value::Number(42.0).to_string(), "42");
161 assert_eq!(Value::Number(3.14).to_string(), "3.14");
162 assert_eq!(Value::Bool(true).to_string(), "true");
163 assert_eq!(Value::Bool(false).to_string(), "false");
164 assert_eq!(Value::Null.to_string(), "null");
165 assert_eq!(Value::List(vec![]).to_string(), "[]");
166 assert_eq!(
167 Value::List(vec![Value::Number(1.0), Value::Number(2.0)]).to_string(),
168 "[1, 2]"
169 );
170 assert_eq!(Value::Object(vec![]).to_string(), "{}");
171 assert_eq!(
172 Value::Object(vec![("a".to_string(), Value::Number(1.0))]).to_string(),
173 "{a = 1}"
174 );
175 }
176
177 #[test]
178 fn test_document_default() {
179 let doc = Document::default();
180 assert!(doc.blocks.is_empty());
181 }
182
183 #[test]
184 fn test_block_clone() {
185 let mut attrs = HashMap::new();
186 attrs.insert("key".to_string(), Value::String("value".to_string()));
187 let block = Block {
188 name: "test".to_string(),
189 labels: vec!["label".to_string()],
190 blocks: vec![],
191 attributes: attrs,
192 };
193 let cloned = block.clone();
194 assert_eq!(cloned.name, "test");
195 assert_eq!(cloned.labels, vec!["label"]);
196 }
197
198 #[test]
199 fn test_value_clone() {
200 let value = Value::String("test".to_string());
201 let cloned = value.clone();
202 assert_eq!(value, cloned);
203 }
204
205 #[test]
206 fn test_value_partial_eq() {
207 assert_eq!(
208 Value::String("a".to_string()),
209 Value::String("a".to_string())
210 );
211 assert_ne!(
212 Value::String("a".to_string()),
213 Value::String("b".to_string())
214 );
215 assert_eq!(Value::Number(42.0), Value::Number(42.0));
216 assert_ne!(Value::Number(42.0), Value::Number(43.0));
217 assert_eq!(Value::Bool(true), Value::Bool(true));
218 assert_ne!(Value::Bool(true), Value::Bool(false));
219 assert_eq!(Value::Null, Value::Null);
220 }
221}