schemurai 2.3.0 → 3.0.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/README.md +67 -4
- data/ext/schemurai_native/Cargo.lock +1 -1
- data/ext/schemurai_native/Cargo.toml +1 -1
- data/ext/schemurai_native/glue.c +155 -14
- data/ext/schemurai_native/src/budget.rs +101 -0
- data/ext/schemurai_native/src/diagnostics.rs +395 -225
- data/ext/schemurai_native/src/enumeration.rs +154 -0
- data/ext/schemurai_native/src/evaluation.rs +219 -0
- data/ext/schemurai_native/src/graph.rs +50 -0
- data/ext/schemurai_native/src/instance_graph.rs +84 -0
- data/ext/schemurai_native/src/lib.rs +947 -184
- data/ext/schemurai_native/src/memo.rs +1149 -0
- data/ext/schemurai_native/src/memo_scope.rs +240 -0
- data/ext/schemurai_native/src/number.rs +7 -8
- data/ext/schemurai_native/src/pattern.rs +157 -14
- data/ext/schemurai_native/src/scope.rs +243 -33
- data/ext/schemurai_native/src/structural.rs +86 -0
- data/ext/schemurai_native/src/tracking.rs +245 -90
- data/lib/schemurai/backend.rb +1 -0
- data/lib/schemurai/evaluator.rb +45 -3
- data/lib/schemurai/limit_budget.rb +78 -0
- data/lib/schemurai/limit_exceeded.rb +18 -0
- data/lib/schemurai/limited_evaluator.rb +100 -0
- data/lib/schemurai/native/evaluator.rb +32 -5
- data/lib/schemurai/schema_domain.rb +12 -7
- data/lib/schemurai/schema_graph.rb +10 -1
- data/lib/schemurai/unique_items.rb +2 -0
- data/lib/schemurai/version.rb +1 -1
- data/lib/schemurai/vm/compiler.rb +7 -3
- data/lib/schemurai/vm/evaluator.rb +50 -3
- data/lib/schemurai/vm/limited_evaluator.rb +97 -0
- data/lib/schemurai.rb +74 -27
- data/sig/schemurai.rbs +20 -12
- metadata +16 -4
|
@@ -127,6 +127,7 @@ module Schemurai
|
|
|
127
127
|
end
|
|
128
128
|
instruction_index = 1
|
|
129
129
|
while (opcode = code[instruction_index])
|
|
130
|
+
@budget&.checkpoint
|
|
130
131
|
operand = code[instruction_index + 1]
|
|
131
132
|
case opcode
|
|
132
133
|
when :boolean
|
|
@@ -178,6 +179,7 @@ module Schemurai
|
|
|
178
179
|
when :oneOf
|
|
179
180
|
matches = 0
|
|
180
181
|
operand.each do |child|
|
|
182
|
+
@budget&.checkpoint
|
|
181
183
|
matches += 1 if evaluate_valid(child, instance)
|
|
182
184
|
return false if matches > 1
|
|
183
185
|
end
|
|
@@ -237,6 +239,7 @@ module Schemurai
|
|
|
237
239
|
|
|
238
240
|
instruction_index = 1
|
|
239
241
|
while (opcode = code[instruction_index])
|
|
242
|
+
@budget&.checkpoint
|
|
240
243
|
operand = code[instruction_index + 1]
|
|
241
244
|
case opcode
|
|
242
245
|
when :boolean
|
|
@@ -345,6 +348,7 @@ module Schemurai
|
|
|
345
348
|
return target unless rules.fragment == "" && target.recursive_anchor?
|
|
346
349
|
|
|
347
350
|
@dynamic_scope&.each do |resource|
|
|
351
|
+
@budget&.checkpoint
|
|
348
352
|
compiled = @compiler.compile(resource.root)
|
|
349
353
|
return compiled if compiled.recursive_anchor?
|
|
350
354
|
end
|
|
@@ -359,6 +363,7 @@ module Schemurai
|
|
|
359
363
|
return target unless target.dynamic_anchor == fragment
|
|
360
364
|
|
|
361
365
|
Array(@dynamic_scope).each do |resource|
|
|
366
|
+
@budget&.checkpoint
|
|
362
367
|
dynamic = @graph.dynamic_anchor(resource, fragment)
|
|
363
368
|
return @compiler.compile(dynamic) if dynamic
|
|
364
369
|
end
|
|
@@ -560,6 +565,7 @@ module Schemurai
|
|
|
560
565
|
|
|
561
566
|
if (prefix_items = rules.prefix_items)
|
|
562
567
|
prefix_items.each_with_index do |child, index|
|
|
568
|
+
@budget&.checkpoint
|
|
563
569
|
break if index >= length
|
|
564
570
|
return false unless evaluate_valid(child, value[index])
|
|
565
571
|
end
|
|
@@ -568,12 +574,14 @@ module Schemurai
|
|
|
568
574
|
items = rules.items
|
|
569
575
|
if rules.items_list
|
|
570
576
|
items.each_with_index do |child, index|
|
|
577
|
+
@budget&.checkpoint
|
|
571
578
|
break if index >= length
|
|
572
579
|
return false unless evaluate_valid(child, value[index])
|
|
573
580
|
end
|
|
574
581
|
if length > items.length && (additional = rules.additional)
|
|
575
582
|
index = items.length
|
|
576
583
|
while index < length
|
|
584
|
+
@budget&.checkpoint
|
|
577
585
|
return false unless evaluate_valid(additional, value[index])
|
|
578
586
|
index += 1
|
|
579
587
|
end
|
|
@@ -582,6 +590,7 @@ module Schemurai
|
|
|
582
590
|
start = rules.prefix_items&.length || 0
|
|
583
591
|
index = start
|
|
584
592
|
while index < length
|
|
593
|
+
@budget&.checkpoint
|
|
585
594
|
return false unless evaluate_valid(items, value[index])
|
|
586
595
|
index += 1
|
|
587
596
|
end
|
|
@@ -618,6 +627,7 @@ module Schemurai
|
|
|
618
627
|
|
|
619
628
|
if (prefix_items = rules.prefix_items)
|
|
620
629
|
prefix_items.each_with_index do |child, index|
|
|
630
|
+
@budget&.checkpoint
|
|
621
631
|
break if index >= value.length
|
|
622
632
|
valid = evaluate_child_at(child, value[index], index, "prefixItems", index)
|
|
623
633
|
return Evaluation.invalid if !valid && !@errors
|
|
@@ -628,6 +638,7 @@ module Schemurai
|
|
|
628
638
|
items = rules.items
|
|
629
639
|
if rules.items_list
|
|
630
640
|
items.each_with_index do |child, index|
|
|
641
|
+
@budget&.checkpoint
|
|
631
642
|
break if index >= value.length
|
|
632
643
|
valid = evaluate_child_at(child, value[index], index, "items", index)
|
|
633
644
|
return Evaluation.invalid if !valid && !@errors
|
|
@@ -635,6 +646,7 @@ module Schemurai
|
|
|
635
646
|
end
|
|
636
647
|
if value.length > items.length && (additional = rules.additional)
|
|
637
648
|
(items.length...value.length).each do |index|
|
|
649
|
+
@budget&.checkpoint
|
|
638
650
|
valid = evaluate_child_at(additional, value[index], index, "additionalItems")
|
|
639
651
|
return Evaluation.invalid if !valid && !@errors
|
|
640
652
|
(evaluation ||= tracked_evaluation).record_item(index)
|
|
@@ -643,6 +655,7 @@ module Schemurai
|
|
|
643
655
|
elsif items
|
|
644
656
|
start = rules.prefix_items&.length || 0
|
|
645
657
|
(start...value.length).each do |index|
|
|
658
|
+
@budget&.checkpoint
|
|
646
659
|
valid = evaluate_child_at(items, value[index], index, "items")
|
|
647
660
|
return Evaluation.invalid if !valid && !@errors
|
|
648
661
|
(evaluation ||= tracked_evaluation).record_item(index)
|
|
@@ -653,6 +666,7 @@ module Schemurai
|
|
|
653
666
|
matched = nil
|
|
654
667
|
index = 0
|
|
655
668
|
while index < value.length
|
|
669
|
+
@budget&.checkpoint
|
|
656
670
|
(matched ||= []) << index if trial(contains, value[index]).valid?
|
|
657
671
|
index += 1
|
|
658
672
|
end
|
|
@@ -673,6 +687,7 @@ module Schemurai
|
|
|
673
687
|
prior_items = prior_evaluation.evaluated_items
|
|
674
688
|
index = 0
|
|
675
689
|
while index < value.length
|
|
690
|
+
@budget&.checkpoint
|
|
676
691
|
if prior_items.include?(index) || evaluation&.evaluated_items&.include?(index)
|
|
677
692
|
index += 1
|
|
678
693
|
next
|
|
@@ -692,7 +707,10 @@ module Schemurai
|
|
|
692
707
|
length = value.length
|
|
693
708
|
return false if rules.max_properties && length > rules.max_properties
|
|
694
709
|
return false if rules.min_properties && length < rules.min_properties
|
|
695
|
-
if rules.required && !rules.required.all?
|
|
710
|
+
if rules.required && !rules.required.all? do |name|
|
|
711
|
+
@budget&.checkpoint
|
|
712
|
+
value.key?(name)
|
|
713
|
+
end
|
|
696
714
|
return false
|
|
697
715
|
end
|
|
698
716
|
|
|
@@ -701,6 +719,7 @@ module Schemurai
|
|
|
701
719
|
additional = rules.additional
|
|
702
720
|
unless properties.empty? && patterns.nil? && additional.nil?
|
|
703
721
|
value.each do |name, property_value|
|
|
722
|
+
@budget&.checkpoint
|
|
704
723
|
matched = false
|
|
705
724
|
if (child = properties[name])
|
|
706
725
|
matched = true
|
|
@@ -708,6 +727,7 @@ module Schemurai
|
|
|
708
727
|
end
|
|
709
728
|
if patterns
|
|
710
729
|
patterns.each do |pattern, child|
|
|
730
|
+
@budget&.checkpoint
|
|
711
731
|
next unless ecma_regexp(pattern).match?(name)
|
|
712
732
|
matched = true
|
|
713
733
|
return false unless evaluate_valid(child, property_value)
|
|
@@ -722,9 +742,13 @@ module Schemurai
|
|
|
722
742
|
end
|
|
723
743
|
if (dependencies = rules.dependencies)
|
|
724
744
|
dependencies.each do |name, dependency|
|
|
745
|
+
@budget&.checkpoint
|
|
725
746
|
next unless value.key?(name)
|
|
726
747
|
if dependency.is_a?(Array)
|
|
727
|
-
return false unless dependency.all?
|
|
748
|
+
return false unless dependency.all? do |required_name|
|
|
749
|
+
@budget&.checkpoint
|
|
750
|
+
value.key?(required_name)
|
|
751
|
+
end
|
|
728
752
|
else
|
|
729
753
|
return false unless evaluate_valid(dependency, value)
|
|
730
754
|
end
|
|
@@ -732,12 +756,17 @@ module Schemurai
|
|
|
732
756
|
end
|
|
733
757
|
if (dependent_required = rules.dependent_required)
|
|
734
758
|
dependent_required.each do |name, required_names|
|
|
759
|
+
@budget&.checkpoint
|
|
735
760
|
next unless value.key?(name)
|
|
736
|
-
return false unless required_names.all?
|
|
761
|
+
return false unless required_names.all? do |required_name|
|
|
762
|
+
@budget&.checkpoint
|
|
763
|
+
value.key?(required_name)
|
|
764
|
+
end
|
|
737
765
|
end
|
|
738
766
|
end
|
|
739
767
|
if (dependent_schemas = rules.dependent_schemas)
|
|
740
768
|
dependent_schemas.each do |name, child|
|
|
769
|
+
@budget&.checkpoint
|
|
741
770
|
next unless value.key?(name)
|
|
742
771
|
return false unless evaluate_valid(child, value)
|
|
743
772
|
end
|
|
@@ -763,6 +792,7 @@ module Schemurai
|
|
|
763
792
|
end
|
|
764
793
|
if (required = rules.required)
|
|
765
794
|
required.each do |name|
|
|
795
|
+
@budget&.checkpoint
|
|
766
796
|
next if value.key?(name)
|
|
767
797
|
return Evaluation.invalid unless @errors
|
|
768
798
|
|
|
@@ -773,6 +803,7 @@ module Schemurai
|
|
|
773
803
|
properties = rules.properties
|
|
774
804
|
patterns = rules.patterns
|
|
775
805
|
value.each do |name, property_value|
|
|
806
|
+
@budget&.checkpoint
|
|
776
807
|
matched = false
|
|
777
808
|
if (child = properties[name])
|
|
778
809
|
matched = true
|
|
@@ -782,6 +813,7 @@ module Schemurai
|
|
|
782
813
|
end
|
|
783
814
|
if patterns
|
|
784
815
|
patterns.each do |pattern, child|
|
|
816
|
+
@budget&.checkpoint
|
|
785
817
|
next unless ecma_regexp(pattern).match?(name)
|
|
786
818
|
matched = true
|
|
787
819
|
valid = evaluate_child_at(child, property_value, name, "patternProperties", pattern)
|
|
@@ -798,15 +830,18 @@ module Schemurai
|
|
|
798
830
|
|
|
799
831
|
if (property_names = rules.property_names)
|
|
800
832
|
value.each_key do |name|
|
|
833
|
+
@budget&.checkpoint
|
|
801
834
|
valid = evaluate_child_at(property_names, name, name, "propertyNames")
|
|
802
835
|
return Evaluation.invalid if !valid && !@errors
|
|
803
836
|
end
|
|
804
837
|
end
|
|
805
838
|
if (dependencies = rules.dependencies)
|
|
806
839
|
dependencies.each do |name, dependency|
|
|
840
|
+
@budget&.checkpoint
|
|
807
841
|
next unless value.key?(name)
|
|
808
842
|
if dependency.is_a?(Array)
|
|
809
843
|
dependency.each do |required_name|
|
|
844
|
+
@budget&.checkpoint
|
|
810
845
|
unless value.key?(required_name)
|
|
811
846
|
return Evaluation.invalid unless @errors
|
|
812
847
|
|
|
@@ -817,6 +852,7 @@ module Schemurai
|
|
|
817
852
|
result = evaluate_at(dependency, value, MISSING_SEGMENT, "dependencies", name)
|
|
818
853
|
if result.valid? && !result.evaluated_properties.empty?
|
|
819
854
|
result.evaluated_properties.each do |property|
|
|
855
|
+
@budget&.checkpoint
|
|
820
856
|
(evaluation ||= tracked_evaluation).record_property(property)
|
|
821
857
|
end
|
|
822
858
|
end
|
|
@@ -825,8 +861,10 @@ module Schemurai
|
|
|
825
861
|
end
|
|
826
862
|
if (dependent_required = rules.dependent_required)
|
|
827
863
|
dependent_required.each do |name, required_names|
|
|
864
|
+
@budget&.checkpoint
|
|
828
865
|
next unless value.key?(name)
|
|
829
866
|
required_names.each do |required_name|
|
|
867
|
+
@budget&.checkpoint
|
|
830
868
|
unless value.key?(required_name)
|
|
831
869
|
return Evaluation.invalid unless @errors
|
|
832
870
|
|
|
@@ -837,10 +875,12 @@ module Schemurai
|
|
|
837
875
|
end
|
|
838
876
|
if (dependent_schemas = rules.dependent_schemas)
|
|
839
877
|
dependent_schemas.each do |name, child|
|
|
878
|
+
@budget&.checkpoint
|
|
840
879
|
next unless value.key?(name)
|
|
841
880
|
result = evaluate_at(child, value, MISSING_SEGMENT, "dependentSchemas", name)
|
|
842
881
|
if result.valid? && !result.evaluated_properties.empty?
|
|
843
882
|
result.evaluated_properties.each do |property|
|
|
883
|
+
@budget&.checkpoint
|
|
844
884
|
(evaluation ||= tracked_evaluation).record_property(property)
|
|
845
885
|
end
|
|
846
886
|
end
|
|
@@ -849,6 +889,7 @@ module Schemurai
|
|
|
849
889
|
if (unevaluated = rules.unevaluated)
|
|
850
890
|
prior_properties = prior_evaluation.evaluated_properties
|
|
851
891
|
value.each_key do |name|
|
|
892
|
+
@budget&.checkpoint
|
|
852
893
|
next if prior_properties.include?(name) || evaluation&.evaluated_properties&.include?(name)
|
|
853
894
|
|
|
854
895
|
valid = evaluate_child_at(unevaluated, value[name], name, "unevaluatedProperties")
|
|
@@ -867,6 +908,7 @@ module Schemurai
|
|
|
867
908
|
when :allOf
|
|
868
909
|
index = 0
|
|
869
910
|
while index < operand.length
|
|
911
|
+
@budget&.checkpoint
|
|
870
912
|
evaluation = evaluation.merge(evaluate_at(operand[index], value, MISSING_SEGMENT, "allOf", index))
|
|
871
913
|
index += 1
|
|
872
914
|
end
|
|
@@ -874,6 +916,7 @@ module Schemurai
|
|
|
874
916
|
matches = 0
|
|
875
917
|
index = 0
|
|
876
918
|
while index < operand.length
|
|
919
|
+
@budget&.checkpoint
|
|
877
920
|
result = trial(operand[index], value)
|
|
878
921
|
if result.valid?
|
|
879
922
|
matches += 1
|
|
@@ -887,6 +930,7 @@ module Schemurai
|
|
|
887
930
|
matched_evaluation = nil
|
|
888
931
|
index = 0
|
|
889
932
|
while index < operand.length
|
|
933
|
+
@budget&.checkpoint
|
|
890
934
|
result = trial(operand[index], value)
|
|
891
935
|
if result.valid?
|
|
892
936
|
matches += 1
|
|
@@ -989,6 +1033,7 @@ module Schemurai
|
|
|
989
1033
|
case left
|
|
990
1034
|
when Hash
|
|
991
1035
|
left.length == right.length && left.all? do |key, value|
|
|
1036
|
+
@budget&.checkpoint
|
|
992
1037
|
right.key?(key) && json_equal?(value, right[key])
|
|
993
1038
|
end
|
|
994
1039
|
when Array
|
|
@@ -996,6 +1041,7 @@ module Schemurai
|
|
|
996
1041
|
|
|
997
1042
|
index = 0
|
|
998
1043
|
while index < left.length
|
|
1044
|
+
@budget&.checkpoint
|
|
999
1045
|
return false unless json_equal?(left[index], right[index])
|
|
1000
1046
|
index += 1
|
|
1001
1047
|
end
|
|
@@ -1024,6 +1070,7 @@ module Schemurai
|
|
|
1024
1070
|
escaped = false
|
|
1025
1071
|
in_class = false
|
|
1026
1072
|
pattern.each_char do |character|
|
|
1073
|
+
@budget&.checkpoint
|
|
1027
1074
|
if escaped
|
|
1028
1075
|
translated << case character
|
|
1029
1076
|
when "d" then in_class ? "0-9" : "[0-9]"
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../limit_exceeded"
|
|
4
|
+
require_relative "../limit_budget"
|
|
5
|
+
require_relative "evaluator"
|
|
6
|
+
|
|
7
|
+
module Schemurai
|
|
8
|
+
module VM
|
|
9
|
+
# VM execution with invocation-local limits; no Ruby evaluator delegation.
|
|
10
|
+
class LimitedEvaluator < Evaluator
|
|
11
|
+
def validate(instance, max_errors: nil, timeout: nil, budget: nil)
|
|
12
|
+
@budget = budget || Internal::LimitBudget.new(max_errors: max_errors, timeout: timeout)
|
|
13
|
+
result = super(instance)
|
|
14
|
+
@budget.check_deadline!
|
|
15
|
+
result
|
|
16
|
+
ensure
|
|
17
|
+
@budget = nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def valid?(instance, max_errors: nil, timeout: nil, budget: nil)
|
|
21
|
+
@budget = budget || Internal::LimitBudget.new(max_errors: max_errors, timeout: timeout)
|
|
22
|
+
result = super(instance)
|
|
23
|
+
@budget.check_deadline!
|
|
24
|
+
result
|
|
25
|
+
ensure
|
|
26
|
+
@budget = nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private def evaluate(node, instance)
|
|
30
|
+
@budget.checkpoint
|
|
31
|
+
super
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private def evaluate_valid(node, instance)
|
|
35
|
+
@budget.checkpoint
|
|
36
|
+
super
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private def add_error(keyword, message = nil, append_keyword: true, &block)
|
|
40
|
+
@budget.reserve_error if @errors
|
|
41
|
+
super
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private def valid_string?(node, value)
|
|
45
|
+
@budget.check_deadline!
|
|
46
|
+
result = super
|
|
47
|
+
@budget.check_deadline!
|
|
48
|
+
result
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private def check_string(node, value)
|
|
52
|
+
@budget.check_deadline!
|
|
53
|
+
result = super
|
|
54
|
+
@budget.check_deadline!
|
|
55
|
+
result
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private def decimal(value)
|
|
59
|
+
@budget.check_deadline!
|
|
60
|
+
result = super
|
|
61
|
+
@budget.check_deadline!
|
|
62
|
+
result
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private def ecma_regexp(pattern)
|
|
66
|
+
@budget.check_deadline!
|
|
67
|
+
result = super
|
|
68
|
+
@budget.check_deadline!
|
|
69
|
+
result
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private def valid_content?(schema, value)
|
|
73
|
+
@budget.check_deadline!
|
|
74
|
+
result = super
|
|
75
|
+
@budget.check_deadline!
|
|
76
|
+
result
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private def check_content(schema, value)
|
|
80
|
+
@budget.check_deadline!
|
|
81
|
+
result = super
|
|
82
|
+
@budget.check_deadline!
|
|
83
|
+
result
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private def json_equal?(left, right)
|
|
87
|
+
@budget.checkpoint
|
|
88
|
+
super
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
private def json_fingerprint(value)
|
|
92
|
+
@budget.checkpoint
|
|
93
|
+
super
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
data/lib/schemurai.rb
CHANGED
|
@@ -11,6 +11,8 @@ module Schemurai
|
|
|
11
11
|
# Base class for errors raised by Schemurai.
|
|
12
12
|
class Error < StandardError; end
|
|
13
13
|
|
|
14
|
+
require_relative "schemurai/limited_evaluator"
|
|
15
|
+
|
|
14
16
|
# Raised when a schema or URI cannot be resolved from a registry.
|
|
15
17
|
class ResolutionError < Error; end
|
|
16
18
|
|
|
@@ -96,27 +98,31 @@ module Schemurai
|
|
|
96
98
|
# Compiles +schema+ and returns a Validator.
|
|
97
99
|
#
|
|
98
100
|
# +base_uri+ supplies the schema's base URI. Pass +content: true+ or
|
|
99
|
-
# +format: true+ to enable the corresponding optional assertions.
|
|
100
|
-
|
|
101
|
+
# +format: true+ to enable the corresponding optional assertions. +max_errors:+ and +timeout:+
|
|
102
|
+
# configure per-call validation caps; schema compilation is not bounded.
|
|
103
|
+
def compile(schema, base_uri: nil, content: false, format: false, max_errors: nil, timeout: nil)
|
|
101
104
|
raise Error, "cannot compile schemas after the registry is made shareable" if shareable?
|
|
102
105
|
|
|
103
106
|
validate_schema!(schema) if validate_schema?
|
|
104
107
|
root = @graph.compile(schema, base_uri: base_uri)
|
|
105
|
-
build_validator(root, content: content, format: format)
|
|
108
|
+
build_validator(root, content: content, format: format, max_errors: max_errors, timeout: timeout)
|
|
106
109
|
end
|
|
107
110
|
|
|
108
111
|
# Validates +schema+ against its declared meta-schema and returns a Result.
|
|
109
112
|
# Schemas without +$schema+ are validated as Draft 7.
|
|
110
|
-
def validate_schema(schema)
|
|
113
|
+
def validate_schema(schema, max_errors: nil, timeout: nil)
|
|
111
114
|
raise Error, "cannot validate schemas after the registry is made shareable" if shareable?
|
|
112
115
|
|
|
113
116
|
root = @graph.meta_schema_root(schema)
|
|
114
|
-
build_validator(root, content: false, format: false).validate(schema)
|
|
117
|
+
build_validator(root, content: false, format: false, max_errors: max_errors, timeout: timeout).validate(schema)
|
|
115
118
|
end
|
|
116
119
|
|
|
117
120
|
# Returns whether +schema+ conforms to its declared meta-schema.
|
|
118
|
-
def valid_schema?(schema)
|
|
119
|
-
|
|
121
|
+
def valid_schema?(schema, max_errors: nil, timeout: nil)
|
|
122
|
+
raise Error, "cannot validate schemas after the registry is made shareable" if shareable?
|
|
123
|
+
|
|
124
|
+
root = @graph.meta_schema_root(schema)
|
|
125
|
+
build_validator(root, content: false, format: false, max_errors: max_errors, timeout: timeout).valid?(schema)
|
|
120
126
|
end
|
|
121
127
|
|
|
122
128
|
# Resolves and freezes registered schemas, then makes the registry Ractor-shareable.
|
|
@@ -127,6 +133,13 @@ module Schemurai
|
|
|
127
133
|
|
|
128
134
|
@graph.make_shareable
|
|
129
135
|
@compiler&.compile_all
|
|
136
|
+
if @backend == :native
|
|
137
|
+
programs = @graph.nodes.map { |node| @compiler.compile(node) }
|
|
138
|
+
# Build every option variant before the registry becomes immutable.
|
|
139
|
+
@native_programs = [false, true].product([false, true]).to_h do |format, content|
|
|
140
|
+
[[format, content], Native::Program.new(programs, @compiler, format, content).make_shareable]
|
|
141
|
+
end
|
|
142
|
+
end
|
|
130
143
|
Ractor.make_shareable(self)
|
|
131
144
|
end
|
|
132
145
|
|
|
@@ -138,24 +151,36 @@ module Schemurai
|
|
|
138
151
|
# Returns a Validator for the registered resource at +uri+.
|
|
139
152
|
#
|
|
140
153
|
# The registry must first be made shareable with #make_shareable.
|
|
141
|
-
def validator_for(uri, content: false, format: false)
|
|
154
|
+
def validator_for(uri, content: false, format: false, max_errors: nil, timeout: nil)
|
|
142
155
|
raise Error, "make_shareable must be called before retrieving validators by URI" unless shareable?
|
|
143
156
|
|
|
144
157
|
root = @graph.node_at(uri)
|
|
145
158
|
raise ResolutionError, "unregistered schema URI #{uri.inspect}" unless root
|
|
146
159
|
|
|
147
|
-
build_validator(root, content: content, format: format)
|
|
160
|
+
build_validator(root, content: content, format: format, max_errors: max_errors, timeout: timeout)
|
|
148
161
|
end
|
|
149
162
|
|
|
150
|
-
private def build_validator(root, content:, format:)
|
|
163
|
+
private def build_validator(root, content:, format:, max_errors: nil, timeout: nil)
|
|
151
164
|
evaluator = if @compiler
|
|
152
165
|
program = @compiler.compile(root)
|
|
153
166
|
evaluator_class = (@backend == :native) ? Native::Evaluator : VM::Evaluator
|
|
154
|
-
|
|
167
|
+
if @native_programs
|
|
168
|
+
compiled = @native_programs.fetch([!!format, !!content])
|
|
169
|
+
Native::Evaluator.new(@graph, @compiler, program, content: content, format: format, compiled: compiled)
|
|
170
|
+
else
|
|
171
|
+
evaluator_class.new(@graph, @compiler, program, content: content, format: format)
|
|
172
|
+
end
|
|
155
173
|
else
|
|
156
174
|
Internal::Evaluator.new(@graph, root, content: content, format: format)
|
|
157
175
|
end
|
|
158
|
-
|
|
176
|
+
bounded_evaluator = lambda do
|
|
177
|
+
case @backend
|
|
178
|
+
when :native then evaluator
|
|
179
|
+
when :vm then VM::LimitedEvaluator.new(@graph, @compiler, program, content: content, format: format)
|
|
180
|
+
else Internal::LimitedEvaluator.new(@graph, root, content: content, format: format)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
Validator.new(evaluator, max_errors: max_errors, timeout: timeout, bounded_evaluator: bounded_evaluator)
|
|
159
184
|
end
|
|
160
185
|
|
|
161
186
|
private def validate_schema!(schema)
|
|
@@ -174,8 +199,12 @@ module Schemurai
|
|
|
174
199
|
#
|
|
175
200
|
# Applications should normally create validators through Schemurai.compile
|
|
176
201
|
# or SchemaRegistry#compile.
|
|
177
|
-
def initialize(evaluator)
|
|
202
|
+
def initialize(evaluator, max_errors: nil, timeout: nil, bounded_evaluator: nil)
|
|
178
203
|
@evaluator = evaluator
|
|
204
|
+
Internal::LimitBudget.validate_options!(max_errors, timeout)
|
|
205
|
+
@max_errors = max_errors
|
|
206
|
+
@timeout = timeout
|
|
207
|
+
@bounded_evaluator = bounded_evaluator
|
|
179
208
|
end
|
|
180
209
|
|
|
181
210
|
# The backend reported by the evaluator, +:ruby+, +:vm+, or +:native+.
|
|
@@ -184,13 +213,28 @@ module Schemurai
|
|
|
184
213
|
end
|
|
185
214
|
|
|
186
215
|
# Validates +instance+ and returns a Result containing all reported errors.
|
|
187
|
-
|
|
188
|
-
|
|
216
|
+
# +max_errors:+ and +timeout:+ override compiled defaults. Raises LimitExceeded when evaluation
|
|
217
|
+
# cannot complete; no partial Result is returned.
|
|
218
|
+
def validate(instance, max_errors: @max_errors, timeout: @timeout)
|
|
219
|
+
Internal::LimitBudget.validate_options!(max_errors, timeout)
|
|
220
|
+
return @evaluator.validate(instance) if max_errors.nil? && timeout.nil?
|
|
221
|
+
|
|
222
|
+
limited_evaluator.validate(instance, max_errors: max_errors, timeout: timeout)
|
|
189
223
|
end
|
|
190
224
|
|
|
191
225
|
# Returns whether +instance+ is valid without building a detailed error list.
|
|
192
|
-
|
|
193
|
-
|
|
226
|
+
# The timeout applies; the diagnostic count limit is ignored.
|
|
227
|
+
def valid?(instance, max_errors: @max_errors, timeout: @timeout)
|
|
228
|
+
Internal::LimitBudget.validate_options!(max_errors, timeout)
|
|
229
|
+
return @evaluator.valid?(instance) if timeout.nil?
|
|
230
|
+
|
|
231
|
+
limited_evaluator.valid?(instance, timeout: timeout)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
private def limited_evaluator
|
|
235
|
+
raise ArgumentError, "this manually constructed Validator has no bounded evaluator" unless @bounded_evaluator
|
|
236
|
+
|
|
237
|
+
@bounded_evaluator.call
|
|
194
238
|
end
|
|
195
239
|
end
|
|
196
240
|
|
|
@@ -204,31 +248,32 @@ module Schemurai
|
|
|
204
248
|
# +schemas+ maps external URI strings to schemas. +base_uri+ supplies the
|
|
205
249
|
# schema's base URI. Optional content and format assertions are disabled by
|
|
206
250
|
# default. +backend+ accepts +:ruby+, +:vm+, +:native+, or +:default+.
|
|
207
|
-
module_function def compile(schema, schemas: {}, base_uri: nil, content: false, format: false, validate_schema: false, backend: Backend.requested)
|
|
251
|
+
module_function def compile(schema, schemas: {}, base_uri: nil, content: false, format: false, validate_schema: false, backend: Backend.requested, max_errors: nil, timeout: nil)
|
|
208
252
|
SchemaRegistry.new(schemas: schemas, validate_schema: validate_schema, backend: backend).compile(
|
|
209
253
|
schema,
|
|
210
254
|
base_uri: base_uri,
|
|
211
255
|
content: content,
|
|
212
|
-
format: format
|
|
256
|
+
format: format,
|
|
257
|
+
max_errors: max_errors, timeout: timeout
|
|
213
258
|
)
|
|
214
259
|
end
|
|
215
260
|
|
|
216
261
|
# Validates +schema+ against its declared meta-schema and returns a Result.
|
|
217
262
|
# Schemas without +$schema+ are validated as Draft 7.
|
|
218
|
-
module_function def validate_schema(schema, schemas: {}, backend: Backend.requested)
|
|
219
|
-
SchemaRegistry.new(schemas: schemas, backend: backend).validate_schema(schema)
|
|
263
|
+
module_function def validate_schema(schema, schemas: {}, backend: Backend.requested, max_errors: nil, timeout: nil)
|
|
264
|
+
SchemaRegistry.new(schemas: schemas, backend: backend).validate_schema(schema, max_errors: max_errors, timeout: timeout)
|
|
220
265
|
end
|
|
221
266
|
|
|
222
267
|
# Returns whether +schema+ conforms to its declared meta-schema.
|
|
223
|
-
module_function def valid_schema?(schema, schemas: {}, backend: Backend.requested)
|
|
224
|
-
|
|
268
|
+
module_function def valid_schema?(schema, schemas: {}, backend: Backend.requested, max_errors: nil, timeout: nil)
|
|
269
|
+
SchemaRegistry.new(schemas: schemas, backend: backend).valid_schema?(schema, max_errors: max_errors, timeout: timeout)
|
|
225
270
|
end
|
|
226
271
|
|
|
227
272
|
# Validates +instance+ against +schema+ and returns a Result.
|
|
228
273
|
#
|
|
229
274
|
# This convenience method compiles the schema for each call. Use .compile
|
|
230
275
|
# when validating multiple instances against the same schema.
|
|
231
|
-
module_function def validate(schema, instance, schemas: {}, base_uri: nil, content: false, format: false, validate_schema: false, backend: Backend.requested)
|
|
276
|
+
module_function def validate(schema, instance, schemas: {}, base_uri: nil, content: false, format: false, validate_schema: false, backend: Backend.requested, max_errors: nil, timeout: nil)
|
|
232
277
|
compile(
|
|
233
278
|
schema,
|
|
234
279
|
schemas: schemas,
|
|
@@ -236,7 +281,8 @@ module Schemurai
|
|
|
236
281
|
content: content,
|
|
237
282
|
format: format,
|
|
238
283
|
validate_schema: validate_schema,
|
|
239
|
-
backend: backend
|
|
284
|
+
backend: backend,
|
|
285
|
+
max_errors: max_errors, timeout: timeout
|
|
240
286
|
).validate(instance)
|
|
241
287
|
end
|
|
242
288
|
|
|
@@ -244,7 +290,7 @@ module Schemurai
|
|
|
244
290
|
#
|
|
245
291
|
# This convenience method compiles the schema for each call. Use .compile
|
|
246
292
|
# when validating multiple instances against the same schema.
|
|
247
|
-
module_function def valid?(schema, instance, schemas: {}, base_uri: nil, content: false, format: false, validate_schema: false, backend: Backend.requested)
|
|
293
|
+
module_function def valid?(schema, instance, schemas: {}, base_uri: nil, content: false, format: false, validate_schema: false, backend: Backend.requested, max_errors: nil, timeout: nil)
|
|
248
294
|
compile(
|
|
249
295
|
schema,
|
|
250
296
|
schemas: schemas,
|
|
@@ -252,7 +298,8 @@ module Schemurai
|
|
|
252
298
|
content: content,
|
|
253
299
|
format: format,
|
|
254
300
|
validate_schema: validate_schema,
|
|
255
|
-
backend: backend
|
|
301
|
+
backend: backend,
|
|
302
|
+
max_errors: max_errors, timeout: timeout
|
|
256
303
|
).valid?(instance)
|
|
257
304
|
end
|
|
258
305
|
|
data/sig/schemurai.rbs
CHANGED
|
@@ -19,6 +19,14 @@ module Schemurai
|
|
|
19
19
|
class Error < StandardError
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
class LimitExceeded < Error
|
|
23
|
+
attr_reader limit: Symbol
|
|
24
|
+
attr_reader maximum: Integer | Float
|
|
25
|
+
attr_reader attempted: Integer | Float
|
|
26
|
+
|
|
27
|
+
def initialize: (Symbol limit, Integer | Float maximum, Integer | Float attempted) -> void
|
|
28
|
+
end
|
|
29
|
+
|
|
22
30
|
class ResolutionError < Error
|
|
23
31
|
end
|
|
24
32
|
|
|
@@ -63,10 +71,10 @@ module Schemurai
|
|
|
63
71
|
end
|
|
64
72
|
|
|
65
73
|
class Validator
|
|
66
|
-
def initialize: (_Evaluator evaluator) -> void
|
|
74
|
+
def initialize: (_Evaluator evaluator, ?max_errors: Integer?, ?timeout: (Integer | Float)?, ?bounded_evaluator: (^() -> untyped)?) -> void
|
|
67
75
|
def backend: () -> backend
|
|
68
|
-
def validate: (json_value instance) -> Result
|
|
69
|
-
def valid?: (json_value instance) -> bool
|
|
76
|
+
def validate: (json_value instance, ?max_errors: Integer?, ?timeout: (Integer | Float)?) -> Result
|
|
77
|
+
def valid?: (json_value instance, ?max_errors: Integer?, ?timeout: (Integer | Float)?) -> bool
|
|
70
78
|
end
|
|
71
79
|
|
|
72
80
|
class SchemaRegistry
|
|
@@ -76,25 +84,25 @@ module Schemurai
|
|
|
76
84
|
|
|
77
85
|
def validate_schema?: () -> bool
|
|
78
86
|
|
|
79
|
-
def compile: (schema schema, ?base_uri: String?, ?content: bool, ?format: bool) -> Validator
|
|
87
|
+
def compile: (schema schema, ?base_uri: String?, ?content: bool, ?format: bool, ?max_errors: Integer?, ?timeout: (Integer | Float)?) -> Validator
|
|
80
88
|
|
|
81
|
-
def validate_schema: (schema schema) -> Result
|
|
82
|
-
def valid_schema?: (schema schema) -> bool
|
|
89
|
+
def validate_schema: (schema schema, ?max_errors: Integer?, ?timeout: (Integer | Float)?) -> Result
|
|
90
|
+
def valid_schema?: (schema schema, ?max_errors: Integer?, ?timeout: (Integer | Float)?) -> bool
|
|
83
91
|
def make_shareable: () -> self
|
|
84
92
|
def shareable?: () -> bool
|
|
85
93
|
|
|
86
|
-
def validator_for: (String uri, ?content: bool, ?format: bool) -> Validator
|
|
94
|
+
def validator_for: (String uri, ?content: bool, ?format: bool, ?max_errors: Integer?, ?timeout: (Integer | Float)?) -> Validator
|
|
87
95
|
end
|
|
88
96
|
|
|
89
97
|
def self.backend: () -> backend
|
|
90
98
|
|
|
91
|
-
def self.compile: (schema schema, ?schemas: schemas, ?base_uri: String?, ?content: bool, ?format: bool, ?validate_schema: bool, ?backend: backend_selection) -> Validator
|
|
99
|
+
def self.compile: (schema schema, ?schemas: schemas, ?base_uri: String?, ?content: bool, ?format: bool, ?validate_schema: bool, ?backend: backend_selection, ?max_errors: Integer?, ?timeout: (Integer | Float)?) -> Validator
|
|
92
100
|
|
|
93
|
-
def self.validate_schema: (schema schema, ?schemas: schemas, ?backend: backend_selection) -> Result
|
|
101
|
+
def self.validate_schema: (schema schema, ?schemas: schemas, ?backend: backend_selection, ?max_errors: Integer?, ?timeout: (Integer | Float)?) -> Result
|
|
94
102
|
|
|
95
|
-
def self.valid_schema?: (schema schema, ?schemas: schemas, ?backend: backend_selection) -> bool
|
|
103
|
+
def self.valid_schema?: (schema schema, ?schemas: schemas, ?backend: backend_selection, ?max_errors: Integer?, ?timeout: (Integer | Float)?) -> bool
|
|
96
104
|
|
|
97
|
-
def self.validate: (schema schema, json_value instance, ?schemas: schemas, ?base_uri: String?, ?content: bool, ?format: bool, ?validate_schema: bool, ?backend: backend_selection) -> Result
|
|
105
|
+
def self.validate: (schema schema, json_value instance, ?schemas: schemas, ?base_uri: String?, ?content: bool, ?format: bool, ?validate_schema: bool, ?backend: backend_selection, ?max_errors: Integer?, ?timeout: (Integer | Float)?) -> Result
|
|
98
106
|
|
|
99
|
-
def self.valid?: (schema schema, json_value instance, ?schemas: schemas, ?base_uri: String?, ?content: bool, ?format: bool, ?validate_schema: bool, ?backend: backend_selection) -> bool
|
|
107
|
+
def self.valid?: (schema schema, json_value instance, ?schemas: schemas, ?base_uri: String?, ?content: bool, ?format: bool, ?validate_schema: bool, ?backend: backend_selection, ?max_errors: Integer?, ?timeout: (Integer | Float)?) -> bool
|
|
100
108
|
end
|