1use std::fmt;
4use std::sync::Arc;
5use std::sync::LazyLock;
6
7use ferrin_spec::error::TypeValidationError;
8use schemars::JsonSchema;
9use serde::de::DeserializeOwned;
10use serde_json::Value;
11use serde_json::json;
12
13use crate::dialect::SchemaDialect;
14use crate::transform::SchemaTransform;
15
16type LazyValue = LazyLock<Value, Box<dyn FnOnce() -> Value + Send>>;
17type Validate<T> = dyn Fn(Value) -> Result<T, TypeValidationError> + Send + Sync;
18
19pub struct Schema<T> {
25 json_schema: Arc<LazyValue>,
26 validate: Arc<Validate<T>>,
27}
28
29impl<T> Clone for Schema<T> {
30 fn clone(&self) -> Self {
31 Self {
32 json_schema: Arc::clone(&self.json_schema),
33 validate: Arc::clone(&self.validate),
34 }
35 }
36}
37
38impl<T> fmt::Debug for Schema<T> {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 f.debug_struct("Schema")
41 .field("json_schema", &LazyLock::get(&self.json_schema))
42 .finish_non_exhaustive()
43 }
44}
45
46impl<T: DeserializeOwned + JsonSchema + 'static> Schema<T> {
47 #[must_use]
50 pub fn derived() -> Self {
51 Self::derived_with(SchemaDialect::default())
52 }
53
54 #[must_use]
56 pub fn derived_with(dialect: SchemaDialect) -> Self {
57 Self::lazy(
58 move || SchemaTransform::AdditionalPropertiesFalse.applied(dialect.generate::<T>()),
59 deserialize_into::<T>,
60 )
61 }
62}
63
64impl<T: DeserializeOwned + 'static> Schema<T> {
65 #[must_use]
71 pub fn typed_from_json_schema(schema: Value) -> Self {
72 let dynamic = Schema::<Value>::from_json_schema(schema);
73 let dynamic_validate = Arc::clone(&dynamic.validate);
74 Self {
75 json_schema: dynamic.json_schema,
76 validate: Arc::new(move |value| {
77 let value = dynamic_validate(value)?;
78 deserialize_into::<T>(value)
79 }),
80 }
81 }
82}
83
84impl Schema<Value> {
85 #[must_use]
92 pub fn from_json_schema(schema: Value) -> Self {
93 let json_schema = Arc::new(LazyValue::new(Box::new(move || schema)));
94 let validate = dynamic_validator(Arc::clone(&json_schema));
95 Self {
96 json_schema,
97 validate,
98 }
99 }
100
101 #[must_use]
104 pub fn empty_object() -> Self {
105 Self::from_json_schema(json!({
106 "type": "object",
107 "properties": {},
108 "additionalProperties": false,
109 }))
110 }
111
112 #[must_use]
114 pub fn any() -> Self {
115 Self::from_json_schema(json!({}))
116 }
117}
118
119impl<T: 'static> Schema<T> {
120 pub fn lazy(
122 json_schema: impl FnOnce() -> Value + Send + 'static,
123 validate: impl Fn(Value) -> Result<T, TypeValidationError> + Send + Sync + 'static,
124 ) -> Self {
125 Self {
126 json_schema: Arc::new(LazyValue::new(Box::new(json_schema))),
127 validate: Arc::new(validate),
128 }
129 }
130
131 pub fn with_json_schema_and_validator(
133 json_schema: Value,
134 validate: impl Fn(Value) -> Result<T, TypeValidationError> + Send + Sync + 'static,
135 ) -> Self {
136 Self::lazy(move || json_schema, validate)
137 }
138
139 #[must_use]
141 pub fn with_validator(
142 self,
143 validate: impl Fn(Value) -> Result<T, TypeValidationError> + Send + Sync + 'static,
144 ) -> Self {
145 Self {
146 json_schema: self.json_schema,
147 validate: Arc::new(validate),
148 }
149 }
150
151 #[must_use]
154 pub fn transformed(&self, transform: SchemaTransform) -> Self {
155 let source = Arc::clone(&self.json_schema);
156 Self {
157 json_schema: Arc::new(LazyValue::new(Box::new(move || {
158 transform.applied((**source).clone())
159 }))),
160 validate: Arc::clone(&self.validate),
161 }
162 }
163
164 #[must_use]
167 pub fn erased(&self) -> Schema<Value> {
168 let validate = Arc::clone(&self.validate);
169 Schema {
170 json_schema: Arc::clone(&self.json_schema),
171 validate: Arc::new(move |value| validate(value.clone()).map(|_| value)),
172 }
173 }
174}
175
176impl<T> Schema<T> {
177 #[must_use]
179 pub fn json_schema(&self) -> &Value {
180 &self.json_schema
181 }
182
183 pub fn validate(&self, value: Value) -> Result<T, TypeValidationError> {
189 (self.validate)(value)
190 }
191}
192
193fn deserialize_into<T: DeserializeOwned>(value: Value) -> Result<T, TypeValidationError> {
194 match serde_json::from_value::<T>(value.clone()) {
195 Ok(typed) => Ok(typed),
196 Err(error) => Err(TypeValidationError::new(value, error)),
197 }
198}
199
200#[cfg(feature = "json-schema-validation")]
201fn dynamic_validator(json_schema: Arc<LazyValue>) -> Arc<Validate<Value>> {
202 use std::sync::OnceLock;
203
204 use crate::validation::ValidationIssues;
205 use crate::validation::Validator;
206
207 let compiled: OnceLock<Result<Validator, String>> = OnceLock::new();
208 Arc::new(move |value| {
209 let validator = compiled
210 .get_or_init(|| Validator::compile(&json_schema).map_err(|error| error.to_string()));
211 match validator {
212 Ok(validator) => validator
213 .validate(&value)
214 .map(|()| value.clone())
215 .map_err(|issues| TypeValidationError::new(value, issues)),
216 Err(message) => Err(TypeValidationError::new(
217 value,
218 ValidationIssues::message(message.clone()),
219 )),
220 }
221 })
222}
223
224#[cfg(not(feature = "json-schema-validation"))]
225fn dynamic_validator(_json_schema: Arc<LazyValue>) -> Arc<Validate<Value>> {
226 Arc::new(Ok)
227}