use parking_lot::RwLock;
use serde_json::{json, Value};
use std::collections::HashMap;
use std::sync::Arc;
use crate::error::SchemaErrors;
use crate::path::JsonPath;
use crate::schema::ValueValidator;
use crate::validation::{RegistryAccess, ValidationContext};
use stillwater::Validation;
type SchemaMap = Arc<RwLock<HashMap<String, Arc<dyn ValueValidator>>>>;
pub struct SchemaRegistry {
schemas: SchemaMap,
max_depth: usize,
}
impl SchemaRegistry {
pub fn new() -> Self {
Self {
schemas: Arc::new(RwLock::new(HashMap::new())),
max_depth: 100,
}
}
pub fn with_max_depth(mut self, depth: usize) -> Self {
self.max_depth = depth;
self
}
pub fn register<S>(&self, name: impl Into<String>, schema: S) -> Result<(), RegistryError>
where
S: ValueValidator + 'static,
{
let name = name.into();
let mut schemas = self.schemas.write();
if schemas.contains_key(&name) {
return Err(RegistryError::DuplicateName(name));
}
schemas.insert(name, Arc::new(schema));
Ok(())
}
pub fn get(&self, name: &str) -> Option<Arc<dyn ValueValidator>> {
self.schemas.read().get(name).cloned()
}
pub fn validate_refs(&self) -> Vec<String> {
let schemas = self.schemas.read();
let mut all_refs = Vec::new();
for schema in schemas.values() {
schema.collect_refs(&mut all_refs);
}
let mut unresolved = Vec::new();
for ref_name in all_refs {
if !schemas.contains_key(&ref_name) {
unresolved.push(ref_name);
}
}
unresolved.sort();
unresolved.dedup();
unresolved
}
pub fn validate(
&self,
schema_name: &str,
value: &Value,
) -> Result<Validation<Value, SchemaErrors>, RegistryError> {
let schema = self
.get(schema_name)
.ok_or_else(|| RegistryError::SchemaNotFound(schema_name.to_string()))?;
let context = ValidationContext::new(Arc::new(self.clone()), self.max_depth);
Ok(schema.validate_value_with_context(value, &JsonPath::root(), &context))
}
pub fn to_json_schema(&self) -> Value {
let schemas = self.schemas.read();
let mut defs = serde_json::Map::new();
for (name, schema) in schemas.iter() {
defs.insert(name.clone(), schema.to_json_schema());
}
json!({
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$defs": defs
})
}
pub fn export_schema(&self, name: &str) -> Option<Value> {
let schema = self.get(name)?;
let base = self.to_json_schema();
let mut result = schema.to_json_schema();
result["$schema"] = json!("https://json-schema.org/draft/2020-12/schema");
result["$defs"] = base["$defs"].clone();
Some(result)
}
}
impl Default for SchemaRegistry {
fn default() -> Self {
Self::new()
}
}
impl Clone for SchemaRegistry {
fn clone(&self) -> Self {
Self {
schemas: Arc::clone(&self.schemas),
max_depth: self.max_depth,
}
}
}
impl RegistryAccess for SchemaRegistry {
fn get_schema(&self, name: &str) -> Option<Arc<dyn ValueValidator>> {
self.get(name)
}
}
#[derive(Debug, thiserror::Error)]
pub enum RegistryError {
#[error("schema '{0}' already registered")]
DuplicateName(String),
#[error("schema '{0}' not found")]
SchemaNotFound(String),
}