1use indexmap::IndexMap;
13use serde::{Deserialize, Deserializer, Serialize, Serializer};
14use serde_json::Value as JsonValue;
15use std::ops::Deref;
16use std::sync::OnceLock;
17
18pub struct QuillValue {
27 node: Node,
28 json: OnceLock<JsonValue>,
30}
31
32#[derive(Debug, Clone, PartialEq)]
34struct Node {
35 fill: bool,
36 kind: Kind,
37}
38
39#[derive(Debug, Clone, PartialEq)]
40enum Kind {
41 Null,
42 Bool(bool),
43 Number(serde_json::Number),
44 String(String),
45 Array(Vec<Node>),
46 Object(IndexMap<String, Node>),
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Hash)]
54pub enum PathSegment {
55 Key(String),
56 Index(usize),
57}
58
59fn collect_fill_paths(node: &Node, prefix: &mut Vec<PathSegment>, out: &mut Vec<Vec<PathSegment>>) {
60 if node.fill {
61 out.push(prefix.clone());
62 }
63 match &node.kind {
64 Kind::Array(items) => {
65 for (i, child) in items.iter().enumerate() {
66 prefix.push(PathSegment::Index(i));
67 collect_fill_paths(child, prefix, out);
68 prefix.pop();
69 }
70 }
71 Kind::Object(entries) => {
72 for (k, child) in entries {
73 prefix.push(PathSegment::Key(k.clone()));
74 collect_fill_paths(child, prefix, out);
75 prefix.pop();
76 }
77 }
78 _ => {}
79 }
80}
81
82fn node_at_mut<'a>(node: &'a mut Node, path: &[PathSegment]) -> Option<&'a mut Node> {
83 let mut cur = node;
84 for seg in path {
85 cur = match (&mut cur.kind, seg) {
86 (Kind::Object(entries), PathSegment::Key(k)) => entries.get_mut(k)?,
87 (Kind::Array(items), PathSegment::Index(i)) => items.get_mut(*i)?,
88 _ => return None,
89 };
90 }
91 Some(cur)
92}
93
94fn node_is_object(node: &Node, path: &[PathSegment]) -> bool {
95 fn at<'a>(node: &'a Node, path: &[PathSegment]) -> Option<&'a Node> {
96 let mut cur = node;
97 for seg in path {
98 cur = match (&cur.kind, seg) {
99 (Kind::Object(entries), PathSegment::Key(k)) => entries.get(k)?,
100 (Kind::Array(items), PathSegment::Index(i)) => items.get(*i)?,
101 _ => return None,
102 };
103 }
104 Some(cur)
105 }
106 matches!(at(node, path).map(|n| &n.kind), Some(Kind::Object(_)))
107}
108
109impl Node {
110 fn from_json(value: &JsonValue) -> Node {
111 let kind = match value {
112 JsonValue::Null => Kind::Null,
113 JsonValue::Bool(b) => Kind::Bool(*b),
114 JsonValue::Number(n) => Kind::Number(n.clone()),
115 JsonValue::String(s) => Kind::String(s.clone()),
116 JsonValue::Array(items) => Kind::Array(items.iter().map(Node::from_json).collect()),
117 JsonValue::Object(map) => Kind::Object(
118 map.iter()
119 .map(|(k, v)| (k.clone(), Node::from_json(v)))
120 .collect(),
121 ),
122 };
123 Node { fill: false, kind }
124 }
125
126 fn to_json(&self) -> JsonValue {
127 match &self.kind {
128 Kind::Null => JsonValue::Null,
129 Kind::Bool(b) => JsonValue::Bool(*b),
130 Kind::Number(n) => JsonValue::Number(n.clone()),
131 Kind::String(s) => JsonValue::String(s.clone()),
132 Kind::Array(items) => JsonValue::Array(items.iter().map(Node::to_json).collect()),
133 Kind::Object(entries) => JsonValue::Object(
134 entries
135 .iter()
136 .map(|(k, n)| (k.clone(), n.to_json()))
137 .collect(),
138 ),
139 }
140 }
141}
142
143pub fn json_depth_exceeds(value: &serde_json::Value, max_depth: usize) -> bool {
154 use serde_json::Value;
155 let mut stack: Vec<(&Value, usize)> = vec![(value, 0)];
157 while let Some((v, depth)) = stack.pop() {
158 match v {
159 Value::Array(items) => {
160 if depth + 1 > max_depth && !items.is_empty() {
161 return true;
162 }
163 stack.extend(items.iter().map(|c| (c, depth + 1)));
164 }
165 Value::Object(map) => {
166 if depth + 1 > max_depth && !map.is_empty() {
167 return true;
168 }
169 stack.extend(map.values().map(|c| (c, depth + 1)));
170 }
171 _ => {}
172 }
173 }
174 false
175}
176
177impl QuillValue {
178 fn from_node(node: Node) -> Self {
179 QuillValue {
180 node,
181 json: OnceLock::new(),
182 }
183 }
184
185 pub fn from_yaml_str(yaml_str: &str) -> Result<Self, serde_saphyr::Error> {
187 let json_val: serde_json::Value = serde_saphyr::from_str(yaml_str)?;
188 Ok(Self::from_json(json_val))
189 }
190
191 pub fn as_json(&self) -> &serde_json::Value {
196 self.json.get_or_init(|| self.node.to_json())
197 }
198
199 pub fn into_json(self) -> serde_json::Value {
201 match self.json.into_inner() {
202 Some(json) => json,
203 None => self.node.to_json(),
204 }
205 }
206
207 pub fn from_json(json_val: serde_json::Value) -> Self {
209 let node = Node::from_json(&json_val);
210 let json = OnceLock::new();
211 let _ = json.set(json_val);
218 QuillValue { node, json }
219 }
220
221 pub fn string(s: impl Into<String>) -> Self {
223 Self::from_json(serde_json::Value::String(s.into()))
224 }
225
226 pub fn integer(n: i64) -> Self {
228 Self::from_json(serde_json::Value::Number(n.into()))
229 }
230
231 pub fn bool(b: bool) -> Self {
233 Self::from_json(serde_json::Value::Bool(b))
234 }
235
236 pub fn null() -> Self {
238 Self::from_json(serde_json::Value::Null)
239 }
240
241 pub fn fill(&self) -> bool {
243 self.node.fill
244 }
245
246 pub fn with_fill(mut self, fill: bool) -> Self {
248 self.node.fill = fill;
249 self
250 }
251
252 pub fn set_fill(&mut self, fill: bool) {
256 self.node.fill = fill;
257 }
258
259 pub fn fill_paths(&self) -> Vec<Vec<PathSegment>> {
264 let mut out = Vec::new();
265 let mut prefix = Vec::new();
266 collect_fill_paths(&self.node, &mut prefix, &mut out);
267 out
268 }
269
270 pub fn nonroot_fill_paths(&self) -> impl Iterator<Item = Vec<PathSegment>> {
275 self.fill_paths().into_iter().filter(|p| !p.is_empty())
276 }
277
278 pub fn set_fill_at(&mut self, path: &[PathSegment]) -> bool {
281 match node_at_mut(&mut self.node, path) {
282 Some(n) => {
283 n.fill = true;
284 true
285 }
286 None => false,
287 }
288 }
289
290 pub fn is_object_at(&self, path: &[PathSegment]) -> bool {
293 node_is_object(&self.node, path)
294 }
295}
296
297impl Deref for QuillValue {
298 type Target = serde_json::Value;
299
300 fn deref(&self) -> &Self::Target {
301 self.as_json()
302 }
303}
304
305impl PartialEq for QuillValue {
306 fn eq(&self, other: &Self) -> bool {
309 self.node == other.node
310 }
311}
312
313impl Clone for QuillValue {
314 fn clone(&self) -> Self {
315 let json = OnceLock::new();
316 if let Some(cached) = self.json.get() {
317 let _ = json.set(cached.clone());
318 }
319 QuillValue {
320 node: self.node.clone(),
321 json,
322 }
323 }
324}
325
326impl std::fmt::Debug for QuillValue {
327 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
328 if self.node.fill {
329 write!(f, "QuillValue(!must_fill {:?})", self.as_json())
330 } else {
331 write!(f, "QuillValue({:?})", self.as_json())
332 }
333 }
334}
335
336impl Serialize for QuillValue {
337 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
338 self.as_json().serialize(serializer)
339 }
340}
341
342impl<'de> Deserialize<'de> for QuillValue {
343 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
344 let json = serde_json::Value::deserialize(deserializer)?;
345 Ok(QuillValue::from_json(json))
346 }
347}
348
349impl QuillValue {
352 pub fn is_null(&self) -> bool {
354 self.as_json().is_null()
355 }
356
357 pub fn as_str(&self) -> Option<&str> {
359 self.as_json().as_str()
360 }
361
362 pub fn as_bool(&self) -> Option<bool> {
364 self.as_json().as_bool()
365 }
366
367 pub fn as_i64(&self) -> Option<i64> {
369 self.as_json().as_i64()
370 }
371
372 pub fn as_u64(&self) -> Option<u64> {
374 self.as_json().as_u64()
375 }
376
377 pub fn as_f64(&self) -> Option<f64> {
379 self.as_json().as_f64()
380 }
381
382 pub fn as_array(&self) -> Option<&Vec<serde_json::Value>> {
384 self.as_json().as_array()
385 }
386
387 pub fn as_object(&self) -> Option<&serde_json::Map<String, serde_json::Value>> {
389 self.as_json().as_object()
390 }
391
392 pub fn get(&self, key: &str) -> Option<QuillValue> {
394 match &self.node.kind {
395 Kind::Object(entries) => entries.get(key).map(|n| QuillValue::from_node(n.clone())),
396 _ => None,
397 }
398 }
399}
400
401#[cfg(test)]
402mod tests {
403 use super::*;
404
405 #[test]
406 fn test_from_yaml_value() {
407 let yaml_str = r#"
408 package:
409 name: test
410 version: 1.0.0
411 "#;
412 let json_val: serde_json::Value = serde_saphyr::from_str(yaml_str).unwrap();
413 let quill_val = QuillValue::from_json(json_val);
414
415 assert!(quill_val.as_object().is_some());
416 assert_eq!(
417 quill_val
418 .get("package")
419 .unwrap()
420 .get("name")
421 .unwrap()
422 .as_str(),
423 Some("test")
424 );
425 }
426
427 #[test]
428 fn test_from_yaml_str() {
429 let yaml_str = r#"
430 title: Test Document
431 author: John Doe
432 count: 42
433 "#;
434 let quill_val = QuillValue::from_yaml_str(yaml_str).unwrap();
435
436 assert_eq!(
437 quill_val.get("title").as_ref().and_then(|v| v.as_str()),
438 Some("Test Document")
439 );
440 assert_eq!(
441 quill_val.get("author").as_ref().and_then(|v| v.as_str()),
442 Some("John Doe")
443 );
444 assert_eq!(
445 quill_val.get("count").as_ref().and_then(|v| v.as_i64()),
446 Some(42)
447 );
448 }
449
450 #[test]
451 fn test_delegating_methods() {
452 let quill_val = QuillValue::from_json(serde_json::json!({
453 "name": "test",
454 "count": 42,
455 "active": true,
456 "items": [1, 2, 3]
457 }));
458
459 assert_eq!(
460 quill_val.get("name").as_ref().and_then(|v| v.as_str()),
461 Some("test")
462 );
463 assert_eq!(
464 quill_val.get("count").as_ref().and_then(|v| v.as_i64()),
465 Some(42)
466 );
467 assert_eq!(
468 quill_val.get("active").as_ref().and_then(|v| v.as_bool()),
469 Some(true)
470 );
471 assert!(quill_val
472 .get("items")
473 .as_ref()
474 .and_then(|v| v.as_array())
475 .is_some());
476 }
477
478 #[test]
479 fn test_yaml_custom_tags_ignored_at_value_level() {
480 let yaml_str = "memo_from: !must_fill 2d lt example";
486 let quill_val = QuillValue::from_yaml_str(yaml_str).unwrap();
487
488 assert_eq!(
489 quill_val.get("memo_from").as_ref().and_then(|v| v.as_str()),
490 Some("2d lt example")
491 );
492 }
493
494 #[test]
495 fn json_round_trips_through_the_tree() {
496 let original = serde_json::json!({
499 "z": 1,
500 "a": [true, "x", 3.5, null],
501 "nested": { "k": 42 }
502 });
503 let qv = QuillValue::from_json(original.clone());
504 assert_eq!(qv.as_json(), &original);
505
506 let relowered = QuillValue::from_node(qv.node.clone()).into_json();
508 assert_eq!(relowered, original);
509 }
510
511 #[test]
512 fn fill_marker_rides_on_the_node_not_the_json() {
513 let qv = QuillValue::string("draft").with_fill(true);
514 assert!(qv.fill());
515 assert_eq!(qv.as_json(), &serde_json::json!("draft"));
517 assert_ne!(qv, QuillValue::string("draft"));
519 assert_eq!(qv, QuillValue::string("draft").with_fill(true));
520 }
521}