use std::fmt;
use std::sync::Arc;
use std::sync::LazyLock;
use ferrin_spec::error::TypeValidationError;
use schemars::JsonSchema;
use serde::de::DeserializeOwned;
use serde_json::Value;
use serde_json::json;
use crate::dialect::SchemaDialect;
use crate::transform::SchemaTransform;
type LazyValue = LazyLock<Value, Box<dyn FnOnce() -> Value + Send>>;
type Validate<T> = dyn Fn(Value) -> Result<T, TypeValidationError> + Send + Sync;
pub struct Schema<T> {
json_schema: Arc<LazyValue>,
validate: Arc<Validate<T>>,
}
impl<T> Clone for Schema<T> {
fn clone(&self) -> Self {
Self {
json_schema: Arc::clone(&self.json_schema),
validate: Arc::clone(&self.validate),
}
}
}
impl<T> fmt::Debug for Schema<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Schema")
.field("json_schema", &LazyLock::get(&self.json_schema))
.finish_non_exhaustive()
}
}
impl<T: DeserializeOwned + JsonSchema + 'static> Schema<T> {
#[must_use]
pub fn derived() -> Self {
Self::derived_with(SchemaDialect::default())
}
#[must_use]
pub fn derived_with(dialect: SchemaDialect) -> Self {
Self::lazy(
move || {
let mut schema = dialect.generate::<T>();
crate::transform::add_additional_properties_false(&mut schema);
schema
},
deserialize_into::<T>,
)
}
}
impl<T: DeserializeOwned + 'static> Schema<T> {
#[must_use]
pub fn typed_from_json_schema(schema: Value) -> Self {
let dynamic = Schema::<Value>::from_json_schema(schema);
let dynamic_validate = Arc::clone(&dynamic.validate);
Self {
json_schema: dynamic.json_schema,
validate: Arc::new(move |value| {
let value = dynamic_validate(value)?;
deserialize_into::<T>(value)
}),
}
}
}
impl Schema<Value> {
#[must_use]
pub fn from_json_schema(schema: Value) -> Self {
let json_schema = Arc::new(LazyValue::new(Box::new(move || schema)));
let validate = dynamic_validator(Arc::clone(&json_schema));
Self {
json_schema,
validate,
}
}
#[must_use]
pub fn empty_object() -> Self {
Self::from_json_schema(json!({
"type": "object",
"properties": {},
"additionalProperties": false,
}))
}
#[must_use]
pub fn any() -> Self {
Self::from_json_schema(json!({}))
}
}
impl<T: 'static> Schema<T> {
pub fn lazy(
json_schema: impl FnOnce() -> Value + Send + 'static,
validate: impl Fn(Value) -> Result<T, TypeValidationError> + Send + Sync + 'static,
) -> Self {
Self {
json_schema: Arc::new(LazyValue::new(Box::new(json_schema))),
validate: Arc::new(validate),
}
}
pub fn with_json_schema_and_validator(
json_schema: Value,
validate: impl Fn(Value) -> Result<T, TypeValidationError> + Send + Sync + 'static,
) -> Self {
Self::lazy(move || json_schema, validate)
}
#[must_use]
pub fn with_validator(
self,
validate: impl Fn(Value) -> Result<T, TypeValidationError> + Send + Sync + 'static,
) -> Self {
Self {
json_schema: self.json_schema,
validate: Arc::new(validate),
}
}
pub fn transformed(&self, transform: SchemaTransform) -> Result<Self, crate::SchemaError> {
let transformed = transform.applied(self.json_schema().clone())?;
Ok(Self {
json_schema: Arc::new(LazyValue::new(Box::new(move || transformed))),
validate: Arc::clone(&self.validate),
})
}
#[must_use]
pub fn erased(&self) -> Schema<Value> {
let validate = Arc::clone(&self.validate);
Schema {
json_schema: Arc::clone(&self.json_schema),
validate: Arc::new(move |value| validate(value.clone()).map(|_| value)),
}
}
}
impl<T> Schema<T> {
#[must_use]
pub fn json_schema(&self) -> &Value {
&self.json_schema
}
pub fn validate(&self, value: Value) -> Result<T, TypeValidationError> {
(self.validate)(value)
}
}
fn deserialize_into<T: DeserializeOwned>(value: Value) -> Result<T, TypeValidationError> {
match serde_json::from_value::<T>(value.clone()) {
Ok(typed) => Ok(typed),
Err(error) => Err(TypeValidationError::new(value, error)),
}
}
#[cfg(feature = "json-schema-validation")]
fn dynamic_validator(json_schema: Arc<LazyValue>) -> Arc<Validate<Value>> {
use std::sync::OnceLock;
use crate::validation::ValidationIssues;
use crate::validation::Validator;
let compiled: OnceLock<Result<Validator, String>> = OnceLock::new();
Arc::new(move |value| {
let validator = compiled
.get_or_init(|| Validator::compile(&json_schema).map_err(|error| error.to_string()));
match validator {
Ok(validator) => validator
.validate(&value)
.map(|()| value.clone())
.map_err(|issues| TypeValidationError::new(value, issues)),
Err(message) => Err(TypeValidationError::new(
value,
ValidationIssues::message(message.clone()),
)),
}
})
}
#[cfg(not(feature = "json-schema-validation"))]
fn dynamic_validator(_json_schema: Arc<LazyValue>) -> Arc<Validate<Value>> {
Arc::new(Ok)
}