1#![allow(missing_debug_implementations)] #![allow(dead_code)]
6#![allow(clippy::pattern_type_mismatch, clippy::needless_continue)]
7
8use crate::{
9 schema::{error::ValidationError, Schema, Type},
10 *,
11};
12use alloc::collections::BTreeMap;
13use regex::Regex;
14
15type String = Rc<str>;
16
17pub struct SchemaValidator;
19
20impl SchemaValidator {
21 pub fn validate(value: &Value, schema: &Schema) -> Result<(), ValidationError> {
49 Self::validate_with_path(value, schema, "")
50 }
51
52 fn validate_with_path(
54 value: &Value,
55 schema: &Schema,
56 path: &str,
57 ) -> Result<(), ValidationError> {
58 match schema.as_type() {
59 Type::Any { .. } => {
60 Ok(())
62 }
63 Type::Integer {
64 minimum, maximum, ..
65 } => Self::validate_integer(value, *minimum, *maximum, path),
66 Type::Number {
67 minimum, maximum, ..
68 } => Self::validate_number(value, *minimum, *maximum, path),
69 Type::Boolean { .. } => Self::validate_boolean(value, path),
70 Type::Null { .. } => Self::validate_null(value, path),
71 Type::String {
72 min_length,
73 max_length,
74 pattern,
75 ..
76 } => Self::validate_string(value, *min_length, *max_length, pattern.as_ref(), path),
77 Type::Array {
78 items,
79 min_items,
80 max_items,
81 ..
82 } => Self::validate_array(value, items, *min_items, *max_items, path),
83 Type::Object {
84 properties,
85 required,
86 additional_properties,
87 discriminated_subobject,
88 ..
89 } => Self::validate_object(
90 value,
91 properties,
92 required.as_ref().map(|r| &**r),
93 additional_properties.as_ref(),
94 discriminated_subobject.as_ref().map(|d| &**d),
95 path,
96 ),
97 Type::AnyOf(schemas) => Self::validate_any_of(value, schemas, path),
98 Type::Const {
99 value: const_value, ..
100 } => Self::validate_const(value, const_value, path),
101 Type::Enum { values, .. } => Self::validate_enum(value, values, path),
102 Type::Set { items, .. } => Self::validate_set(value, items, path),
103 }
104 }
105
106 fn validate_integer(
107 value: &Value,
108 minimum: Option<i64>,
109 maximum: Option<i64>,
110 path: &str,
111 ) -> Result<(), ValidationError> {
112 match value {
113 Value::Number(num) => {
114 if let Some(int_val) = num.as_i64() {
115 if let Some(min) = minimum {
116 if int_val < min {
117 return Err(ValidationError::OutOfRange {
118 value: int_val.to_string().into(),
119 min: Some(min.to_string().into()),
120 max: maximum.map(|m| m.to_string().into()),
121 path: path.to_string().into(),
122 });
123 }
124 }
125 if let Some(max) = maximum {
126 if int_val > max {
127 return Err(ValidationError::OutOfRange {
128 value: int_val.to_string().into(),
129 min: minimum.map(|m| m.to_string().into()),
130 max: Some(max.to_string().into()),
131 path: path.into(),
132 });
133 }
134 }
135 Ok(())
136 } else {
137 Err(ValidationError::TypeMismatch {
138 expected: "integer".into(),
139 actual: "non-integer number".into(),
140 path: path.into(),
141 })
142 }
143 }
144 _ => Err(ValidationError::TypeMismatch {
145 expected: "integer".into(),
146 actual: Self::value_type_name(value),
147 path: path.into(),
148 }),
149 }
150 }
151
152 fn validate_number(
153 value: &Value,
154 minimum: Option<f64>,
155 maximum: Option<f64>,
156 path: &str,
157 ) -> Result<(), ValidationError> {
158 match value {
159 Value::Number(num) => {
160 if let Some(float_val) = num.as_f64() {
162 if let Some(min) = minimum {
163 if float_val < min {
164 return Err(ValidationError::OutOfRange {
165 value: float_val.to_string().into(),
166 min: Some(min.to_string().into()),
167 max: maximum.map(|m| m.to_string().into()),
168 path: path.into(),
169 });
170 }
171 }
172 if let Some(max) = maximum {
173 if float_val > max {
174 return Err(ValidationError::OutOfRange {
175 value: float_val.to_string().into(),
176 min: minimum.map(|m| m.to_string().into()),
177 max: Some(max.to_string().into()),
178 path: path.to_string().into(),
179 });
180 }
181 }
182 Ok(())
183 } else {
184 if let Some(int_val) = num.as_i64() {
189 if let Some(min) = minimum {
191 if (int_val as f64) < min {
192 return Err(ValidationError::OutOfRange {
193 value: int_val.to_string().into(),
194 min: Some(min.to_string().into()),
195 max: maximum.map(|m| m.to_string().into()),
196 path: path.into(),
197 });
198 }
199 }
200 if let Some(max) = maximum {
201 if (int_val as f64) > max {
202 return Err(ValidationError::OutOfRange {
203 value: int_val.to_string().into(),
204 min: minimum.map(|m| m.to_string().into()),
205 max: Some(max.to_string().into()),
206 path: path.into(),
207 });
208 }
209 }
210 Ok(())
211 } else if let Some(big_val) = num.as_big() {
212 if let Some(min) = minimum {
214 let min_int = min as i128;
215 if let Some(val_i128) = num_traits::ToPrimitive::to_i128(&*big_val) {
216 if val_i128 < min_int {
217 return Err(ValidationError::OutOfRange {
218 value: big_val.to_string().into(),
219 min: Some(min.to_string().into()),
220 max: maximum.map(|m| m.to_string().into()),
221 path: path.into(),
222 });
223 }
224 }
225 }
228 if let Some(max) = maximum {
229 let max_int = max as i128;
230 if let Some(val_i128) = num_traits::ToPrimitive::to_i128(&*big_val) {
231 if val_i128 > max_int {
232 return Err(ValidationError::OutOfRange {
233 value: big_val.to_string().into(),
234 min: minimum.map(|m| m.to_string().into()),
235 max: Some(max.to_string().into()),
236 path: path.into(),
237 });
238 }
239 }
240 }
242 Ok(())
243 } else {
244 Err(ValidationError::TypeMismatch {
245 expected: "number".into(),
246 actual: "non-numeric value".into(),
247 path: path.into(),
248 })
249 }
250 }
251 }
252 _ => Err(ValidationError::TypeMismatch {
253 expected: "number".into(),
254 actual: Self::value_type_name(value),
255 path: path.into(),
256 }),
257 }
258 }
259
260 fn validate_boolean(value: &Value, path: &str) -> Result<(), ValidationError> {
261 match value {
262 Value::Bool(_) => Ok(()),
263 _ => Err(ValidationError::TypeMismatch {
264 expected: "boolean".into(),
265 actual: Self::value_type_name(value),
266 path: path.into(),
267 }),
268 }
269 }
270
271 fn validate_null(value: &Value, path: &str) -> Result<(), ValidationError> {
272 match value {
273 Value::Null => Ok(()),
274 _ => Err(ValidationError::TypeMismatch {
275 expected: "null".into(),
276 actual: Self::value_type_name(value),
277 path: path.into(),
278 }),
279 }
280 }
281
282 fn validate_string(
283 value: &Value,
284 min_length: Option<usize>,
285 max_length: Option<usize>,
286 pattern: Option<&String>,
287 path: &str,
288 ) -> Result<(), ValidationError> {
289 match value {
290 Value::String(string_value) => {
291 let str_len = string_value.len();
292
293 if let Some(min) = min_length {
295 if str_len < min {
296 return Err(ValidationError::LengthConstraint {
297 actual_length: str_len,
298 min_length: Some(min),
299 max_length,
300 path: path.into(),
301 });
302 }
303 }
304 if let Some(max) = max_length {
305 if str_len > max {
306 return Err(ValidationError::LengthConstraint {
307 actual_length: str_len,
308 min_length,
309 max_length: Some(max),
310 path: path.into(),
311 });
312 }
313 }
314
315 if let Some(pattern_str) = pattern {
317 let regex =
318 Regex::new(pattern_str).map_err(|e| ValidationError::InvalidPattern {
319 pattern: pattern_str.as_ref().into(),
320 error: e.to_string().into(),
321 })?;
322
323 if !regex.is_match(string_value) {
324 return Err(ValidationError::PatternMismatch {
325 value: string_value.to_string().into(),
326 pattern: pattern_str.clone(),
327 path: path.into(),
328 });
329 }
330 }
331
332 Ok(())
333 }
334 _ => Err(ValidationError::TypeMismatch {
335 expected: "string".into(),
336 actual: Self::value_type_name(value),
337 path: path.into(),
338 }),
339 }
340 }
341
342 fn validate_array(
343 value: &Value,
344 items_schema: &Schema,
345 min_items: Option<usize>,
346 max_items: Option<usize>,
347 path: &str,
348 ) -> Result<(), ValidationError> {
349 match value {
350 Value::Array(array_value) => {
351 let arr_len = array_value.len();
352
353 if let Some(min) = min_items {
355 if arr_len < min {
356 return Err(ValidationError::ArraySizeConstraint {
357 actual_size: arr_len,
358 min_items: Some(min),
359 max_items,
360 path: path.into(),
361 });
362 }
363 }
364 if let Some(max) = max_items {
365 if arr_len > max {
366 return Err(ValidationError::ArraySizeConstraint {
367 actual_size: arr_len,
368 min_items,
369 max_items: Some(max),
370 path: path.into(),
371 });
372 }
373 }
374
375 for (index, item) in array_value.iter().enumerate() {
377 Self::validate_with_path(
378 item,
379 items_schema,
380 &if path.is_empty() {
381 format!("[{index}]")
382 } else {
383 format!("{path}[{index}]")
384 },
385 )
386 .map_err(|e| {
387 ValidationError::ArrayItemValidationFailed {
388 index,
389 path: path.into(),
390 error: Box::new(e),
391 }
392 })?;
393 }
394
395 Ok(())
396 }
397 _ => Err(ValidationError::TypeMismatch {
398 expected: "array".into(),
399 actual: Self::value_type_name(value),
400 path: path.into(),
401 }),
402 }
403 }
404
405 fn validate_object(
406 value: &Value,
407 properties: &BTreeMap<String, Schema>,
408 required: Option<&Vec<String>>,
409 additional_properties: Option<&Schema>,
410 discriminated_subobject: Option<&crate::schema::DiscriminatedSubobject>,
411 path: &str,
412 ) -> Result<(), ValidationError> {
413 match value {
414 Value::Object(object_value) => {
415 if let Some(required_props) = required {
417 for required_prop in required_props.iter() {
418 if !object_value.contains_key(&Value::String(required_prop.clone())) {
419 return Err(ValidationError::MissingRequiredProperty {
420 property: required_prop.clone(),
421 path: path.into(),
422 });
423 }
424 }
425 }
426
427 if let Some(discriminated_subobject) = discriminated_subobject {
430 Self::validate_discriminated_subobject_with_base(
431 object_value,
432 discriminated_subobject,
433 properties,
434 additional_properties,
435 path,
436 )?;
437 } else {
438 for (prop_name, prop_value) in object_value.iter() {
441 let prop_name_str = match prop_name {
443 Value::String(string_key) => string_key,
444 _ => {
445 return Err(ValidationError::NonStringKey {
446 key_type: Self::value_type_name(prop_name),
447 path: path.into(),
448 });
449 }
450 };
451
452 let make_prop_path = || {
454 if path.is_empty() {
455 format!("[{prop_name_str}]")
456 } else {
457 format!("{path}.{prop_name_str}")
458 }
459 };
460
461 if let Some(prop_schema) = properties.get(prop_name_str) {
462 Self::validate_with_path(prop_value, prop_schema, &make_prop_path())
464 .map_err(|e| ValidationError::PropertyValidationFailed {
465 property: prop_name_str.clone(),
466 path: path.into(),
467 error: Box::new(e),
468 })?;
469 } else if let Some(additional_schema) = additional_properties {
470 Self::validate_with_path(
472 prop_value,
473 additional_schema,
474 &make_prop_path(),
475 )
476 .map_err(|e| {
477 ValidationError::PropertyValidationFailed {
478 property: prop_name_str.clone(),
479 path: path.into(),
480 error: Box::new(e),
481 }
482 })?;
483 } else {
484 return Err(ValidationError::AdditionalPropertiesNotAllowed {
486 property: prop_name_str.clone(),
487 path: path.into(),
488 });
489 }
490 }
491 }
492
493 Ok(())
494 }
495 _ => Err(ValidationError::TypeMismatch {
496 expected: "object".into(),
497 actual: Self::value_type_name(value),
498 path: path.into(),
499 }),
500 }
501 }
502
503 fn validate_any_of(
504 value: &Value,
505 schemas: &Vec<Schema>,
506 path: &str,
507 ) -> Result<(), ValidationError> {
508 let mut errors = Vec::new();
509
510 for schema in schemas {
511 match Self::validate_with_path(value, schema, path) {
512 Ok(()) => return Ok(()), Err(e) => errors.push(e),
514 }
515 }
516
517 Err(ValidationError::NoUnionMatch {
519 path: path.into(),
520 errors,
521 })
522 }
523
524 fn validate_const(
525 value: &Value,
526 const_value: &Value,
527 path: &str,
528 ) -> Result<(), ValidationError> {
529 if value == const_value {
530 Ok(())
531 } else {
532 let expected_json =
533 serde_json::to_string(const_value).unwrap_or_else(|_| format!("{const_value:?}"));
534 let actual_json = serde_json::to_string(value).unwrap_or_else(|_| format!("{value:?}"));
535
536 Err(ValidationError::ConstMismatch {
537 expected: expected_json.into(),
538 actual: actual_json.into(),
539 path: path.into(),
540 })
541 }
542 }
543
544 fn validate_enum(
545 value: &Value,
546 allowed_values: &[Value],
547 path: &str,
548 ) -> Result<(), ValidationError> {
549 if allowed_values.contains(value) {
550 Ok(())
551 } else {
552 let value_json = serde_json::to_string(value).unwrap_or_else(|_| format!("{value:?}"));
554
555 let allowed_json: Vec<String> = allowed_values
556 .iter()
557 .map(|v| {
558 serde_json::to_string(v)
559 .unwrap_or_else(|_| format!("{v:?}"))
560 .into()
561 })
562 .collect();
563
564 Err(ValidationError::NotInEnum {
565 value: value_json.into(),
566 allowed_values: allowed_json,
567 path: path.into(),
568 })
569 }
570 }
571
572 fn validate_set(
573 value: &Value,
574 items_schema: &Schema,
575 path: &str,
576 ) -> Result<(), ValidationError> {
577 match value {
578 Value::Set(set_value) => {
579 for (index, item) in set_value.iter().enumerate() {
581 Self::validate_with_path(
582 item,
583 items_schema,
584 &if path.is_empty() {
585 format!("{{{index}}}]")
586 } else {
587 format!("{path}{{{index}}}]")
588 },
589 )?;
590 }
591 Ok(())
592 }
593 _ => Err(ValidationError::TypeMismatch {
594 expected: "set".into(),
595 actual: Self::value_type_name(value),
596 path: path.into(),
597 }),
598 }
599 }
600
601 fn validate_discriminated_subobject_with_base(
602 object_value: &BTreeMap<Value, Value>,
603 discriminated_subobject: &crate::schema::DiscriminatedSubobject,
604 base_properties: &BTreeMap<String, Schema>,
605 base_additional_properties: Option<&Schema>,
606 path: &str,
607 ) -> Result<(), ValidationError> {
608 let discriminator_field = &discriminated_subobject.discriminator;
609 let discriminator_key = Value::String(discriminator_field.clone());
610
611 let discriminator_value = object_value.get(&discriminator_key).ok_or_else(|| {
613 ValidationError::MissingDiscriminator {
614 discriminator: discriminator_field.clone(),
615 path: path.into(),
616 }
617 })?;
618
619 let discriminator_str = match discriminator_value {
621 Value::String(string_value) => string_value.as_ref(),
622 _ => {
623 return Err(ValidationError::TypeMismatch {
624 expected: "string".into(),
625 actual: Self::value_type_name(discriminator_value),
626 path: format!("{path}.{discriminator_field}").into(),
627 });
628 }
629 };
630
631 let variant_schema = discriminated_subobject
633 .variants
634 .get(discriminator_str)
635 .ok_or_else(|| ValidationError::UnknownDiscriminatorValue {
636 discriminator: discriminator_field.clone(),
637 value: discriminator_str.into(),
638 allowed_values: discriminated_subobject.variants.keys().cloned().collect(),
639 path: path.into(),
640 })?;
641
642 for (prop_name, prop_value) in object_value.iter() {
644 let prop_name_str = match prop_name {
646 Value::String(string_key) => string_key,
647 _ => {
648 return Err(ValidationError::NonStringKey {
649 key_type: Self::value_type_name(prop_name),
650 path: path.into(),
651 });
652 }
653 };
654
655 let make_prop_path = || {
657 if path.is_empty() {
658 format!("[{prop_name_str}]")
659 } else {
660 format!("{path}.{prop_name_str}")
661 }
662 };
663
664 if variant_schema.properties.get(prop_name_str).is_some() {
666 continue;
668 }
669
670 if let Some(prop_schema) = base_properties.get(prop_name_str) {
672 Self::validate_with_path(prop_value, prop_schema, &make_prop_path()).map_err(
674 |e| ValidationError::PropertyValidationFailed {
675 property: prop_name_str.clone(),
676 path: path.into(),
677 error: Box::new(e),
678 },
679 )?;
680 continue;
681 }
682
683 if variant_schema.additional_properties.is_some() {
685 continue;
688 } else if let Some(base_additional) = base_additional_properties {
689 Self::validate_with_path(prop_value, base_additional, &make_prop_path()).map_err(
691 |e| ValidationError::PropertyValidationFailed {
692 property: prop_name_str.clone(),
693 path: path.into(),
694 error: Box::new(e),
695 },
696 )?;
697 } else {
698 return Err(ValidationError::AdditionalPropertiesNotAllowed {
700 property: prop_name_str.clone(),
701 path: path.into(),
702 });
703 }
704 }
705
706 Self::validate_subobject(object_value, variant_schema, path).map_err(|e| {
708 ValidationError::DiscriminatedSubobjectValidationFailed {
709 discriminator: discriminator_field.clone(),
710 value: discriminator_str.into(),
711 path: path.into(),
712 error: Box::new(e),
713 }
714 })
715 }
716
717 fn validate_subobject(
718 object_value: &BTreeMap<Value, Value>,
719 subobject: &crate::schema::Subobject,
720 path: &str,
721 ) -> Result<(), ValidationError> {
722 if let Some(required_props) = &subobject.required {
724 for required_prop in required_props.iter() {
725 if !object_value.contains_key(&Value::String(required_prop.clone())) {
726 return Err(ValidationError::MissingRequiredProperty {
727 property: required_prop.clone(),
728 path: path.into(),
729 });
730 }
731 }
732 }
733
734 for (prop_name, prop_schema) in subobject.properties.iter() {
736 let prop_key = Value::String(prop_name.clone());
737 if let Some(prop_value) = object_value.get(&prop_key) {
738 Self::validate_with_path(
739 prop_value,
740 prop_schema,
741 &if path.is_empty() {
742 format!("[{prop_name}]")
743 } else {
744 format!("{path}.{prop_name}")
745 },
746 )
747 .map_err(|e| ValidationError::PropertyValidationFailed {
748 property: prop_name.clone(),
749 path: path.into(),
750 error: Box::new(e),
751 })?;
752 }
753 }
754
755 if let Some(additional_schema) = &subobject.additional_properties {
757 for (prop_name, prop_value) in object_value.iter() {
758 if let Value::String(prop_name_str) = prop_name {
759 if !subobject.properties.contains_key(prop_name_str) {
760 Self::validate_with_path(
761 prop_value,
762 additional_schema,
763 &if path.is_empty() {
764 format!("[{prop_name_str}]")
765 } else {
766 format!("{path}.{prop_name_str}")
767 },
768 )
769 .map_err(|e| {
770 ValidationError::PropertyValidationFailed {
771 property: prop_name_str.clone(),
772 path: path.into(),
773 error: Box::new(e),
774 }
775 })?;
776 }
777 }
778 }
779 }
780
781 Ok(())
782 }
783 fn value_type_name(value: &Value) -> String {
784 match value {
785 Value::Null => "null".into(),
786 Value::Bool(_) => "boolean".into(),
787 Value::Number(_) => "number".into(),
788 Value::String(_) => "string".into(),
789 Value::Array(_) => "array".into(),
790 Value::Set(_) => "set".into(),
791 Value::Object(_) => "object".into(),
792 Value::Undefined => "undefined".into(),
793 }
794 }
795}