schemurai 2.2.3 → 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 +81 -7
- data/ext/schemurai_native/Cargo.lock +99 -0
- data/ext/schemurai_native/Cargo.toml +21 -0
- data/ext/schemurai_native/build.rs +35 -0
- data/ext/schemurai_native/extconf.rb +63 -0
- data/ext/schemurai_native/glue.c +780 -0
- data/ext/schemurai_native/protocol.def +63 -0
- data/ext/schemurai_native/protocol.h +10 -0
- data/ext/schemurai_native/src/budget.rs +101 -0
- data/ext/schemurai_native/src/content.rs +574 -0
- data/ext/schemurai_native/src/diagnostics.rs +1135 -0
- data/ext/schemurai_native/src/enumeration.rs +154 -0
- data/ext/schemurai_native/src/evaluation.rs +219 -0
- data/ext/schemurai_native/src/format.rs +357 -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 +3139 -0
- 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 +1054 -0
- data/ext/schemurai_native/src/pattern.rs +431 -0
- data/ext/schemurai_native/src/scope.rs +420 -0
- data/ext/schemurai_native/src/structural.rs +86 -0
- data/ext/schemurai_native/src/tracking.rs +687 -0
- data/lib/schemurai/backend.rb +3 -1
- 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/diagnostics.rb +52 -0
- data/lib/schemurai/native/evaluator.rb +59 -0
- 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 +81 -33
- data/sig/schemurai.rbs +21 -13
- metadata +36 -6
data/lib/schemurai/version.rb
CHANGED
|
@@ -391,12 +391,16 @@ module Schemurai
|
|
|
391
391
|
compiled.freeze unless compiled.empty?
|
|
392
392
|
end
|
|
393
393
|
|
|
394
|
-
|
|
394
|
+
# Keep aliases inside one operand while copying away from caller-owned
|
|
395
|
+
# containers. A fresh identity table also rechecks later compilations.
|
|
396
|
+
private def snapshot(value, copies = {}.compare_by_identity)
|
|
397
|
+
return copies[value] if copies.key?(value)
|
|
398
|
+
|
|
395
399
|
case value
|
|
396
400
|
when Hash
|
|
397
|
-
value.to_h { |key, item| [snapshot(key), snapshot(item)] }.freeze
|
|
401
|
+
copies[value] = value.to_h { |key, item| [snapshot(key, copies), snapshot(item, copies)] }.freeze
|
|
398
402
|
when Array
|
|
399
|
-
value.map { |item| snapshot(item) }.freeze
|
|
403
|
+
copies[value] = value.map { |item| snapshot(item, copies) }.freeze
|
|
400
404
|
when String
|
|
401
405
|
-value
|
|
402
406
|
else
|
|
@@ -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
|
|
|
@@ -72,19 +74,19 @@ module Schemurai
|
|
|
72
74
|
# Register every external schema with +schemas:+ before compiling. A registry
|
|
73
75
|
# can be made shareable once registration and compilation are complete.
|
|
74
76
|
class SchemaRegistry
|
|
75
|
-
# The selected backend,
|
|
77
|
+
# The selected backend, +:ruby+, +:vm+, or +:native+.
|
|
76
78
|
attr_reader :backend
|
|
77
79
|
|
|
78
80
|
# Creates a registry.
|
|
79
81
|
#
|
|
80
82
|
# +schemas+ maps absolute URI strings to JSON-shaped Ruby schemas. Pass
|
|
81
83
|
# +validate_schema: true+ to validate every registered and compiled schema
|
|
82
|
-
# against its meta-schema. +backend+ accepts +:ruby+, +:vm+, or +:default+.
|
|
84
|
+
# against its meta-schema. +backend+ accepts +:ruby+, +:vm+, +:native+, or +:default+.
|
|
83
85
|
def initialize(schemas: {}, validate_schema: false, backend: Backend.requested)
|
|
84
86
|
@backend = Backend.resolve(backend)
|
|
85
87
|
@validate_schema = validate_schema
|
|
86
88
|
@graph = Internal::SchemaGraph.new(schemas: schemas)
|
|
87
|
-
@compiler = VM::Compiler.new(@graph) if @backend
|
|
89
|
+
@compiler = VM::Compiler.new(@graph) if %i[vm native].include?(@backend)
|
|
88
90
|
schemas.each_value { |schema| validate_schema!(schema) } if validate_schema?
|
|
89
91
|
end
|
|
90
92
|
|
|
@@ -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,23 +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
|
|
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
|
|
154
173
|
else
|
|
155
174
|
Internal::Evaluator.new(@graph, root, content: content, format: format)
|
|
156
175
|
end
|
|
157
|
-
|
|
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)
|
|
158
184
|
end
|
|
159
185
|
|
|
160
186
|
private def validate_schema!(schema)
|
|
@@ -173,27 +199,46 @@ module Schemurai
|
|
|
173
199
|
#
|
|
174
200
|
# Applications should normally create validators through Schemurai.compile
|
|
175
201
|
# or SchemaRegistry#compile.
|
|
176
|
-
def initialize(evaluator)
|
|
202
|
+
def initialize(evaluator, max_errors: nil, timeout: nil, bounded_evaluator: nil)
|
|
177
203
|
@evaluator = evaluator
|
|
204
|
+
Internal::LimitBudget.validate_options!(max_errors, timeout)
|
|
205
|
+
@max_errors = max_errors
|
|
206
|
+
@timeout = timeout
|
|
207
|
+
@bounded_evaluator = bounded_evaluator
|
|
178
208
|
end
|
|
179
209
|
|
|
180
|
-
# The backend reported by the evaluator,
|
|
210
|
+
# The backend reported by the evaluator, +:ruby+, +:vm+, or +:native+.
|
|
181
211
|
def backend
|
|
182
212
|
@evaluator.backend
|
|
183
213
|
end
|
|
184
214
|
|
|
185
215
|
# Validates +instance+ and returns a Result containing all reported errors.
|
|
186
|
-
|
|
187
|
-
|
|
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)
|
|
188
223
|
end
|
|
189
224
|
|
|
190
225
|
# Returns whether +instance+ is valid without building a detailed error list.
|
|
191
|
-
|
|
192
|
-
|
|
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
|
|
193
238
|
end
|
|
194
239
|
end
|
|
195
240
|
|
|
196
|
-
# Returns the backend selected by SCHEMURAI_BACKEND,
|
|
241
|
+
# Returns the backend selected by SCHEMURAI_BACKEND, +:ruby+, +:vm+, or +:native+.
|
|
197
242
|
module_function def backend
|
|
198
243
|
Backend.resolve
|
|
199
244
|
end
|
|
@@ -202,32 +247,33 @@ module Schemurai
|
|
|
202
247
|
#
|
|
203
248
|
# +schemas+ maps external URI strings to schemas. +base_uri+ supplies the
|
|
204
249
|
# schema's base URI. Optional content and format assertions are disabled by
|
|
205
|
-
# default. +backend+ accepts +:ruby+, +:vm+, or +:default+.
|
|
206
|
-
module_function def compile(schema, schemas: {}, base_uri: nil, content: false, format: false, validate_schema: false, backend: Backend.requested)
|
|
250
|
+
# default. +backend+ accepts +:ruby+, +:vm+, +:native+, or +:default+.
|
|
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)
|
|
207
252
|
SchemaRegistry.new(schemas: schemas, validate_schema: validate_schema, backend: backend).compile(
|
|
208
253
|
schema,
|
|
209
254
|
base_uri: base_uri,
|
|
210
255
|
content: content,
|
|
211
|
-
format: format
|
|
256
|
+
format: format,
|
|
257
|
+
max_errors: max_errors, timeout: timeout
|
|
212
258
|
)
|
|
213
259
|
end
|
|
214
260
|
|
|
215
261
|
# Validates +schema+ against its declared meta-schema and returns a Result.
|
|
216
262
|
# Schemas without +$schema+ are validated as Draft 7.
|
|
217
|
-
module_function def validate_schema(schema, schemas: {}, backend: Backend.requested)
|
|
218
|
-
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)
|
|
219
265
|
end
|
|
220
266
|
|
|
221
267
|
# Returns whether +schema+ conforms to its declared meta-schema.
|
|
222
|
-
module_function def valid_schema?(schema, schemas: {}, backend: Backend.requested)
|
|
223
|
-
|
|
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)
|
|
224
270
|
end
|
|
225
271
|
|
|
226
272
|
# Validates +instance+ against +schema+ and returns a Result.
|
|
227
273
|
#
|
|
228
274
|
# This convenience method compiles the schema for each call. Use .compile
|
|
229
275
|
# when validating multiple instances against the same schema.
|
|
230
|
-
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)
|
|
231
277
|
compile(
|
|
232
278
|
schema,
|
|
233
279
|
schemas: schemas,
|
|
@@ -235,7 +281,8 @@ module Schemurai
|
|
|
235
281
|
content: content,
|
|
236
282
|
format: format,
|
|
237
283
|
validate_schema: validate_schema,
|
|
238
|
-
backend: backend
|
|
284
|
+
backend: backend,
|
|
285
|
+
max_errors: max_errors, timeout: timeout
|
|
239
286
|
).validate(instance)
|
|
240
287
|
end
|
|
241
288
|
|
|
@@ -243,7 +290,7 @@ module Schemurai
|
|
|
243
290
|
#
|
|
244
291
|
# This convenience method compiles the schema for each call. Use .compile
|
|
245
292
|
# when validating multiple instances against the same schema.
|
|
246
|
-
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)
|
|
247
294
|
compile(
|
|
248
295
|
schema,
|
|
249
296
|
schemas: schemas,
|
|
@@ -251,7 +298,8 @@ module Schemurai
|
|
|
251
298
|
content: content,
|
|
252
299
|
format: format,
|
|
253
300
|
validate_schema: validate_schema,
|
|
254
|
-
backend: backend
|
|
301
|
+
backend: backend,
|
|
302
|
+
max_errors: max_errors, timeout: timeout
|
|
255
303
|
).valid?(instance)
|
|
256
304
|
end
|
|
257
305
|
|