use indexmap::IndexMap;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value as JsonValue;
use std::ops::Deref;
use std::sync::OnceLock;
pub struct QuillValue {
node: Node,
json: OnceLock<JsonValue>,
}
#[derive(Debug, Clone, PartialEq)]
struct Node {
fill: bool,
kind: Kind,
}
#[derive(Debug, Clone, PartialEq)]
enum Kind {
Null,
Bool(bool),
Number(serde_json::Number),
String(String),
Array(Vec<Node>),
Object(IndexMap<String, Node>),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum PathSegment {
Key(String),
Index(usize),
}
fn collect_fill_paths(node: &Node, prefix: &mut Vec<PathSegment>, out: &mut Vec<Vec<PathSegment>>) {
if node.fill {
out.push(prefix.clone());
}
match &node.kind {
Kind::Array(items) => {
for (i, child) in items.iter().enumerate() {
prefix.push(PathSegment::Index(i));
collect_fill_paths(child, prefix, out);
prefix.pop();
}
}
Kind::Object(entries) => {
for (k, child) in entries {
prefix.push(PathSegment::Key(k.clone()));
collect_fill_paths(child, prefix, out);
prefix.pop();
}
}
_ => {}
}
}
fn node_at_mut<'a>(node: &'a mut Node, path: &[PathSegment]) -> Option<&'a mut Node> {
let mut cur = node;
for seg in path {
cur = match (&mut cur.kind, seg) {
(Kind::Object(entries), PathSegment::Key(k)) => entries.get_mut(k)?,
(Kind::Array(items), PathSegment::Index(i)) => items.get_mut(*i)?,
_ => return None,
};
}
Some(cur)
}
fn node_is_object(node: &Node, path: &[PathSegment]) -> bool {
fn at<'a>(node: &'a Node, path: &[PathSegment]) -> Option<&'a Node> {
let mut cur = node;
for seg in path {
cur = match (&cur.kind, seg) {
(Kind::Object(entries), PathSegment::Key(k)) => entries.get(k)?,
(Kind::Array(items), PathSegment::Index(i)) => items.get(*i)?,
_ => return None,
};
}
Some(cur)
}
matches!(at(node, path).map(|n| &n.kind), Some(Kind::Object(_)))
}
impl Node {
fn from_json(value: &JsonValue) -> Node {
let kind = match value {
JsonValue::Null => Kind::Null,
JsonValue::Bool(b) => Kind::Bool(*b),
JsonValue::Number(n) => Kind::Number(n.clone()),
JsonValue::String(s) => Kind::String(s.clone()),
JsonValue::Array(items) => Kind::Array(items.iter().map(Node::from_json).collect()),
JsonValue::Object(map) => Kind::Object(
map.iter()
.map(|(k, v)| (k.clone(), Node::from_json(v)))
.collect(),
),
};
Node { fill: false, kind }
}
fn to_json(&self) -> JsonValue {
match &self.kind {
Kind::Null => JsonValue::Null,
Kind::Bool(b) => JsonValue::Bool(*b),
Kind::Number(n) => JsonValue::Number(n.clone()),
Kind::String(s) => JsonValue::String(s.clone()),
Kind::Array(items) => JsonValue::Array(items.iter().map(Node::to_json).collect()),
Kind::Object(entries) => JsonValue::Object(
entries
.iter()
.map(|(k, n)| (k.clone(), n.to_json()))
.collect(),
),
}
}
}
pub fn json_depth_exceeds(value: &serde_json::Value, max_depth: usize) -> bool {
use serde_json::Value;
let mut stack: Vec<(&Value, usize)> = vec![(value, 0)];
while let Some((v, depth)) = stack.pop() {
match v {
Value::Array(items) => {
if depth + 1 > max_depth && !items.is_empty() {
return true;
}
stack.extend(items.iter().map(|c| (c, depth + 1)));
}
Value::Object(map) => {
if depth + 1 > max_depth && !map.is_empty() {
return true;
}
stack.extend(map.values().map(|c| (c, depth + 1)));
}
_ => {}
}
}
false
}
impl QuillValue {
fn from_node(node: Node) -> Self {
QuillValue {
node,
json: OnceLock::new(),
}
}
pub fn from_yaml_str(yaml_str: &str) -> Result<Self, serde_saphyr::Error> {
let json_val: serde_json::Value = serde_saphyr::from_str(yaml_str)?;
Ok(Self::from_json(json_val))
}
pub fn as_json(&self) -> &serde_json::Value {
self.json.get_or_init(|| self.node.to_json())
}
pub fn into_json(self) -> serde_json::Value {
match self.json.into_inner() {
Some(json) => json,
None => self.node.to_json(),
}
}
pub fn from_json(json_val: serde_json::Value) -> Self {
let node = Node::from_json(&json_val);
let json = OnceLock::new();
let _ = json.set(json_val);
QuillValue { node, json }
}
pub fn string(s: impl Into<String>) -> Self {
Self::from_json(serde_json::Value::String(s.into()))
}
pub fn integer(n: i64) -> Self {
Self::from_json(serde_json::Value::Number(n.into()))
}
pub fn bool(b: bool) -> Self {
Self::from_json(serde_json::Value::Bool(b))
}
pub fn null() -> Self {
Self::from_json(serde_json::Value::Null)
}
pub fn fill(&self) -> bool {
self.node.fill
}
pub fn with_fill(mut self, fill: bool) -> Self {
self.node.fill = fill;
self
}
pub fn set_fill(&mut self, fill: bool) {
self.node.fill = fill;
}
pub fn fill_paths(&self) -> Vec<Vec<PathSegment>> {
let mut out = Vec::new();
let mut prefix = Vec::new();
collect_fill_paths(&self.node, &mut prefix, &mut out);
out
}
pub fn nonroot_fill_paths(&self) -> impl Iterator<Item = Vec<PathSegment>> {
self.fill_paths().into_iter().filter(|p| !p.is_empty())
}
pub fn set_fill_at(&mut self, path: &[PathSegment]) -> bool {
match node_at_mut(&mut self.node, path) {
Some(n) => {
n.fill = true;
true
}
None => false,
}
}
pub fn is_object_at(&self, path: &[PathSegment]) -> bool {
node_is_object(&self.node, path)
}
}
impl Deref for QuillValue {
type Target = serde_json::Value;
fn deref(&self) -> &Self::Target {
self.as_json()
}
}
impl PartialEq for QuillValue {
fn eq(&self, other: &Self) -> bool {
self.node == other.node
}
}
impl Clone for QuillValue {
fn clone(&self) -> Self {
let json = OnceLock::new();
if let Some(cached) = self.json.get() {
let _ = json.set(cached.clone());
}
QuillValue {
node: self.node.clone(),
json,
}
}
}
impl std::fmt::Debug for QuillValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.node.fill {
write!(f, "QuillValue(!must_fill {:?})", self.as_json())
} else {
write!(f, "QuillValue({:?})", self.as_json())
}
}
}
impl Serialize for QuillValue {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.as_json().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for QuillValue {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let json = serde_json::Value::deserialize(deserializer)?;
Ok(QuillValue::from_json(json))
}
}
impl QuillValue {
pub fn is_null(&self) -> bool {
self.as_json().is_null()
}
pub fn as_str(&self) -> Option<&str> {
self.as_json().as_str()
}
pub fn as_bool(&self) -> Option<bool> {
self.as_json().as_bool()
}
pub fn as_i64(&self) -> Option<i64> {
self.as_json().as_i64()
}
pub fn as_u64(&self) -> Option<u64> {
self.as_json().as_u64()
}
pub fn as_f64(&self) -> Option<f64> {
self.as_json().as_f64()
}
pub fn as_array(&self) -> Option<&Vec<serde_json::Value>> {
self.as_json().as_array()
}
pub fn as_object(&self) -> Option<&serde_json::Map<String, serde_json::Value>> {
self.as_json().as_object()
}
pub fn get(&self, key: &str) -> Option<QuillValue> {
match &self.node.kind {
Kind::Object(entries) => entries.get(key).map(|n| QuillValue::from_node(n.clone())),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_yaml_value() {
let yaml_str = r#"
package:
name: test
version: 1.0.0
"#;
let json_val: serde_json::Value = serde_saphyr::from_str(yaml_str).unwrap();
let quill_val = QuillValue::from_json(json_val);
assert!(quill_val.as_object().is_some());
assert_eq!(
quill_val
.get("package")
.unwrap()
.get("name")
.unwrap()
.as_str(),
Some("test")
);
}
#[test]
fn test_from_yaml_str() {
let yaml_str = r#"
title: Test Document
author: John Doe
count: 42
"#;
let quill_val = QuillValue::from_yaml_str(yaml_str).unwrap();
assert_eq!(
quill_val.get("title").as_ref().and_then(|v| v.as_str()),
Some("Test Document")
);
assert_eq!(
quill_val.get("author").as_ref().and_then(|v| v.as_str()),
Some("John Doe")
);
assert_eq!(
quill_val.get("count").as_ref().and_then(|v| v.as_i64()),
Some(42)
);
}
#[test]
fn test_delegating_methods() {
let quill_val = QuillValue::from_json(serde_json::json!({
"name": "test",
"count": 42,
"active": true,
"items": [1, 2, 3]
}));
assert_eq!(
quill_val.get("name").as_ref().and_then(|v| v.as_str()),
Some("test")
);
assert_eq!(
quill_val.get("count").as_ref().and_then(|v| v.as_i64()),
Some(42)
);
assert_eq!(
quill_val.get("active").as_ref().and_then(|v| v.as_bool()),
Some(true)
);
assert!(quill_val
.get("items")
.as_ref()
.and_then(|v| v.as_array())
.is_some());
}
#[test]
fn test_yaml_custom_tags_ignored_at_value_level() {
let yaml_str = "memo_from: !must_fill 2d lt example";
let quill_val = QuillValue::from_yaml_str(yaml_str).unwrap();
assert_eq!(
quill_val.get("memo_from").as_ref().and_then(|v| v.as_str()),
Some("2d lt example")
);
}
#[test]
fn json_round_trips_through_the_tree() {
let original = serde_json::json!({
"z": 1,
"a": [true, "x", 3.5, null],
"nested": { "k": 42 }
});
let qv = QuillValue::from_json(original.clone());
assert_eq!(qv.as_json(), &original);
let relowered = QuillValue::from_node(qv.node.clone()).into_json();
assert_eq!(relowered, original);
}
#[test]
fn fill_marker_rides_on_the_node_not_the_json() {
let qv = QuillValue::string("draft").with_fill(true);
assert!(qv.fill());
assert_eq!(qv.as_json(), &serde_json::json!("draft"));
assert_ne!(qv, QuillValue::string("draft"));
assert_eq!(qv, QuillValue::string("draft").with_fill(true));
}
}