jsonschema_rs 0.48.5 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -1
- data/Cargo.toml +2 -2
- data/ext/jsonschema/Cargo.lock +15 -13
- data/ext/jsonschema/Cargo.toml +3 -3
- data/lib/jsonschema/version.rb +1 -1
- data/sig/jsonschema.rbs +53 -4
- data/src/canonical.rs +492 -14
- data/src/lib.rs +110 -295
- data/src/options.rs +24 -21
- data/src/validator_map.rs +1 -4
- metadata +1 -1
data/src/lib.rs
CHANGED
|
@@ -15,13 +15,21 @@ mod ser;
|
|
|
15
15
|
mod static_id;
|
|
16
16
|
mod validator_map;
|
|
17
17
|
|
|
18
|
-
use jsonschema::{
|
|
18
|
+
use jsonschema::{
|
|
19
|
+
json::{
|
|
20
|
+
magnus_child, magnus_invalidate_members_cache, magnus_is_object, magnus_probe_root,
|
|
21
|
+
magnus_take_pending_error, Magnus, MagnusPendingErrorScope, PendingError, RbNode,
|
|
22
|
+
},
|
|
23
|
+
paths::LocationSegment,
|
|
24
|
+
ValidationOptions,
|
|
25
|
+
};
|
|
19
26
|
use magnus::{
|
|
20
27
|
error::ErrorType,
|
|
21
28
|
function,
|
|
22
29
|
gc::{register_address, register_mark_object, unregister_address},
|
|
23
30
|
method,
|
|
24
31
|
prelude::*,
|
|
32
|
+
rb_sys::AsRawValue,
|
|
25
33
|
scan_args::scan_args,
|
|
26
34
|
value::{Lazy, ReprValue},
|
|
27
35
|
DataTypeFunctions, Error, Exception, ExceptionClass, RClass, RHash, RModule, RObject, RString,
|
|
@@ -45,7 +53,7 @@ use crate::{
|
|
|
45
53
|
},
|
|
46
54
|
registry::Registry,
|
|
47
55
|
retriever::{retriever_error_message, RubyRetriever},
|
|
48
|
-
ser::{to_canonical_string, to_schema_value
|
|
56
|
+
ser::{to_canonical_string, to_schema_value},
|
|
49
57
|
static_id::define_rb_intern,
|
|
50
58
|
};
|
|
51
59
|
|
|
@@ -79,14 +87,14 @@ define_rb_intern!(static ID_SYM_EVALUATION_PATH_POINTER: "evaluation_path_pointe
|
|
|
79
87
|
define_rb_intern!(static ID_SYM_ABSOLUTE_KEYWORD_LOCATION: "absolute_keyword_location");
|
|
80
88
|
|
|
81
89
|
struct BuiltValidator {
|
|
82
|
-
validator: jsonschema::Validator
|
|
90
|
+
validator: jsonschema::Validator<Magnus>,
|
|
83
91
|
callback_roots: CallbackRoots,
|
|
84
92
|
compilation_roots: CompilationRootsRef,
|
|
85
93
|
}
|
|
86
94
|
|
|
87
95
|
fn build_validator(
|
|
88
96
|
ruby: &Ruby,
|
|
89
|
-
options: ValidationOptions,
|
|
97
|
+
options: ValidationOptions<'_, Arc<dyn jsonschema::Retrieve>, Magnus>,
|
|
90
98
|
registry: Option<&jsonschema::Registry<'_>>,
|
|
91
99
|
retriever: Option<RubyRetriever>,
|
|
92
100
|
callback_roots: CallbackRoots,
|
|
@@ -279,7 +287,7 @@ fn build_verbose_message(
|
|
|
279
287
|
mut message: String,
|
|
280
288
|
schema_path: &jsonschema::paths::Location,
|
|
281
289
|
instance_path: &jsonschema::paths::Location,
|
|
282
|
-
root_instance: Option
|
|
290
|
+
root_instance: Option<RbNode<'_>>,
|
|
283
291
|
failing_instance: &serde_json::Value,
|
|
284
292
|
mask: Option<&str>,
|
|
285
293
|
) -> String {
|
|
@@ -347,21 +355,14 @@ fn build_verbose_message(
|
|
|
347
355
|
let segment = unescape_segment(segment);
|
|
348
356
|
let segment = segment.as_ref();
|
|
349
357
|
let is_index = match current {
|
|
350
|
-
Some(
|
|
358
|
+
Some(node) if magnus_is_object(node) => false,
|
|
351
359
|
_ => is_index_segment(segment),
|
|
352
360
|
};
|
|
353
361
|
message.push('[');
|
|
354
362
|
push_segment(&mut message, segment, is_index);
|
|
355
363
|
message.push(']');
|
|
356
364
|
|
|
357
|
-
current =
|
|
358
|
-
(Some(serde_json::Value::Array(values)), true) => segment
|
|
359
|
-
.parse::<usize>()
|
|
360
|
-
.ok()
|
|
361
|
-
.and_then(|idx| values.get(idx)),
|
|
362
|
-
(Some(serde_json::Value::Object(values)), false) => values.get(segment),
|
|
363
|
-
_ => None,
|
|
364
|
-
};
|
|
365
|
+
current = current.and_then(|node| magnus_child(node, segment, is_index));
|
|
365
366
|
}
|
|
366
367
|
message.push_str(":\n ");
|
|
367
368
|
|
|
@@ -388,7 +389,7 @@ fn error_message(error: &jsonschema::ValidationError<'_>, mask: Option<&str>) ->
|
|
|
388
389
|
fn into_ruby_error(
|
|
389
390
|
ruby: &Ruby,
|
|
390
391
|
error: jsonschema::ValidationError<'_>,
|
|
391
|
-
root_instance: Option
|
|
392
|
+
root_instance: Option<RbNode<'_>>,
|
|
392
393
|
message: &str,
|
|
393
394
|
mask: Option<&str>,
|
|
394
395
|
) -> Result<Value, Error> {
|
|
@@ -470,7 +471,7 @@ fn into_ruby_error(
|
|
|
470
471
|
fn to_ruby_error_value(
|
|
471
472
|
ruby: &Ruby,
|
|
472
473
|
error: jsonschema::ValidationError<'_>,
|
|
473
|
-
root_instance: Option
|
|
474
|
+
root_instance: Option<RbNode<'_>>,
|
|
474
475
|
mask: Option<&str>,
|
|
475
476
|
) -> Result<Value, Error> {
|
|
476
477
|
let message = error_message(&error, mask);
|
|
@@ -485,7 +486,7 @@ pub(crate) fn referencing_error(ruby: &Ruby, message: String) -> Error {
|
|
|
485
486
|
fn raise_validation_error(
|
|
486
487
|
ruby: &Ruby,
|
|
487
488
|
error: jsonschema::ValidationError<'_>,
|
|
488
|
-
root_instance: Option
|
|
489
|
+
root_instance: Option<RbNode<'_>>,
|
|
489
490
|
mask: Option<&str>,
|
|
490
491
|
) -> Error {
|
|
491
492
|
let message = error_message(&error, mask);
|
|
@@ -547,60 +548,36 @@ fn handle_callback_panic(ruby: &Ruby, err: Box<dyn std::any::Any + Send>) -> Err
|
|
|
547
548
|
})
|
|
548
549
|
}
|
|
549
550
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
} else {
|
|
557
|
-
"Validation panicked".to_string()
|
|
558
|
-
};
|
|
559
|
-
Error::new(ruby.exception_runtime_error(), msg)
|
|
551
|
+
fn pending_error_to_ruby(ruby: &Ruby, error: PendingError) -> Error {
|
|
552
|
+
match error {
|
|
553
|
+
PendingError::Type(message) => Error::new(ruby.exception_type_error(), message),
|
|
554
|
+
PendingError::Encoding(message) => Error::new(ruby.exception_encoding_error(), message),
|
|
555
|
+
PendingError::Argument(message) => Error::new(ruby.exception_arg_error(), message),
|
|
556
|
+
}
|
|
560
557
|
}
|
|
561
558
|
|
|
562
|
-
/// Run
|
|
559
|
+
/// Run validation over `instance` in place, raising anything the representation could only record.
|
|
563
560
|
///
|
|
564
|
-
///
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
561
|
+
/// Accessors cannot fail, so a value the representation cannot read is stashed and surfaced here.
|
|
562
|
+
fn surface_pending_errors<T>(
|
|
563
|
+
ruby: &Ruby,
|
|
564
|
+
instance: Value,
|
|
565
|
+
run: impl FnOnce(RbNode<'_>) -> Result<T, Error>,
|
|
566
|
+
) -> Result<T, Error> {
|
|
567
|
+
let _scope = MagnusPendingErrorScope::enter();
|
|
568
|
+
// Ruby is free to mutate the instance between calls, so no snapshot from an earlier one holds.
|
|
569
|
+
magnus_invalidate_members_cache();
|
|
570
|
+
#[allow(unsafe_code)]
|
|
571
|
+
let node = RbNode::new(instance.as_raw());
|
|
572
|
+
magnus_probe_root(node);
|
|
573
|
+
if let Some(error) = magnus_take_pending_error() {
|
|
574
|
+
return Err(pending_error_to_ruby(ruby, error));
|
|
578
575
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
) -> *mut std::ffi::c_void {
|
|
583
|
-
let payload = unsafe { &mut *data.cast::<Payload<F, R>>() };
|
|
584
|
-
let result = panic::catch_unwind(AssertUnwindSafe(|| (payload.f)()));
|
|
585
|
-
payload.result.write(result);
|
|
586
|
-
std::ptr::null_mut()
|
|
576
|
+
let result = run(node)?;
|
|
577
|
+
if let Some(error) = magnus_take_pending_error() {
|
|
578
|
+
return Err(pending_error_to_ruby(ruby, error));
|
|
587
579
|
}
|
|
588
|
-
|
|
589
|
-
let mut payload = Payload {
|
|
590
|
-
f,
|
|
591
|
-
result: std::mem::MaybeUninit::uninit(),
|
|
592
|
-
};
|
|
593
|
-
|
|
594
|
-
unsafe {
|
|
595
|
-
rb_sys::rb_thread_call_without_gvl(
|
|
596
|
-
Some(call::<F, R>),
|
|
597
|
-
(&raw mut payload).cast::<std::ffi::c_void>(),
|
|
598
|
-
None,
|
|
599
|
-
std::ptr::null_mut(),
|
|
600
|
-
)
|
|
601
|
-
};
|
|
602
|
-
|
|
603
|
-
unsafe { payload.result.assume_init() }
|
|
580
|
+
Ok(result)
|
|
604
581
|
}
|
|
605
582
|
|
|
606
583
|
/// Wrapper around `jsonschema::Validator`.
|
|
@@ -611,9 +588,8 @@ where
|
|
|
611
588
|
#[derive(magnus::TypedData)]
|
|
612
589
|
#[magnus(class = "JSONSchema::Validator", free_immediately, size, mark)]
|
|
613
590
|
pub struct Validator {
|
|
614
|
-
validator: jsonschema::Validator
|
|
591
|
+
validator: jsonschema::Validator<Magnus>,
|
|
615
592
|
mask: Option<String>,
|
|
616
|
-
has_ruby_callbacks: bool,
|
|
617
593
|
/// Marked during Ruby's GC mark phase to keep runtime callbacks alive.
|
|
618
594
|
callback_roots: CallbackRoots,
|
|
619
595
|
/// Protects callbacks via `register_address` during schema compilation —
|
|
@@ -630,16 +606,14 @@ impl DataTypeFunctions for Validator {
|
|
|
630
606
|
|
|
631
607
|
impl Validator {
|
|
632
608
|
pub(crate) fn from_jsonschema_with_roots(
|
|
633
|
-
validator: jsonschema::Validator
|
|
609
|
+
validator: jsonschema::Validator<Magnus>,
|
|
634
610
|
mask: Option<String>,
|
|
635
|
-
has_ruby_callbacks: bool,
|
|
636
611
|
callback_roots: CallbackRoots,
|
|
637
612
|
compilation_roots: CompilationRootsRef,
|
|
638
613
|
) -> Self {
|
|
639
614
|
Self {
|
|
640
615
|
validator,
|
|
641
616
|
mask,
|
|
642
|
-
has_ruby_callbacks,
|
|
643
617
|
callback_roots,
|
|
644
618
|
_compilation_roots: compilation_roots,
|
|
645
619
|
}
|
|
@@ -656,70 +630,37 @@ impl Validator {
|
|
|
656
630
|
}
|
|
657
631
|
}
|
|
658
632
|
|
|
659
|
-
#[allow(unsafe_code)]
|
|
660
633
|
fn is_valid(ruby: &Ruby, rb_self: &Self, instance: Value) -> Result<bool, Error> {
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
rb_self.validator.is_valid(&json_instance)
|
|
666
|
-
}));
|
|
667
|
-
match result {
|
|
668
|
-
Ok(valid) => Ok(valid),
|
|
669
|
-
Err(err) => Err(handle_callback_panic(ruby, err)),
|
|
670
|
-
}
|
|
671
|
-
} else {
|
|
672
|
-
// SAFETY: validation is pure Rust with no Ruby callbacks
|
|
673
|
-
match unsafe { without_gvl(|| rb_self.validator.is_valid(&json_instance)) } {
|
|
674
|
-
Ok(valid) => Ok(valid),
|
|
675
|
-
Err(err) => Err(handle_without_gvl_panic(ruby, err)),
|
|
676
|
-
}
|
|
677
|
-
}
|
|
634
|
+
surface_pending_errors(ruby, instance, |node| {
|
|
635
|
+
let result = catch_unwind_silent(AssertUnwindSafe(|| rb_self.validator.is_valid(node)));
|
|
636
|
+
result.map_err(|err| handle_callback_panic(ruby, err))
|
|
637
|
+
})
|
|
678
638
|
}
|
|
679
639
|
|
|
680
|
-
#[allow(unsafe_code)]
|
|
681
640
|
fn validate(ruby: &Ruby, rb_self: &Self, instance: Value) -> Result<(), Error> {
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
if rb_self.has_ruby_callbacks {
|
|
686
|
-
let result = catch_unwind_silent(AssertUnwindSafe(|| {
|
|
687
|
-
rb_self.validator.validate(&json_instance)
|
|
688
|
-
}));
|
|
641
|
+
surface_pending_errors(ruby, instance, |node| {
|
|
642
|
+
let _scope = KeywordCauseScope::enter();
|
|
643
|
+
let result = catch_unwind_silent(AssertUnwindSafe(|| rb_self.validator.validate(node)));
|
|
689
644
|
match result {
|
|
690
645
|
Ok(Ok(())) => Ok(()),
|
|
691
646
|
Ok(Err(error)) => Err(raise_validation_error(
|
|
692
647
|
ruby,
|
|
693
648
|
error,
|
|
694
|
-
Some(
|
|
649
|
+
Some(node),
|
|
695
650
|
rb_self.mask.as_deref(),
|
|
696
651
|
)),
|
|
697
652
|
Err(err) => Err(handle_callback_panic(ruby, err)),
|
|
698
653
|
}
|
|
699
|
-
}
|
|
700
|
-
// SAFETY: validation is pure Rust with no Ruby callbacks
|
|
701
|
-
match unsafe { without_gvl(|| rb_self.validator.validate(&json_instance)) } {
|
|
702
|
-
Ok(Ok(())) => Ok(()),
|
|
703
|
-
Ok(Err(error)) => Err(raise_validation_error(
|
|
704
|
-
ruby,
|
|
705
|
-
error,
|
|
706
|
-
Some(&json_instance),
|
|
707
|
-
rb_self.mask.as_deref(),
|
|
708
|
-
)),
|
|
709
|
-
Err(err) => Err(handle_without_gvl_panic(ruby, err)),
|
|
710
|
-
}
|
|
711
|
-
}
|
|
654
|
+
})
|
|
712
655
|
}
|
|
713
656
|
|
|
714
|
-
#[allow(unsafe_code)]
|
|
715
657
|
fn iter_errors(ruby: &Ruby, rb_self: &Self, instance: Value) -> Result<Value, Error> {
|
|
716
|
-
|
|
717
|
-
|
|
658
|
+
surface_pending_errors(ruby, instance, |node| {
|
|
659
|
+
let _scope = KeywordCauseScope::enter();
|
|
718
660
|
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
let mut iter = rb_self.validator.iter_errors(&json_instance);
|
|
661
|
+
if ruby.block_given() {
|
|
662
|
+
// Lazy path: yield errors one at a time to the block
|
|
663
|
+
let mut iter = rb_self.validator.iter_errors(node);
|
|
723
664
|
loop {
|
|
724
665
|
let result = catch_unwind_silent(AssertUnwindSafe(|| iter.next()));
|
|
725
666
|
match result {
|
|
@@ -727,7 +668,7 @@ impl Validator {
|
|
|
727
668
|
let ruby_error = to_ruby_error_value(
|
|
728
669
|
ruby,
|
|
729
670
|
error,
|
|
730
|
-
Some(
|
|
671
|
+
Some(node),
|
|
731
672
|
rb_self.mask.as_deref(),
|
|
732
673
|
)?;
|
|
733
674
|
ruby.yield_value::<Value, Value>(ruby_error)?;
|
|
@@ -736,34 +677,20 @@ impl Validator {
|
|
|
736
677
|
Err(err) => return Err(handle_callback_panic(ruby, err)),
|
|
737
678
|
}
|
|
738
679
|
}
|
|
739
|
-
|
|
740
|
-
for error in rb_self.validator.iter_errors(&json_instance) {
|
|
741
|
-
let ruby_error = to_ruby_error_value(
|
|
742
|
-
ruby,
|
|
743
|
-
error,
|
|
744
|
-
Some(&json_instance),
|
|
745
|
-
rb_self.mask.as_deref(),
|
|
746
|
-
)?;
|
|
747
|
-
ruby.yield_value::<Value, Value>(ruby_error)?;
|
|
748
|
-
}
|
|
680
|
+
return Ok(ruby.qnil().as_value());
|
|
749
681
|
}
|
|
750
|
-
|
|
751
|
-
} else if rb_self.has_ruby_callbacks {
|
|
752
|
-
// Eager path with callbacks
|
|
682
|
+
|
|
753
683
|
let result = catch_unwind_silent(AssertUnwindSafe(|| {
|
|
754
|
-
rb_self
|
|
755
|
-
.validator
|
|
756
|
-
.iter_errors(&json_instance)
|
|
757
|
-
.collect::<Vec<_>>()
|
|
684
|
+
rb_self.validator.iter_errors(node).collect::<Vec<_>>()
|
|
758
685
|
}));
|
|
759
686
|
match result {
|
|
760
687
|
Ok(errors) => {
|
|
761
688
|
let arr = ruby.ary_new_capa(errors.len());
|
|
762
|
-
for
|
|
689
|
+
for error in errors {
|
|
763
690
|
arr.push(to_ruby_error_value(
|
|
764
691
|
ruby,
|
|
765
|
-
|
|
766
|
-
Some(
|
|
692
|
+
error,
|
|
693
|
+
Some(node),
|
|
767
694
|
rb_self.mask.as_deref(),
|
|
768
695
|
)?)?;
|
|
769
696
|
}
|
|
@@ -771,55 +698,18 @@ impl Validator {
|
|
|
771
698
|
}
|
|
772
699
|
Err(err) => Err(handle_callback_panic(ruby, err)),
|
|
773
700
|
}
|
|
774
|
-
}
|
|
775
|
-
// Eager path without callbacks — release GVL
|
|
776
|
-
// SAFETY: validation is pure Rust with no Ruby callbacks
|
|
777
|
-
let errors = match unsafe {
|
|
778
|
-
without_gvl(|| {
|
|
779
|
-
rb_self
|
|
780
|
-
.validator
|
|
781
|
-
.iter_errors(&json_instance)
|
|
782
|
-
.collect::<Vec<_>>()
|
|
783
|
-
})
|
|
784
|
-
} {
|
|
785
|
-
Ok(errors) => errors,
|
|
786
|
-
Err(err) => return Err(handle_without_gvl_panic(ruby, err)),
|
|
787
|
-
};
|
|
788
|
-
let arr = ruby.ary_new_capa(errors.len());
|
|
789
|
-
for e in errors {
|
|
790
|
-
arr.push(to_ruby_error_value(
|
|
791
|
-
ruby,
|
|
792
|
-
e,
|
|
793
|
-
Some(&json_instance),
|
|
794
|
-
rb_self.mask.as_deref(),
|
|
795
|
-
)?)?;
|
|
796
|
-
}
|
|
797
|
-
Ok(arr.as_value())
|
|
798
|
-
}
|
|
701
|
+
})
|
|
799
702
|
}
|
|
800
703
|
|
|
801
|
-
#[allow(unsafe_code)]
|
|
802
704
|
fn evaluate(ruby: &Ruby, rb_self: &Self, instance: Value) -> Result<Evaluation, Error> {
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
if rb_self.has_ruby_callbacks {
|
|
807
|
-
let result = catch_unwind_silent(AssertUnwindSafe(|| {
|
|
808
|
-
rb_self.validator.evaluate(&json_instance)
|
|
809
|
-
}));
|
|
705
|
+
surface_pending_errors(ruby, instance, |node| {
|
|
706
|
+
let _scope = KeywordCauseScope::enter();
|
|
707
|
+
let result = catch_unwind_silent(AssertUnwindSafe(|| rb_self.validator.evaluate(node)));
|
|
810
708
|
match result {
|
|
811
709
|
Ok(output) => Ok(Evaluation::new(output)),
|
|
812
710
|
Err(err) => Err(handle_callback_panic(ruby, err)),
|
|
813
711
|
}
|
|
814
|
-
}
|
|
815
|
-
// SAFETY: validation is pure Rust with no Ruby callbacks
|
|
816
|
-
let output = match unsafe { without_gvl(|| rb_self.validator.evaluate(&json_instance)) }
|
|
817
|
-
{
|
|
818
|
-
Ok(output) => output,
|
|
819
|
-
Err(err) => return Err(handle_without_gvl_panic(ruby, err)),
|
|
820
|
-
};
|
|
821
|
-
Ok(Evaluation::new(output))
|
|
822
|
-
}
|
|
712
|
+
})
|
|
823
713
|
}
|
|
824
714
|
|
|
825
715
|
fn inspect(&self) -> String {
|
|
@@ -892,7 +782,6 @@ fn validator_for(ruby: &Ruby, args: &[Value]) -> Result<Validator, Error> {
|
|
|
892
782
|
|
|
893
783
|
let json_schema = to_schema_value(ruby, schema)?;
|
|
894
784
|
let parsed = build_parsed_options(ruby, kw, None)?;
|
|
895
|
-
let has_ruby_callbacks = parsed.has_ruby_callbacks;
|
|
896
785
|
let BuiltValidator {
|
|
897
786
|
validator,
|
|
898
787
|
callback_roots,
|
|
@@ -909,7 +798,6 @@ fn validator_for(ruby: &Ruby, args: &[Value]) -> Result<Validator, Error> {
|
|
|
909
798
|
Ok(Validator {
|
|
910
799
|
validator,
|
|
911
800
|
mask: parsed.mask,
|
|
912
|
-
has_ruby_callbacks,
|
|
913
801
|
callback_roots,
|
|
914
802
|
_compilation_roots: compilation_roots,
|
|
915
803
|
})
|
|
@@ -922,7 +810,6 @@ fn validator_map_for(ruby: &Ruby, args: &[Value]) -> Result<validator_map::Valid
|
|
|
922
810
|
|
|
923
811
|
let json_schema = to_schema_value(ruby, schema)?;
|
|
924
812
|
let parsed = build_parsed_options(ruby, kw, None)?;
|
|
925
|
-
let has_ruby_callbacks = parsed.has_ruby_callbacks;
|
|
926
813
|
let callback_roots = parsed.callback_roots;
|
|
927
814
|
let compilation_roots = parsed.compilation_roots;
|
|
928
815
|
let mask = parsed.mask;
|
|
@@ -938,7 +825,6 @@ fn validator_map_for(ruby: &Ruby, args: &[Value]) -> Result<validator_map::Valid
|
|
|
938
825
|
match options.build_map(&json_schema) {
|
|
939
826
|
Ok(inner) => Ok(validator_map::ValidatorMap {
|
|
940
827
|
inner,
|
|
941
|
-
has_ruby_callbacks,
|
|
942
828
|
callback_roots,
|
|
943
829
|
compilation_roots,
|
|
944
830
|
mask,
|
|
@@ -1013,14 +899,12 @@ fn canonical_json_to_string(ruby: &Ruby, object: Value) -> Result<String, Error>
|
|
|
1013
899
|
to_canonical_string(ruby, object)
|
|
1014
900
|
}
|
|
1015
901
|
|
|
1016
|
-
#[allow(unsafe_code)]
|
|
1017
902
|
fn is_valid(ruby: &Ruby, args: &[Value]) -> Result<bool, Error> {
|
|
1018
903
|
let parsed_args = scan_args::<(Value, Value), (), (), (), _, ()>(args)?;
|
|
1019
904
|
let (schema, instance) = parsed_args.required;
|
|
1020
905
|
let kw = extract_kwargs(ruby, parsed_args.keywords)?;
|
|
1021
906
|
|
|
1022
907
|
let json_schema = to_schema_value(ruby, schema)?;
|
|
1023
|
-
let json_instance = to_value(ruby, instance)?;
|
|
1024
908
|
let parsed = build_parsed_options(ruby, kw, None)?;
|
|
1025
909
|
let has_ruby_callbacks = parsed.has_ruby_callbacks;
|
|
1026
910
|
let BuiltValidator {
|
|
@@ -1037,23 +921,14 @@ fn is_valid(ruby: &Ruby, args: &[Value]) -> Result<bool, Error> {
|
|
|
1037
921
|
&json_schema,
|
|
1038
922
|
)?;
|
|
1039
923
|
|
|
1040
|
-
|
|
1041
|
-
let _callback_roots =
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
}
|
|
1047
|
-
} else {
|
|
1048
|
-
// SAFETY: validation is pure Rust with no Ruby callbacks
|
|
1049
|
-
match unsafe { without_gvl(|| validator.is_valid(&json_instance)) } {
|
|
1050
|
-
Ok(valid) => Ok(valid),
|
|
1051
|
-
Err(err) => Err(handle_without_gvl_panic(ruby, err)),
|
|
1052
|
-
}
|
|
1053
|
-
}
|
|
924
|
+
surface_pending_errors(ruby, instance, |node| {
|
|
925
|
+
let _callback_roots =
|
|
926
|
+
has_ruby_callbacks.then(|| CallbackRootGuard::new(ruby, &callback_roots));
|
|
927
|
+
catch_unwind_silent(AssertUnwindSafe(|| validator.is_valid(node)))
|
|
928
|
+
.map_err(|err| handle_callback_panic(ruby, err))
|
|
929
|
+
})
|
|
1054
930
|
}
|
|
1055
931
|
|
|
1056
|
-
#[allow(unsafe_code)]
|
|
1057
932
|
fn validate(ruby: &Ruby, args: &[Value]) -> Result<(), Error> {
|
|
1058
933
|
let _scope = KeywordCauseScope::enter();
|
|
1059
934
|
let parsed_args = scan_args::<(Value, Value), (), (), (), _, ()>(args)?;
|
|
@@ -1061,7 +936,6 @@ fn validate(ruby: &Ruby, args: &[Value]) -> Result<(), Error> {
|
|
|
1061
936
|
let kw = extract_kwargs(ruby, parsed_args.keywords)?;
|
|
1062
937
|
|
|
1063
938
|
let json_schema = to_schema_value(ruby, schema)?;
|
|
1064
|
-
let json_instance = to_value(ruby, instance)?;
|
|
1065
939
|
let parsed = build_parsed_options(ruby, kw, None)?;
|
|
1066
940
|
let has_ruby_callbacks = parsed.has_ruby_callbacks;
|
|
1067
941
|
let BuiltValidator {
|
|
@@ -1078,35 +952,22 @@ fn validate(ruby: &Ruby, args: &[Value]) -> Result<(), Error> {
|
|
|
1078
952
|
&json_schema,
|
|
1079
953
|
)?;
|
|
1080
954
|
|
|
1081
|
-
|
|
1082
|
-
let _callback_roots =
|
|
1083
|
-
|
|
1084
|
-
match
|
|
955
|
+
surface_pending_errors(ruby, instance, |node| {
|
|
956
|
+
let _callback_roots =
|
|
957
|
+
has_ruby_callbacks.then(|| CallbackRootGuard::new(ruby, &callback_roots));
|
|
958
|
+
match catch_unwind_silent(AssertUnwindSafe(|| validator.validate(node))) {
|
|
1085
959
|
Ok(Ok(())) => Ok(()),
|
|
1086
960
|
Ok(Err(error)) => Err(raise_validation_error(
|
|
1087
961
|
ruby,
|
|
1088
962
|
error,
|
|
1089
|
-
Some(
|
|
963
|
+
Some(node),
|
|
1090
964
|
parsed.mask.as_deref(),
|
|
1091
965
|
)),
|
|
1092
966
|
Err(err) => Err(handle_callback_panic(ruby, err)),
|
|
1093
967
|
}
|
|
1094
|
-
}
|
|
1095
|
-
// SAFETY: validation is pure Rust with no Ruby callbacks
|
|
1096
|
-
match unsafe { without_gvl(|| validator.validate(&json_instance)) } {
|
|
1097
|
-
Ok(Ok(())) => Ok(()),
|
|
1098
|
-
Ok(Err(error)) => Err(raise_validation_error(
|
|
1099
|
-
ruby,
|
|
1100
|
-
error,
|
|
1101
|
-
Some(&json_instance),
|
|
1102
|
-
parsed.mask.as_deref(),
|
|
1103
|
-
)),
|
|
1104
|
-
Err(err) => Err(handle_without_gvl_panic(ruby, err)),
|
|
1105
|
-
}
|
|
1106
|
-
}
|
|
968
|
+
})
|
|
1107
969
|
}
|
|
1108
970
|
|
|
1109
|
-
#[allow(unsafe_code)]
|
|
1110
971
|
fn each_error(ruby: &Ruby, args: &[Value]) -> Result<Value, Error> {
|
|
1111
972
|
let _scope = KeywordCauseScope::enter();
|
|
1112
973
|
let parsed_args = scan_args::<(Value, Value), (), (), (), _, ()>(args)?;
|
|
@@ -1114,7 +975,6 @@ fn each_error(ruby: &Ruby, args: &[Value]) -> Result<Value, Error> {
|
|
|
1114
975
|
let kw = extract_kwargs(ruby, parsed_args.keywords)?;
|
|
1115
976
|
|
|
1116
977
|
let json_schema = to_schema_value(ruby, schema)?;
|
|
1117
|
-
let json_instance = to_value(ruby, instance)?;
|
|
1118
978
|
let parsed = build_parsed_options(ruby, kw, None)?;
|
|
1119
979
|
let has_ruby_callbacks = parsed.has_ruby_callbacks;
|
|
1120
980
|
let BuiltValidator {
|
|
@@ -1131,49 +991,37 @@ fn each_error(ruby: &Ruby, args: &[Value]) -> Result<Value, Error> {
|
|
|
1131
991
|
&json_schema,
|
|
1132
992
|
)?;
|
|
1133
993
|
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
994
|
+
surface_pending_errors(ruby, instance, |node| {
|
|
995
|
+
let _callback_roots =
|
|
996
|
+
has_ruby_callbacks.then(|| CallbackRootGuard::new(ruby, &callback_roots));
|
|
997
|
+
|
|
998
|
+
if ruby.block_given() {
|
|
999
|
+
// Lazy path: yield errors one at a time to the block
|
|
1000
|
+
let mut iter = validator.iter_errors(node);
|
|
1139
1001
|
loop {
|
|
1140
|
-
|
|
1141
|
-
match result {
|
|
1002
|
+
match catch_unwind_silent(AssertUnwindSafe(|| iter.next())) {
|
|
1142
1003
|
Ok(Some(error)) => {
|
|
1143
|
-
let ruby_error =
|
|
1144
|
-
ruby,
|
|
1145
|
-
error,
|
|
1146
|
-
Some(&json_instance),
|
|
1147
|
-
parsed.mask.as_deref(),
|
|
1148
|
-
)?;
|
|
1004
|
+
let ruby_error =
|
|
1005
|
+
to_ruby_error_value(ruby, error, Some(node), parsed.mask.as_deref())?;
|
|
1149
1006
|
ruby.yield_value::<Value, Value>(ruby_error)?;
|
|
1150
1007
|
}
|
|
1151
1008
|
Ok(None) => break,
|
|
1152
1009
|
Err(err) => return Err(handle_callback_panic(ruby, err)),
|
|
1153
1010
|
}
|
|
1154
1011
|
}
|
|
1155
|
-
|
|
1156
|
-
for error in validator.iter_errors(&json_instance) {
|
|
1157
|
-
let ruby_error =
|
|
1158
|
-
to_ruby_error_value(ruby, error, Some(&json_instance), parsed.mask.as_deref())?;
|
|
1159
|
-
ruby.yield_value::<Value, Value>(ruby_error)?;
|
|
1160
|
-
}
|
|
1012
|
+
return Ok(ruby.qnil().as_value());
|
|
1161
1013
|
}
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
let result = catch_unwind_silent(AssertUnwindSafe(|| {
|
|
1167
|
-
validator.iter_errors(&json_instance).collect::<Vec<_>>()
|
|
1168
|
-
}));
|
|
1169
|
-
match result {
|
|
1014
|
+
|
|
1015
|
+
match catch_unwind_silent(AssertUnwindSafe(|| {
|
|
1016
|
+
validator.iter_errors(node).collect::<Vec<_>>()
|
|
1017
|
+
})) {
|
|
1170
1018
|
Ok(errors) => {
|
|
1171
1019
|
let arr = ruby.ary_new_capa(errors.len());
|
|
1172
|
-
for
|
|
1020
|
+
for error in errors {
|
|
1173
1021
|
arr.push(to_ruby_error_value(
|
|
1174
1022
|
ruby,
|
|
1175
|
-
|
|
1176
|
-
Some(
|
|
1023
|
+
error,
|
|
1024
|
+
Some(node),
|
|
1177
1025
|
parsed.mask.as_deref(),
|
|
1178
1026
|
)?)?;
|
|
1179
1027
|
}
|
|
@@ -1181,29 +1029,9 @@ fn each_error(ruby: &Ruby, args: &[Value]) -> Result<Value, Error> {
|
|
|
1181
1029
|
}
|
|
1182
1030
|
Err(err) => Err(handle_callback_panic(ruby, err)),
|
|
1183
1031
|
}
|
|
1184
|
-
}
|
|
1185
|
-
// Eager path without callbacks — release GVL
|
|
1186
|
-
// SAFETY: validation is pure Rust with no Ruby callbacks
|
|
1187
|
-
let errors = match unsafe {
|
|
1188
|
-
without_gvl(|| validator.iter_errors(&json_instance).collect::<Vec<_>>())
|
|
1189
|
-
} {
|
|
1190
|
-
Ok(errors) => errors,
|
|
1191
|
-
Err(err) => return Err(handle_without_gvl_panic(ruby, err)),
|
|
1192
|
-
};
|
|
1193
|
-
let arr = ruby.ary_new_capa(errors.len());
|
|
1194
|
-
for e in errors {
|
|
1195
|
-
arr.push(to_ruby_error_value(
|
|
1196
|
-
ruby,
|
|
1197
|
-
e,
|
|
1198
|
-
Some(&json_instance),
|
|
1199
|
-
parsed.mask.as_deref(),
|
|
1200
|
-
)?)?;
|
|
1201
|
-
}
|
|
1202
|
-
Ok(arr.as_value())
|
|
1203
|
-
}
|
|
1032
|
+
})
|
|
1204
1033
|
}
|
|
1205
1034
|
|
|
1206
|
-
#[allow(unsafe_code)]
|
|
1207
1035
|
fn evaluate(ruby: &Ruby, args: &[Value]) -> Result<Evaluation, Error> {
|
|
1208
1036
|
let _scope = KeywordCauseScope::enter();
|
|
1209
1037
|
let parsed_args = scan_args::<(Value, Value), (), (), (), _, ()>(args)?;
|
|
@@ -1211,7 +1039,6 @@ fn evaluate(ruby: &Ruby, args: &[Value]) -> Result<Evaluation, Error> {
|
|
|
1211
1039
|
let kw = extract_evaluate_kwargs(ruby, parsed_args.keywords)?;
|
|
1212
1040
|
|
|
1213
1041
|
let json_schema = to_schema_value(ruby, schema)?;
|
|
1214
|
-
let json_instance = to_value(ruby, instance)?;
|
|
1215
1042
|
let parsed = build_parsed_options(ruby, kw, None)?;
|
|
1216
1043
|
let has_ruby_callbacks = parsed.has_ruby_callbacks;
|
|
1217
1044
|
let BuiltValidator {
|
|
@@ -1228,21 +1055,14 @@ fn evaluate(ruby: &Ruby, args: &[Value]) -> Result<Evaluation, Error> {
|
|
|
1228
1055
|
&json_schema,
|
|
1229
1056
|
)?;
|
|
1230
1057
|
|
|
1231
|
-
|
|
1232
|
-
let _callback_roots =
|
|
1233
|
-
|
|
1234
|
-
match
|
|
1058
|
+
surface_pending_errors(ruby, instance, |node| {
|
|
1059
|
+
let _callback_roots =
|
|
1060
|
+
has_ruby_callbacks.then(|| CallbackRootGuard::new(ruby, &callback_roots));
|
|
1061
|
+
match catch_unwind_silent(AssertUnwindSafe(|| validator.evaluate(node))) {
|
|
1235
1062
|
Ok(output) => Ok(Evaluation::new(output)),
|
|
1236
1063
|
Err(err) => Err(handle_callback_panic(ruby, err)),
|
|
1237
1064
|
}
|
|
1238
|
-
}
|
|
1239
|
-
// SAFETY: validation is pure Rust with no Ruby callbacks
|
|
1240
|
-
let output = match unsafe { without_gvl(|| validator.evaluate(&json_instance)) } {
|
|
1241
|
-
Ok(output) => output,
|
|
1242
|
-
Err(err) => return Err(handle_without_gvl_panic(ruby, err)),
|
|
1243
|
-
};
|
|
1244
|
-
Ok(Evaluation::new(output))
|
|
1245
|
-
}
|
|
1065
|
+
})
|
|
1246
1066
|
}
|
|
1247
1067
|
|
|
1248
1068
|
macro_rules! define_draft_validator {
|
|
@@ -1267,7 +1087,6 @@ macro_rules! define_draft_validator {
|
|
|
1267
1087
|
|
|
1268
1088
|
let json_schema = to_schema_value(ruby, schema)?;
|
|
1269
1089
|
let parsed = build_parsed_options(ruby, kw, Some($draft))?;
|
|
1270
|
-
let has_ruby_callbacks = parsed.has_ruby_callbacks;
|
|
1271
1090
|
let BuiltValidator {
|
|
1272
1091
|
validator,
|
|
1273
1092
|
callback_roots,
|
|
@@ -1285,7 +1104,6 @@ macro_rules! define_draft_validator {
|
|
|
1285
1104
|
inner: Validator {
|
|
1286
1105
|
validator,
|
|
1287
1106
|
mask: parsed.mask,
|
|
1288
|
-
has_ruby_callbacks,
|
|
1289
1107
|
callback_roots,
|
|
1290
1108
|
_compilation_roots: compilation_roots,
|
|
1291
1109
|
},
|
|
@@ -1394,12 +1212,9 @@ fn meta_validate(ruby: &Ruby, args: &[Value]) -> Result<(), Error> {
|
|
|
1394
1212
|
if let jsonschema::error::ValidationErrorKind::Referencing(err) = error.kind() {
|
|
1395
1213
|
return Err(referencing_error(ruby, err.to_string()));
|
|
1396
1214
|
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
Some(&json_schema),
|
|
1401
|
-
None,
|
|
1402
|
-
))
|
|
1215
|
+
#[allow(unsafe_code)]
|
|
1216
|
+
let node = RbNode::new(schema.as_raw());
|
|
1217
|
+
Err(raise_validation_error(ruby, error, Some(node), None))
|
|
1403
1218
|
}
|
|
1404
1219
|
}
|
|
1405
1220
|
}
|