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)]
54#[non_exhaustive]
55pub enum PathSegment {
56 Key(String),
57 Index(usize),
58}
59
60fn collect_fill_paths(node: &Node, prefix: &mut Vec<PathSegment>, out: &mut Vec<Vec<PathSegment>>) {
61 if node.fill {
62 out.push(prefix.clone());
63 }
64 match &node.kind {
65 Kind::Array(items) => {
66 for (i, child) in items.iter().enumerate() {
67 prefix.push(PathSegment::Index(i));
68 collect_fill_paths(child, prefix, out);
69 prefix.pop();
70 }
71 }
72 Kind::Object(entries) => {
73 for (k, child) in entries {
74 prefix.push(PathSegment::Key(k.clone()));
75 collect_fill_paths(child, prefix, out);
76 prefix.pop();
77 }
78 }
79 _ => {}
80 }
81}
82
83fn node_at_mut<'a>(node: &'a mut Node, path: &[PathSegment]) -> Option<&'a mut Node> {
84 let mut cur = node;
85 for seg in path {
86 cur = match (&mut cur.kind, seg) {
87 (Kind::Object(entries), PathSegment::Key(k)) => entries.get_mut(k)?,
88 (Kind::Array(items), PathSegment::Index(i)) => items.get_mut(*i)?,
89 _ => return None,
90 };
91 }
92 Some(cur)
93}
94
95fn node_is_object(node: &Node, path: &[PathSegment]) -> bool {
96 fn at<'a>(node: &'a Node, path: &[PathSegment]) -> Option<&'a Node> {
97 let mut cur = node;
98 for seg in path {
99 cur = match (&cur.kind, seg) {
100 (Kind::Object(entries), PathSegment::Key(k)) => entries.get(k)?,
101 (Kind::Array(items), PathSegment::Index(i)) => items.get(*i)?,
102 _ => return None,
103 };
104 }
105 Some(cur)
106 }
107 matches!(at(node, path).map(|n| &n.kind), Some(Kind::Object(_)))
108}
109
110impl Node {
111 fn from_json(value: &JsonValue) -> Node {
112 let kind = match value {
113 JsonValue::Null => Kind::Null,
114 JsonValue::Bool(b) => Kind::Bool(*b),
115 JsonValue::Number(n) => Kind::Number(n.clone()),
116 JsonValue::String(s) => Kind::String(s.clone()),
117 JsonValue::Array(items) => Kind::Array(items.iter().map(Node::from_json).collect()),
118 JsonValue::Object(map) => Kind::Object(
119 map.iter()
120 .map(|(k, v)| (k.clone(), Node::from_json(v)))
121 .collect(),
122 ),
123 };
124 Node { fill: false, kind }
125 }
126
127 fn to_json(&self) -> JsonValue {
128 match &self.kind {
129 Kind::Null => JsonValue::Null,
130 Kind::Bool(b) => JsonValue::Bool(*b),
131 Kind::Number(n) => JsonValue::Number(n.clone()),
132 Kind::String(s) => JsonValue::String(s.clone()),
133 Kind::Array(items) => JsonValue::Array(items.iter().map(Node::to_json).collect()),
134 Kind::Object(entries) => JsonValue::Object(
135 entries
136 .iter()
137 .map(|(k, n)| (k.clone(), n.to_json()))
138 .collect(),
139 ),
140 }
141 }
142}
143
144pub fn json_depth_exceeds(value: &serde_json::Value, max_depth: usize) -> bool {
169 use serde_json::Value;
170 let mut stack: Vec<(&Value, usize)> = vec![(value, 0)];
172 while let Some((v, depth)) = stack.pop() {
173 match v {
174 Value::Array(items) => {
175 if depth + 1 > max_depth {
176 return true;
177 }
178 stack.extend(items.iter().map(|c| (c, depth + 1)));
179 }
180 Value::Object(map) => {
181 if depth + 1 > max_depth {
182 return true;
183 }
184 stack.extend(map.values().map(|c| (c, depth + 1)));
185 }
186 _ => {}
187 }
188 }
189 false
190}
191
192pub(crate) fn depth_check_meta_map<E>(
199 map: serde_json::Map<String, serde_json::Value>,
200 on_too_deep: impl FnOnce(usize) -> E,
201) -> Result<serde_json::Map<String, serde_json::Value>, E> {
202 let max = crate::document::limits::MAX_YAML_DEPTH;
203 let as_value = serde_json::Value::Object(map);
204 if json_depth_exceeds(&as_value, max) {
205 return Err(on_too_deep(max));
206 }
207 let serde_json::Value::Object(map) = as_value else {
208 unreachable!("constructed as Object above")
209 };
210 Ok(map)
211}
212
213impl QuillValue {
214 fn from_node(node: Node) -> Self {
215 QuillValue {
216 node,
217 json: OnceLock::new(),
218 }
219 }
220
221 pub fn from_yaml_str(yaml_str: &str) -> Result<Self, crate::error::YamlError> {
227 let json_val: serde_json::Value = serde_saphyr::from_str_with_options(
228 yaml_str,
229 crate::document::limits::yaml_parse_options(),
230 )
231 .map_err(|e| crate::error::YamlError::from_de(e, yaml_str))?;
232 Ok(Self::from_json(json_val))
233 }
234
235 pub fn as_json(&self) -> &serde_json::Value {
240 self.json.get_or_init(|| self.node.to_json())
241 }
242
243 pub fn into_json(self) -> serde_json::Value {
245 match self.json.into_inner() {
246 Some(json) => json,
247 None => self.node.to_json(),
248 }
249 }
250
251 pub fn from_json(json_val: serde_json::Value) -> Self {
253 let node = Node::from_json(&json_val);
254 let json = OnceLock::new();
255 let _ = json.set(json_val);
262 QuillValue { node, json }
263 }
264
265 pub fn string(s: impl Into<String>) -> Self {
267 Self::from_json(serde_json::Value::String(s.into()))
268 }
269
270 pub fn integer(n: i64) -> Self {
272 Self::from_json(serde_json::Value::Number(n.into()))
273 }
274
275 pub fn bool(b: bool) -> Self {
277 Self::from_json(serde_json::Value::Bool(b))
278 }
279
280 pub fn null() -> Self {
282 Self::from_json(serde_json::Value::Null)
283 }
284
285 pub fn fill(&self) -> bool {
287 self.node.fill
288 }
289
290 pub fn fill_paths(&self) -> Vec<Vec<PathSegment>> {
295 let mut out = Vec::new();
296 let mut prefix = Vec::new();
297 collect_fill_paths(&self.node, &mut prefix, &mut out);
298 out
299 }
300
301 pub fn nonroot_fill_paths(&self) -> impl Iterator<Item = Vec<PathSegment>> {
306 self.fill_paths().into_iter().filter(|p| !p.is_empty())
307 }
308
309 pub fn set_fill_at(&mut self, path: &[PathSegment]) -> bool {
312 match node_at_mut(&mut self.node, path) {
313 Some(n) => {
314 n.fill = true;
315 true
316 }
317 None => false,
318 }
319 }
320
321 pub fn is_object_at(&self, path: &[PathSegment]) -> bool {
324 node_is_object(&self.node, path)
325 }
326}
327
328macro_rules! impl_from_scalar {
333 ($($ty:ty),* $(,)?) => {$(
334 impl From<$ty> for QuillValue {
335 fn from(v: $ty) -> Self {
336 QuillValue::from_json(serde_json::Value::from(v))
337 }
338 }
339 )*};
340}
341impl_from_scalar!(&str, String, bool, i32, i64, u32, u64, f64);
342
343impl From<serde_json::Value> for QuillValue {
344 fn from(v: serde_json::Value) -> Self {
345 QuillValue::from_json(v)
346 }
347}
348
349impl Deref for QuillValue {
350 type Target = serde_json::Value;
351
352 fn deref(&self) -> &Self::Target {
353 self.as_json()
354 }
355}
356
357impl PartialEq for QuillValue {
358 fn eq(&self, other: &Self) -> bool {
361 self.node == other.node
362 }
363}
364
365impl Clone for QuillValue {
366 fn clone(&self) -> Self {
367 let json = OnceLock::new();
368 if let Some(cached) = self.json.get() {
369 let _ = json.set(cached.clone());
370 }
371 QuillValue {
372 node: self.node.clone(),
373 json,
374 }
375 }
376}
377
378impl std::fmt::Debug for QuillValue {
379 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
380 if self.node.fill {
381 write!(f, "QuillValue(!must_fill {:?})", self.as_json())
382 } else {
383 write!(f, "QuillValue({:?})", self.as_json())
384 }
385 }
386}
387
388impl Serialize for QuillValue {
389 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
390 self.as_json().serialize(serializer)
391 }
392}
393
394impl<'de> Deserialize<'de> for QuillValue {
395 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
396 let json = serde_json::Value::deserialize(deserializer)?;
397 Ok(QuillValue::from_json(json))
398 }
399}
400
401impl QuillValue {
402 pub fn get(&self, key: &str) -> Option<QuillValue> {
410 match &self.node.kind {
411 Kind::Object(entries) => entries.get(key).map(|n| QuillValue::from_node(n.clone())),
412 _ => None,
413 }
414 }
415}
416
417#[cfg(test)]
418mod tests {
419 use super::*;
420
421 #[test]
422 fn test_from_yaml_str() {
423 let yaml_str = r#"
424 title: Test Document
425 author: John Doe
426 count: 42
427 "#;
428 let quill_val = QuillValue::from_yaml_str(yaml_str).unwrap();
429
430 assert_eq!(
431 quill_val.get("title").as_ref().and_then(|v| v.as_str()),
432 Some("Test Document")
433 );
434 assert_eq!(
435 quill_val.get("author").as_ref().and_then(|v| v.as_str()),
436 Some("John Doe")
437 );
438 assert_eq!(
439 quill_val.get("count").as_ref().and_then(|v| v.as_i64()),
440 Some(42)
441 );
442 }
443
444 #[test]
445 fn from_yaml_str_carries_the_shared_depth_budget() {
446 let max = crate::document::limits::MAX_YAML_DEPTH;
451 let nest = |levels: usize| {
452 let mut yaml = String::new();
453 for i in 0..levels {
454 yaml.push_str(&" ".repeat(i));
455 yaml.push_str("nest:\n");
456 }
457 yaml.push_str(&" ".repeat(levels));
458 yaml.push_str("leaf: 1\n");
459 yaml
460 };
461
462 assert!(QuillValue::from_yaml_str(&nest(max - 1)).is_ok());
463
464 let err = QuillValue::from_yaml_str(&nest(max + 8))
465 .expect_err("over-deep YAML must be refused, not recursed");
466 let msg = err.to_string().to_lowercase();
467 assert!(
468 msg.contains("depth") || msg.contains("budget") || msg.contains("limit"),
469 "error should name the depth budget, got: {err}"
470 );
471 }
472
473 #[test]
474 fn yaml_error_locates_and_sanitizes() {
475 let err = QuillValue::from_yaml_str("a: 1\nb: [unclosed\n")
476 .expect_err("malformed YAML must not parse");
477 let (line, column) = (
478 err.line().expect("the engine locates a parse failure"),
479 err.column().expect("column pairs with line"),
480 );
481 let diag = err.to_diagnostic("quill::yaml_parse_error", "Quill.yaml");
482 let loc = diag.location.expect("a located error carries a Location");
483 assert_eq!((loc.line, loc.column, loc.file.as_str()), (line, column, "Quill.yaml"));
484 assert_eq!(diag.code.as_deref(), Some("quill::yaml_parse_error"));
485 }
486
487 #[test]
490 fn yaml_error_strips_the_engine_api_names() {
491 let err = QuillValue::from_yaml_str("a: 1\na: 2\n")
492 .expect_err("a duplicate key must not parse");
493 assert!(
494 !err.message().contains("DuplicateKeyPolicy") && !err.message().contains("Options"),
495 "engine API names reached the message: {}",
496 err.message()
497 );
498 assert!(err.message().contains("duplicate"), "{}", err.message());
499 }
500
501 #[test]
502 fn test_yaml_custom_tags_ignored_at_value_level() {
503 let yaml_str = "memo_from: !must_fill 2d lt example";
509 let quill_val = QuillValue::from_yaml_str(yaml_str).unwrap();
510
511 assert_eq!(
512 quill_val.get("memo_from").as_ref().and_then(|v| v.as_str()),
513 Some("2d lt example")
514 );
515 }
516
517 #[test]
518 fn json_round_trips_through_the_tree() {
519 let original = serde_json::json!({
522 "z": 1,
523 "a": [true, "x", 3.5, null],
524 "nested": { "k": 42 }
525 });
526 let qv = QuillValue::from_json(original.clone());
527 assert_eq!(qv.as_json(), &original);
528
529 let relowered = QuillValue::from_node(qv.node.clone()).into_json();
531 assert_eq!(relowered, original);
532 }
533
534 #[test]
535 fn depth_check_counts_empty_containers() {
536 use serde_json::json;
537
538 assert!(!json_depth_exceeds(&json!([]), 1));
543 assert!(!json_depth_exceeds(&json!({}), 1));
544 assert!(json_depth_exceeds(&json!([[]]), 1));
545 assert!(json_depth_exceeds(&json!({ "a": {} }), 1));
546
547 let deep_empty = |levels: usize| {
551 let mut v = serde_json::Value::Array(Vec::new());
552 for _ in 1..levels {
553 v = serde_json::Value::Array(vec![v]);
554 }
555 v
556 };
557 assert!(!json_depth_exceeds(&deep_empty(100), 100));
560 assert!(json_depth_exceeds(&deep_empty(101), 100));
561 }
562
563 #[test]
564 fn depth_check_counts_container_levels_not_the_scalar_leaf() {
565 let scalar_terminated = |levels: usize| {
572 let mut v = serde_json::json!(1);
573 for _ in 0..levels {
574 v = serde_json::json!({ "a": v });
575 }
576 v
577 };
578 assert!(!json_depth_exceeds(&scalar_terminated(100), 100));
579 assert!(json_depth_exceeds(&scalar_terminated(101), 100));
580
581 let container_terminated = |levels: usize| {
584 let mut v = serde_json::json!([1, 2, 3]);
585 for _ in 1..levels {
586 v = serde_json::json!({ "a": v });
587 }
588 v
589 };
590 assert!(!json_depth_exceeds(&container_terminated(100), 100));
591 assert!(json_depth_exceeds(&container_terminated(101), 100));
592 }
593
594 #[test]
595 fn fill_marker_rides_on_the_node_not_the_json() {
596 let filled = || {
597 let mut qv = QuillValue::string("draft");
598 assert!(qv.set_fill_at(&[]));
599 qv
600 };
601 let qv = filled();
602 assert!(qv.fill());
603 assert_eq!(qv.as_json(), &serde_json::json!("draft"));
605 assert_ne!(qv, QuillValue::string("draft"));
607 assert_eq!(qv, filled());
608 }
609}