use std::sync::OnceLock;
use serde_json::Value;
const SCHEMA_JSON: &str = include_str!("input_diagram_schema.json");
pub struct DiagramSchema {
root: Value,
}
impl DiagramSchema {
pub fn get() -> &'static DiagramSchema {
static SCHEMA: OnceLock<DiagramSchema> = OnceLock::new();
SCHEMA.get_or_init(|| {
let root = serde_json::from_str(SCHEMA_JSON)
.expect("`input_diagram_schema.json` is not valid JSON.");
DiagramSchema { root }
})
}
pub fn root(&self) -> &Value {
&self.root
}
pub fn deref<'schema>(&'schema self, node: &'schema Value) -> &'schema Value {
let mut node = node;
while let Some(ref_name) = Self::ref_name(node) {
match self.def(ref_name) {
Some(def) => node = def,
None => break,
}
}
node
}
pub fn def(&self, name: &str) -> Option<&Value> {
self.root.get("$defs")?.get(name)
}
pub fn ref_name(node: &Value) -> Option<&str> {
node.get("$ref")?.as_str()?.strip_prefix("#/$defs/")
}
pub fn schema_at(&self, path: &[String]) -> Option<&Value> {
let mut node = &self.root;
for key in path {
let container = self.deref(node);
node = self.field_schema(container, key)?;
}
Some(node)
}
pub fn field_schema<'schema>(
&self,
container: &'schema Value,
key: &str,
) -> Option<&'schema Value> {
if let Some(field) = container
.get("properties")
.and_then(|properties| properties.get(key))
{
return Some(field);
}
container
.get("additionalProperties")
.filter(|additional_properties| additional_properties.is_object())
}
pub fn property_entries<'schema>(
&'schema self,
node: &'schema Value,
) -> Vec<PropertyEntry<'schema>> {
let Some(properties) = self
.deref(node)
.get("properties")
.and_then(Value::as_object)
else {
return Vec::new();
};
properties
.iter()
.map(|(name, schema)| PropertyEntry {
name,
description: schema.get("description").and_then(Value::as_str),
})
.collect()
}
pub fn enum_entries<'schema>(&'schema self, node: &'schema Value) -> Vec<EnumEntry<'schema>> {
let node = self.deref(node);
let Some(one_of) = node.get("oneOf").and_then(Value::as_array) else {
return Vec::new();
};
one_of
.iter()
.filter_map(|variant| {
let value = variant.get("const")?.as_str()?;
Some(EnumEntry {
value,
description: variant.get("description").and_then(Value::as_str),
})
})
.collect()
}
pub fn array_items<'schema>(&'schema self, node: &'schema Value) -> Option<&'schema Value> {
let node = self.deref(node);
if node.get("type").and_then(Value::as_str) == Some("array") {
node.get("items")
} else {
None
}
}
}
pub struct PropertyEntry<'schema> {
pub name: &'schema str,
pub description: Option<&'schema str>,
}
pub struct EnumEntry<'schema> {
pub value: &'schema str,
pub description: Option<&'schema str>,
}