use serde_json::Value;
use super::model::{PatternProp, QuartoSchema, Roots, SchemaNode};
pub fn default_roots() -> Roots {
Roots {
frontmatter: "front-matter".to_string(),
project: "project-config".to_string(),
cell_knitr: Some("engine-knitr".to_string()),
cell_jupyter: Some("engine-jupyter".to_string()),
}
}
pub fn distill(raw: &Value, version: &str, roots: Roots) -> QuartoSchema {
let defs = raw
.as_object()
.map(|obj| {
obj.iter()
.map(|(id, node)| (id.clone(), normalize(node)))
.collect()
})
.unwrap_or_default();
QuartoSchema {
version: version.to_string(),
roots,
defs,
}
}
fn normalize(v: &Value) -> SchemaNode {
let Some(obj) = v.as_object() else {
return SchemaNode::Any;
};
let ty = obj.get("type").and_then(Value::as_str).unwrap_or("");
match ty {
"string" => SchemaNode::String,
"number" => SchemaNode::Number,
"boolean" => SchemaNode::Boolean,
"null" => SchemaNode::Null,
"enum" => SchemaNode::Enum {
values: obj
.get("enum")
.and_then(Value::as_array)
.cloned()
.unwrap_or_default(),
},
"array" => SchemaNode::Array {
items: obj.get("items").map(|i| Box::new(normalize(i))),
},
"ref" => SchemaNode::Ref {
id: obj
.get("$ref")
.and_then(Value::as_str)
.unwrap_or_default()
.to_string(),
},
"anyOf" => SchemaNode::AnyOf {
of: normalize_list(obj.get("anyOf")),
},
"allOf" => SchemaNode::AllOf {
of: normalize_list(obj.get("allOf")),
},
"object" => SchemaNode::Object {
properties: obj
.get("properties")
.and_then(Value::as_object)
.map(|props| {
props
.iter()
.map(|(k, v)| (k.clone(), normalize(v)))
.collect()
})
.unwrap_or_default(),
closed: is_closed(obj),
pattern: obj
.get("patternProperties")
.and_then(Value::as_object)
.map(|pats| {
pats.iter()
.map(|(re, v)| PatternProp {
re: re.clone(),
schema: Box::new(normalize(v)),
})
.collect()
})
.unwrap_or_default(),
},
_ => SchemaNode::Any,
}
}
fn normalize_list(v: Option<&Value>) -> Vec<SchemaNode> {
v.and_then(Value::as_array)
.map(|arr| arr.iter().map(normalize).collect())
.unwrap_or_default()
}
fn is_closed(obj: &serde_json::Map<String, Value>) -> bool {
obj.get("closed").and_then(Value::as_bool).unwrap_or(false)
|| obj.get("additionalProperties") == Some(&Value::Bool(false))
}