1use minijinja::value::Value as MjValue;
7use serde::{Deserialize, Serialize};
8use std::collections::BTreeMap;
9use std::ops::Deref;
10
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
16pub struct QuillValue(serde_json::Value);
17
18impl QuillValue {
19 pub fn from_toml(toml_val: &toml::Value) -> Result<Self, serde_json::Error> {
21 let json_val = serde_json::to_value(toml_val)?;
22 Ok(QuillValue(json_val))
23 }
24
25 pub fn from_yaml_str(yaml_str: &str) -> Result<Self, serde_saphyr::Error> {
27 let json_val: serde_json::Value = serde_saphyr::from_str(yaml_str)?;
28 Ok(QuillValue(json_val))
29 }
30
31 pub fn to_minijinja(&self) -> Result<MjValue, String> {
33 json_to_minijinja(&self.0)
34 }
35
36 pub fn as_json(&self) -> &serde_json::Value {
38 &self.0
39 }
40
41 pub fn into_json(self) -> serde_json::Value {
43 self.0
44 }
45
46 pub fn from_json(json_val: serde_json::Value) -> Self {
48 QuillValue(json_val)
49 }
50}
51
52impl Deref for QuillValue {
53 type Target = serde_json::Value;
54
55 fn deref(&self) -> &Self::Target {
56 &self.0
57 }
58}
59
60fn json_to_minijinja(value: &serde_json::Value) -> Result<MjValue, String> {
62 use serde_json::Value as JsonValue;
63
64 let result = match value {
65 JsonValue::Null => MjValue::from(()),
66 JsonValue::Bool(b) => MjValue::from(*b),
67 JsonValue::Number(n) => {
68 if let Some(i) = n.as_i64() {
69 MjValue::from(i)
70 } else if let Some(u) = n.as_u64() {
71 MjValue::from(u)
72 } else if let Some(f) = n.as_f64() {
73 MjValue::from(f)
74 } else {
75 return Err("Invalid number in JSON".to_string());
76 }
77 }
78 JsonValue::String(s) => MjValue::from(s.clone()),
79 JsonValue::Array(arr) => {
80 let mut vec = Vec::new();
81 for item in arr {
82 vec.push(json_to_minijinja(item)?);
83 }
84 MjValue::from(vec)
85 }
86 JsonValue::Object(map) => {
87 let mut obj = BTreeMap::new();
88 for (k, v) in map {
89 obj.insert(k.clone(), json_to_minijinja(v)?);
90 }
91 MjValue::from_object(obj)
92 }
93 };
94
95 Ok(result)
96}
97
98impl QuillValue {
100 pub fn is_null(&self) -> bool {
102 self.0.is_null()
103 }
104
105 pub fn as_str(&self) -> Option<&str> {
107 self.0.as_str()
108 }
109
110 pub fn as_bool(&self) -> Option<bool> {
112 self.0.as_bool()
113 }
114
115 pub fn as_i64(&self) -> Option<i64> {
117 self.0.as_i64()
118 }
119
120 pub fn as_u64(&self) -> Option<u64> {
122 self.0.as_u64()
123 }
124
125 pub fn as_f64(&self) -> Option<f64> {
127 self.0.as_f64()
128 }
129
130 pub fn as_array(&self) -> Option<&Vec<serde_json::Value>> {
132 self.0.as_array()
133 }
134
135 pub fn as_sequence(&self) -> Option<&Vec<serde_json::Value>> {
137 self.0.as_array()
138 }
139
140 pub fn as_object(&self) -> Option<&serde_json::Map<String, serde_json::Value>> {
142 self.0.as_object()
143 }
144
145 pub fn as_mapping(&self) -> Option<&serde_json::Map<String, serde_json::Value>> {
147 self.0.as_object()
148 }
149
150 pub fn get(&self, key: &str) -> Option<QuillValue> {
152 self.0.get(key).map(|v| QuillValue(v.clone()))
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159
160 #[test]
161 fn test_from_toml() {
162 let toml_str = r#"
163 [package]
164 name = "test"
165 version = "1.0.0"
166 "#;
167 let toml_val: toml::Value = toml::from_str(toml_str).unwrap();
168 let quill_val = QuillValue::from_toml(&toml_val).unwrap();
169
170 assert!(quill_val.as_object().is_some());
171 }
172
173 #[test]
174 fn test_from_yaml_str() {
175 let yaml_str = r#"
176 title: Test Document
177 author: John Doe
178 count: 42
179 "#;
180 let quill_val = QuillValue::from_yaml_str(yaml_str).unwrap();
181
182 assert_eq!(
183 quill_val.get("title").as_ref().and_then(|v| v.as_str()),
184 Some("Test Document")
185 );
186 assert_eq!(
187 quill_val.get("author").as_ref().and_then(|v| v.as_str()),
188 Some("John Doe")
189 );
190 assert_eq!(
191 quill_val.get("count").as_ref().and_then(|v| v.as_i64()),
192 Some(42)
193 );
194 }
195
196 #[test]
197 fn test_to_minijinja() {
198 let json_val = serde_json::json!({
199 "title": "Test",
200 "count": 42,
201 "active": true,
202 "items": [1, 2, 3]
203 });
204 let quill_val = QuillValue::from_json(json_val);
205 let mj_val = quill_val.to_minijinja().unwrap();
206
207 assert!(mj_val.as_object().is_some());
209 }
210
211 #[test]
212 fn test_as_json() {
213 let json_val = serde_json::json!({"key": "value"});
214 let quill_val = QuillValue::from_json(json_val.clone());
215
216 assert_eq!(quill_val.as_json(), &json_val);
217 }
218
219 #[test]
220 fn test_into_json() {
221 let json_val = serde_json::json!({"key": "value"});
222 let quill_val = QuillValue::from_json(json_val.clone());
223
224 assert_eq!(quill_val.into_json(), json_val);
225 }
226
227 #[test]
228 fn test_delegating_methods() {
229 let quill_val = QuillValue::from_json(serde_json::json!({
230 "name": "test",
231 "count": 42,
232 "active": true,
233 "items": [1, 2, 3]
234 }));
235
236 assert_eq!(
237 quill_val.get("name").as_ref().and_then(|v| v.as_str()),
238 Some("test")
239 );
240 assert_eq!(
241 quill_val.get("count").as_ref().and_then(|v| v.as_i64()),
242 Some(42)
243 );
244 assert_eq!(
245 quill_val.get("active").as_ref().and_then(|v| v.as_bool()),
246 Some(true)
247 );
248 assert!(quill_val
249 .get("items")
250 .as_ref()
251 .and_then(|v| v.as_array())
252 .is_some());
253 }
254
255 #[test]
256 fn test_yaml_with_tags() {
257 let yaml_str = r#"
259 value: 42
260 "#;
261 let quill_val = QuillValue::from_yaml_str(yaml_str).unwrap();
262
263 assert!(quill_val.as_object().is_some());
265 }
266
267 #[test]
268 fn test_null_value() {
269 let quill_val = QuillValue::from_json(serde_json::Value::Null);
270 assert!(quill_val.is_null());
271 }
272
273 #[test]
274 fn test_yaml_custom_tags_ignored() {
275 let yaml_str = "memo_from: !fill 2d lt example";
278 let quill_val = QuillValue::from_yaml_str(yaml_str).unwrap();
279
280 assert_eq!(
282 quill_val.get("memo_from").as_ref().and_then(|v| v.as_str()),
283 Some("2d lt example")
284 );
285 }
286}