schemurai 3.0.1 → 3.0.2

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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -4
  3. data/ext/schemurai_native/Cargo.lock +48 -1
  4. data/ext/schemurai_native/Cargo.toml +3 -1
  5. data/ext/schemurai_native/extconf.rb +1 -1
  6. data/ext/schemurai_native/glue.c +1679 -62
  7. data/ext/schemurai_native/protocol.def +45 -0
  8. data/ext/schemurai_native/src/budget.rs +49 -0
  9. data/ext/schemurai_native/src/container.rs +460 -0
  10. data/ext/schemurai_native/src/content.rs +56 -27
  11. data/ext/schemurai_native/src/diagnostics/array.rs +392 -0
  12. data/ext/schemurai_native/src/diagnostics/object.rs +218 -0
  13. data/ext/schemurai_native/src/diagnostics.rs +1087 -307
  14. data/ext/schemurai_native/src/enumeration.rs +6 -1
  15. data/ext/schemurai_native/src/evaluation.rs +49 -20
  16. data/ext/schemurai_native/src/format.rs +8 -6
  17. data/ext/schemurai_native/src/graph.rs +37 -0
  18. data/ext/schemurai_native/src/instance_graph.rs +90 -0
  19. data/ext/schemurai_native/src/lib.rs +910 -237
  20. data/ext/schemurai_native/src/memo.rs +11 -38
  21. data/ext/schemurai_native/src/number/arbitrary.rs +154 -0
  22. data/ext/schemurai_native/src/number.rs +250 -59
  23. data/ext/schemurai_native/src/pattern.rs +113 -101
  24. data/ext/schemurai_native/src/pointer.rs +97 -0
  25. data/ext/schemurai_native/src/runtime/properties.rs +162 -0
  26. data/ext/schemurai_native/src/runtime/reports.rs +266 -0
  27. data/ext/schemurai_native/src/runtime/scopes.rs +430 -0
  28. data/ext/schemurai_native/src/runtime.rs +1020 -0
  29. data/ext/schemurai_native/src/scope.rs +60 -16
  30. data/ext/schemurai_native/src/string.rs +68 -0
  31. data/ext/schemurai_native/src/tracking.rs +244 -174
  32. data/lib/schemurai/container_checks/array_diagnostics.rb +119 -0
  33. data/lib/schemurai/container_checks/object_diagnostics.rb +152 -0
  34. data/lib/schemurai/container_checks.rb +534 -0
  35. data/lib/schemurai/decimal.rb +11 -0
  36. data/lib/schemurai/dialect.rb +0 -12
  37. data/lib/schemurai/ecma_regexp.rb +46 -0
  38. data/lib/schemurai/evaluation_pool.rb +66 -0
  39. data/lib/schemurai/evaluator.rb +9 -48
  40. data/lib/schemurai/json_pointer.rb +11 -0
  41. data/lib/schemurai/native/diagnostics.rb +4 -3
  42. data/lib/schemurai/native/evaluator.rb +38 -10
  43. data/lib/schemurai/native/null_numeric.rb +23 -0
  44. data/lib/schemurai/number_checks.rb +128 -0
  45. data/lib/schemurai/string_checks.rb +166 -0
  46. data/lib/schemurai/type_checks.rb +59 -0
  47. data/lib/schemurai/value_equality.rb +72 -0
  48. data/lib/schemurai/version.rb +1 -1
  49. data/lib/schemurai/vm/compiler.rb +18 -26
  50. data/lib/schemurai/vm/evaluator.rb +87 -545
  51. data/lib/schemurai/vm/limited_evaluator.rb +7 -22
  52. metadata +24 -2
@@ -1,84 +1,62 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../decimal"
3
4
  require "base64"
4
5
  require "json"
5
- require "set" # rubocop:disable Lint/RedundantRequireStatement -- Required on Ruby 3.4.
6
+ require_relative "../evaluation_pool"
6
7
  require_relative "../evaluation"
7
8
  require_relative "../error_message"
8
9
  require_relative "../unique_items"
10
+ require_relative "../value_equality"
11
+ require_relative "../container_checks"
12
+ require_relative "../ecma_regexp"
13
+ require_relative "../json_pointer"
9
14
  require_relative "compiler"
15
+ require_relative "../string_checks"
10
16
 
11
17
  module Schemurai
12
18
  module VM
13
- class EvaluationBuffer
14
- attr_reader :evaluated_properties, :evaluated_items
15
-
16
- def initialize
17
- # Applicators can overlap. Array membership makes collecting and merging
18
- # their annotations quadratic in the number of instance locations.
19
- @evaluated_properties = Set.new
20
- @evaluated_items = Set.new
21
- end
22
-
23
- def valid? = true
24
-
25
- def reset
26
- @evaluated_properties.clear
27
- @evaluated_items.clear
28
- self
29
- end
30
-
31
- def record_property(name)
32
- @evaluated_properties.add(name)
33
- self
34
- end
35
-
36
- def record_item(index)
37
- @evaluated_items.add(index)
38
- self
39
- end
40
-
41
- def merge(other)
42
- return Evaluation.invalid unless other.valid?
43
-
44
- properties = other.evaluated_properties
45
- items = other.evaluated_items
46
- @evaluated_properties.merge(properties) unless properties.empty?
47
- @evaluated_items.merge(items) unless items.empty?
48
- self
49
- end
50
- end
19
+ EvaluationBuffer = Internal::EvaluationBuffer
51
20
  private_constant :EvaluationBuffer
52
21
 
53
22
  class Evaluator
54
23
  include Internal::UniqueItems
55
-
56
- MISSING_SEGMENT = Object.new.freeze
57
- DECIMAL_CACHE_LIMIT = 16
24
+ include Internal::ValueEquality
25
+ include Internal::ContainerChecks::Bounds
26
+ include Internal::ContainerChecks::ObjectChildren
27
+ include Internal::ContainerChecks::ArrayChildren
28
+ include Internal::ContainerChecks::ArrayDiagnostics
29
+ include Internal::ContainerChecks::ObjectDiagnostics
30
+ include Internal::NumberChecks::Evaluation
31
+ include Internal::StringChecks::Evaluation
32
+ include Internal::StringChecks::Regexps
33
+
34
+ MISSING_SEGMENT = Internal::ContainerChecks::ObjectDiagnostics::MISSING_SEGMENT
58
35
 
59
36
  def backend = :vm
60
37
 
61
- def initialize(graph, compiler, root, content: false, format: false)
38
+ def initialize(graph, compiler, root, content: false, format: false, number_state: nil, regexps: nil, dynamic_scope: nil, active_state: nil, native_references: nil, native_reports: nil)
62
39
  @graph = graph
63
40
  @compiler = compiler
64
41
  @root = root
65
42
  @validate_content = content
66
43
  @validate_format = format
67
- @regexps = nil
68
- @active = nil
44
+ @regexps = regexps
45
+ @active = active_state
46
+ @native_reports = native_reports
47
+ @native_references = native_references
69
48
  @resolved_references = nil
70
- @decimals = nil
71
- @multiple_results = nil
72
- @dynamic_scope = nil
49
+ @number_state = number_state
50
+ @dynamic_scope = dynamic_scope
73
51
  @instance_path_buffer = nil
74
52
  @schema_path_buffer = nil
75
- @evaluation_pool = nil
76
- @evaluation_pool_index = 0
53
+ @evaluation_pool = native_reports ? native_reports[1] : Internal::EvaluationPool.new
77
54
  end
78
55
 
79
56
  def validate(instance)
80
57
  @errors = []
81
58
  prepare_evaluation(paths: true)
59
+ @native_reports[0] = self if @native_reports
82
60
  evaluate(@root, instance)
83
61
  Result.new(@errors)
84
62
  ensure
@@ -92,9 +70,37 @@ module Schemurai
92
70
  @instance_path = nil
93
71
  @schema_path = nil
94
72
  reset_evaluation_pool
73
+ @native_reports[0] = self if @native_reports
95
74
  evaluate_valid(@root, instance)
96
75
  end
97
76
 
77
+ # Data exchange for retained native blocks during a compatibility traversal.
78
+ # These methods expose collection state, never programs or instructions.
79
+ private def native_report_context
80
+ [@errors, @error_count || 0, @instance_path, @schema_path]
81
+ end
82
+
83
+ private def native_clear_collection(paths = false)
84
+ @errors = nil
85
+ @error_count = 0
86
+ if paths
87
+ (@instance_path_buffer ||= []).clear
88
+ (@schema_path_buffer ||= []).clear
89
+ @instance_path = @instance_path_buffer
90
+ @schema_path = @schema_path_buffer
91
+ else
92
+ @instance_path = nil
93
+ @schema_path = nil
94
+ end
95
+ end
96
+
97
+ private def native_append_report(errors, count, reset)
98
+ native_clear_collection(reset == 2) unless reset.zero?
99
+ @error_count += count
100
+ @errors.concat(errors) if @errors
101
+ @native_reports[0] = self
102
+ end
103
+
98
104
  private def prepare_evaluation(paths:)
99
105
  @error_count = 0
100
106
  @dynamic_scope&.clear
@@ -142,19 +148,19 @@ module Schemurai
142
148
  target = dynamic_target(program, operand)
143
149
  return false unless valid_reference?(program, target, instance)
144
150
  when :type_null
145
- return false unless instance.nil?
151
+ return false unless Internal::TypeChecks.single?(instance, TYPE_NULL)
146
152
  when :type_boolean
147
- return false unless instance == true || instance == false
153
+ return false unless Internal::TypeChecks.single?(instance, TYPE_BOOLEAN)
148
154
  when :type_object
149
- return false unless instance.is_a?(Hash)
155
+ return false unless Internal::TypeChecks.single?(instance, TYPE_OBJECT)
150
156
  when :type_array
151
- return false unless instance.is_a?(Array)
157
+ return false unless Internal::TypeChecks.single?(instance, TYPE_ARRAY)
152
158
  when :type_number
153
- return false unless instance.is_a?(Numeric) && !instance.is_a?(Complex)
159
+ return false unless Internal::TypeChecks.single?(instance, TYPE_NUMBER)
154
160
  when :type_integer
155
- return false unless instance.is_a?(Numeric) && !instance.is_a?(Complex) && instance.finite? && instance.to_i == instance
161
+ return false unless Internal::TypeChecks.single?(instance, TYPE_INTEGER)
156
162
  when :type_string
157
- return false unless instance.is_a?(String)
163
+ return false unless Internal::TypeChecks.single?(instance, TYPE_STRING)
158
164
  when :typed_number
159
165
  return false unless instance.is_a?(Numeric) && !instance.is_a?(Complex) && valid_number?(operand, instance)
160
166
  when :typed_integer
@@ -213,14 +219,11 @@ module Schemurai
213
219
  end
214
220
 
215
221
  private def reset_evaluation_pool
216
- @evaluation_pool_index = 0
222
+ @evaluation_pool.reset
217
223
  end
218
224
 
219
225
  private def tracked_evaluation
220
- pool = (@evaluation_pool ||= [])
221
- index = @evaluation_pool_index
222
- @evaluation_pool_index += 1
223
- (pool[index] ||= EvaluationBuffer.new).reset
226
+ @evaluation_pool.acquire
224
227
  end
225
228
 
226
229
  private def evaluate(program, instance)
@@ -254,21 +257,21 @@ module Schemurai
254
257
  when :dynamic_ref
255
258
  evaluation = evaluation.merge(evaluate_dynamic_ref(program, operand, instance))
256
259
  when :type_null
257
- check_compiled_type("null", instance.nil?, instance)
260
+ check_compiled_type("null", Internal::TypeChecks.single?(instance, TYPE_NULL), instance)
258
261
  when :type_boolean
259
- check_compiled_type("boolean", instance == true || instance == false, instance)
262
+ check_compiled_type("boolean", Internal::TypeChecks.single?(instance, TYPE_BOOLEAN), instance)
260
263
  when :type_object
261
- check_compiled_type("object", instance.is_a?(Hash), instance)
264
+ check_compiled_type("object", Internal::TypeChecks.single?(instance, TYPE_OBJECT), instance)
262
265
  when :type_array
263
- check_compiled_type("array", instance.is_a?(Array), instance)
266
+ check_compiled_type("array", Internal::TypeChecks.single?(instance, TYPE_ARRAY), instance)
264
267
  when :type_number
265
- check_compiled_type("number", number?(instance), instance)
268
+ check_compiled_type("number", Internal::TypeChecks.single?(instance, TYPE_NUMBER), instance)
266
269
  when :type_integer
267
- check_compiled_type("integer", integer?(instance), instance)
270
+ check_compiled_type("integer", Internal::TypeChecks.single?(instance, TYPE_INTEGER), instance)
268
271
  when :type_string
269
- check_compiled_type("string", instance.is_a?(String), instance)
272
+ check_compiled_type("string", Internal::TypeChecks.single?(instance, TYPE_STRING), instance)
270
273
  when :typed_number
271
- if number?(instance)
274
+ if instance.is_a?(Numeric) && !instance.is_a?(Complex)
272
275
  check_number(operand, instance)
273
276
  else
274
277
  check_compiled_type("number", false, instance)
@@ -372,7 +375,7 @@ module Schemurai
372
375
 
373
376
  private def valid_reference?(source, target, instance)
374
377
  instances = active_instances(source)
375
- return true if instances.key?(instance)
378
+ return true if instances.key?(instance) || @native_references&.reference_active?(source, instance)
376
379
 
377
380
  instances[instance] = true
378
381
  activated = true
@@ -407,7 +410,7 @@ module Schemurai
407
410
 
408
411
  private def evaluate_reference(source, target, instance, keyword)
409
412
  instances = active_instances(source)
410
- return Evaluation.valid if instances.key?(instance)
413
+ return Evaluation.valid if instances.key?(instance) || @native_references&.reference_active?(source, instance)
411
414
 
412
415
  instances[instance] = true
413
416
  activated = true
@@ -438,36 +441,16 @@ module Schemurai
438
441
  end
439
442
 
440
443
  private def instance_type_mask(value, integer:)
441
- return TYPE_NULL if value.nil?
442
- return TYPE_BOOLEAN if value == true || value == false
443
- return TYPE_OBJECT if value.is_a?(Hash)
444
- return TYPE_ARRAY if value.is_a?(Array)
445
- return TYPE_STRING if value.is_a?(String)
446
- return 0 unless number?(value)
447
- return TYPE_NUMBER unless integer
448
-
449
- (value.finite? && value.to_i == value) ? TYPE_NUMBER | TYPE_INTEGER : TYPE_NUMBER
444
+ Internal::TypeChecks.instance_mask(value, integer: integer)
450
445
  end
451
446
 
452
447
  private def integer?(value)
453
- number?(value) && value.finite? && value.to_i == value
454
- end
455
-
456
- private def valid_number?(rules, value)
457
- mask = rules.mask
458
- actual = (mask & MULTIPLE_OF).zero? ? value : decimal(value)
459
- return false if (mask & MAXIMUM) != 0 && actual > rules.maximum
460
- return false if (mask & MINIMUM) != 0 && actual < rules.minimum
461
- return false if (mask & EXCLUSIVE_MAXIMUM) != 0 && actual >= rules.exclusive_maximum
462
- return false if (mask & EXCLUSIVE_MINIMUM) != 0 && actual <= rules.exclusive_minimum
463
- return true if (mask & MULTIPLE_OF).zero?
464
-
465
- valid_multiple?(rules, value)
448
+ Internal::TypeChecks.integer?(value)
466
449
  end
467
450
 
468
451
  private def check_number(rules, value)
469
452
  mask = rules.mask
470
- actual = (mask & MULTIPLE_OF).zero? ? value : decimal(value)
453
+ actual = (mask & MULTIPLE_OF).zero? ? value : number_state.decimal(value)
471
454
  if (mask & MAXIMUM) != 0 && actual > rules.maximum
472
455
  add_error("maximum") { Internal::ErrorMessage.numeric_limit("maximum", rules.maximum) }
473
456
  end
@@ -491,33 +474,6 @@ module Schemurai
491
474
  add_error("multipleOf") { Internal::ErrorMessage.multiple_of(divisor) } unless valid
492
475
  end
493
476
 
494
- private def valid_multiple?(rules, value)
495
- results = (@multiple_results ||= {}.compare_by_identity)
496
- values = (results[rules] ||= {})
497
- return values[value] if values.key?(value)
498
-
499
- values.clear if values.length >= DECIMAL_CACHE_LIMIT
500
- divisor = rules.multiple_of
501
- values[value] = divisor.positive? && decimal(value).remainder(divisor).zero?
502
- end
503
-
504
- private def valid_string?(rules, value)
505
- length = value.length
506
- return false if rules.max_length && length > rules.max_length
507
- return false if rules.min_length && length < rules.min_length
508
- return false if rules.pattern && !ecma_regexp(rules.pattern).match?(value)
509
- if rules.format && (@validate_format || rules.format_assertion)
510
- return false unless rules.format.call(value)
511
- end
512
- if @validate_content && (rules.decode_base64 || rules.parse_json)
513
- return valid_content?(rules, value)
514
- end
515
-
516
- true
517
- rescue RegexpError, IPAddr::InvalidAddressError
518
- false
519
- end
520
-
521
477
  private def check_string(rules, value)
522
478
  length = value.length
523
479
  if rules.max_length && length > rules.max_length
@@ -537,14 +493,6 @@ module Schemurai
537
493
  add_error("pattern") { Internal::ErrorMessage.invalid_pattern(rules.pattern) }
538
494
  end
539
495
 
540
- private def valid_content?(rules, value)
541
- decoded = rules.decode_base64 ? Base64.strict_decode64(value) : value
542
- JSON.parse(decoded) if rules.parse_json
543
- true
544
- rescue ArgumentError, JSON::ParserError
545
- false
546
- end
547
-
548
496
  private def check_content(rules, value)
549
497
  decoded = rules.decode_base64 ? Base64.strict_decode64(value) : value
550
498
  return unless rules.parse_json
@@ -559,347 +507,18 @@ module Schemurai
559
507
 
560
508
  private def valid_array?(rules, value)
561
509
  length = value.length
562
- return false if rules.max_items && length > rules.max_items
563
- return false if rules.min_items && length < rules.min_items
510
+ return false unless valid_container_bounds?(length, rules.max_items, rules.min_items)
564
511
  return false if rules.unique && !unique_items?(value)
565
512
 
566
- if (prefix_items = rules.prefix_items)
567
- prefix_items.each_with_index do |child, index|
568
- @budget&.checkpoint
569
- break if index >= length
570
- return false unless evaluate_valid(child, value[index])
571
- end
572
- end
573
-
574
- items = rules.items
575
- if rules.items_list
576
- items.each_with_index do |child, index|
577
- @budget&.checkpoint
578
- break if index >= length
579
- return false unless evaluate_valid(child, value[index])
580
- end
581
- if length > items.length && (additional = rules.additional)
582
- index = items.length
583
- while index < length
584
- @budget&.checkpoint
585
- return false unless evaluate_valid(additional, value[index])
586
- index += 1
587
- end
588
- end
589
- elsif items
590
- start = rules.prefix_items&.length || 0
591
- index = start
592
- while index < length
593
- @budget&.checkpoint
594
- return false unless evaluate_valid(items, value[index])
595
- index += 1
596
- end
597
- end
598
-
599
- if (contains = rules.contains)
600
- if rules.count_contains
601
- matches = value.count { |item| evaluate_valid(contains, item) }
602
- return false if matches < rules.min_contains || matches > rules.max_contains
603
- else
604
- return false unless value.any? { |item| evaluate_valid(contains, item) }
605
- end
606
- end
607
- true
608
- end
609
-
610
- private def check_array(rules, value, prior_evaluation)
611
- evaluation = nil
612
- if rules.max_items && value.length > rules.max_items
613
- return Evaluation.invalid unless @errors
614
-
615
- add_error("maxItems") { Internal::ErrorMessage.size("maxItems", rules.max_items, value.length) }
616
- end
617
- if rules.min_items && value.length < rules.min_items
618
- return Evaluation.invalid unless @errors
619
-
620
- add_error("minItems") { Internal::ErrorMessage.size("minItems", rules.min_items, value.length) }
621
- end
622
- if rules.unique && !unique_items?(value)
623
- return Evaluation.invalid unless @errors
624
-
625
- add_error("uniqueItems") { Internal::ErrorMessage.unique_items }
626
- end
627
-
628
- if (prefix_items = rules.prefix_items)
629
- prefix_items.each_with_index do |child, index|
630
- @budget&.checkpoint
631
- break if index >= value.length
632
- valid = evaluate_child_at(child, value[index], index, "prefixItems", index)
633
- return Evaluation.invalid if !valid && !@errors
634
- (evaluation ||= tracked_evaluation).record_item(index)
635
- end
636
- end
637
-
638
- items = rules.items
639
- if rules.items_list
640
- items.each_with_index do |child, index|
641
- @budget&.checkpoint
642
- break if index >= value.length
643
- valid = evaluate_child_at(child, value[index], index, "items", index)
644
- return Evaluation.invalid if !valid && !@errors
645
- (evaluation ||= tracked_evaluation).record_item(index)
646
- end
647
- if value.length > items.length && (additional = rules.additional)
648
- (items.length...value.length).each do |index|
649
- @budget&.checkpoint
650
- valid = evaluate_child_at(additional, value[index], index, "additionalItems")
651
- return Evaluation.invalid if !valid && !@errors
652
- (evaluation ||= tracked_evaluation).record_item(index)
653
- end
654
- end
655
- elsif items
656
- start = rules.prefix_items&.length || 0
657
- (start...value.length).each do |index|
658
- @budget&.checkpoint
659
- valid = evaluate_child_at(items, value[index], index, "items")
660
- return Evaluation.invalid if !valid && !@errors
661
- (evaluation ||= tracked_evaluation).record_item(index)
662
- end
663
- end
664
-
665
- if (contains = rules.contains)
666
- matched = nil
667
- index = 0
668
- while index < value.length
669
- @budget&.checkpoint
670
- (matched ||= []) << index if trial(contains, value[index]).valid?
671
- index += 1
672
- end
673
- matched_count = matched ? matched.length : 0
674
- unless matched_count.between?(rules.min_contains, rules.max_contains)
675
- return Evaluation.invalid unless @errors
676
-
677
- add_error("contains") do
678
- Internal::ErrorMessage.contains(matched_count, rules.min_contains, rules.max_contains)
679
- end
680
- end
681
- if matched
682
- matched.each { |index| (evaluation ||= tracked_evaluation).record_item(index) }
683
- end
684
- end
685
-
686
- if (unevaluated = rules.unevaluated)
687
- prior_items = prior_evaluation.evaluated_items
688
- index = 0
689
- while index < value.length
690
- @budget&.checkpoint
691
- if prior_items.include?(index) || evaluation&.evaluated_items&.include?(index)
692
- index += 1
693
- next
694
- end
695
- valid = evaluate_child_at(unevaluated, value[index], index, "unevaluatedItems")
696
- return Evaluation.invalid if !valid && !@errors
697
- (evaluation ||= tracked_evaluation).record_item(index)
698
- index += 1
699
- end
700
- end
701
- return Evaluation.valid unless evaluation
702
-
703
- evaluation
513
+ valid_array_children?(rules, value, length) { |child, item| evaluate_valid(child, item) }
704
514
  end
705
515
 
706
516
  private def valid_object?(rules, value)
707
517
  length = value.length
708
- return false if rules.max_properties && length > rules.max_properties
709
- return false if rules.min_properties && length < rules.min_properties
710
- if rules.required && !rules.required.all? do |name|
711
- @budget&.checkpoint
712
- value.key?(name)
713
- end
714
- return false
715
- end
716
-
717
- properties = rules.properties
718
- patterns = rules.patterns
719
- additional = rules.additional
720
- unless properties.empty? && patterns.nil? && additional.nil?
721
- value.each do |name, property_value|
722
- @budget&.checkpoint
723
- matched = false
724
- if (child = properties[name])
725
- matched = true
726
- return false unless evaluate_valid(child, property_value)
727
- end
728
- if patterns
729
- patterns.each do |pattern, child|
730
- @budget&.checkpoint
731
- next unless ecma_regexp(pattern).match?(name)
732
- matched = true
733
- return false unless evaluate_valid(child, property_value)
734
- end
735
- end
736
- return false if !matched && additional && !evaluate_valid(additional, property_value)
737
- end
738
- end
739
-
740
- if (property_names = rules.property_names)
741
- value.each_key { |name| return false unless evaluate_valid(property_names, name) }
742
- end
743
- if (dependencies = rules.dependencies)
744
- dependencies.each do |name, dependency|
745
- @budget&.checkpoint
746
- next unless value.key?(name)
747
- if dependency.is_a?(Array)
748
- return false unless dependency.all? do |required_name|
749
- @budget&.checkpoint
750
- value.key?(required_name)
751
- end
752
- else
753
- return false unless evaluate_valid(dependency, value)
754
- end
755
- end
756
- end
757
- if (dependent_required = rules.dependent_required)
758
- dependent_required.each do |name, required_names|
759
- @budget&.checkpoint
760
- next unless value.key?(name)
761
- return false unless required_names.all? do |required_name|
762
- @budget&.checkpoint
763
- value.key?(required_name)
764
- end
765
- end
766
- end
767
- if (dependent_schemas = rules.dependent_schemas)
768
- dependent_schemas.each do |name, child|
769
- @budget&.checkpoint
770
- next unless value.key?(name)
771
- return false unless evaluate_valid(child, value)
772
- end
773
- end
774
- true
775
- end
776
-
777
- private def check_object(rules, value, prior_evaluation)
778
- evaluation = nil
779
- if rules.max_properties && value.length > rules.max_properties
780
- return Evaluation.invalid unless @errors
781
-
782
- add_error("maxProperties") do
783
- Internal::ErrorMessage.size("maxProperties", rules.max_properties, value.length)
784
- end
785
- end
786
- if rules.min_properties && value.length < rules.min_properties
787
- return Evaluation.invalid unless @errors
788
-
789
- add_error("minProperties") do
790
- Internal::ErrorMessage.size("minProperties", rules.min_properties, value.length)
791
- end
792
- end
793
- if (required = rules.required)
794
- required.each do |name|
795
- @budget&.checkpoint
796
- next if value.key?(name)
797
- return Evaluation.invalid unless @errors
798
-
799
- add_error("required") { Internal::ErrorMessage.required(name) }
800
- end
801
- end
802
-
803
- properties = rules.properties
804
- patterns = rules.patterns
805
- value.each do |name, property_value|
806
- @budget&.checkpoint
807
- matched = false
808
- if (child = properties[name])
809
- matched = true
810
- valid = evaluate_child_at(child, property_value, name, "properties", name)
811
- return Evaluation.invalid if !valid && !@errors
812
- (evaluation ||= tracked_evaluation).record_property(name)
813
- end
814
- if patterns
815
- patterns.each do |pattern, child|
816
- @budget&.checkpoint
817
- next unless ecma_regexp(pattern).match?(name)
818
- matched = true
819
- valid = evaluate_child_at(child, property_value, name, "patternProperties", pattern)
820
- return Evaluation.invalid if !valid && !@errors
821
- (evaluation ||= tracked_evaluation).record_property(name)
822
- end
823
- end
824
- if !matched && (additional = rules.additional)
825
- valid = evaluate_child_at(additional, property_value, name, "additionalProperties")
826
- return Evaluation.invalid if !valid && !@errors
827
- (evaluation ||= tracked_evaluation).record_property(name)
828
- end
829
- end
830
-
831
- if (property_names = rules.property_names)
832
- value.each_key do |name|
833
- @budget&.checkpoint
834
- valid = evaluate_child_at(property_names, name, name, "propertyNames")
835
- return Evaluation.invalid if !valid && !@errors
836
- end
837
- end
838
- if (dependencies = rules.dependencies)
839
- dependencies.each do |name, dependency|
840
- @budget&.checkpoint
841
- next unless value.key?(name)
842
- if dependency.is_a?(Array)
843
- dependency.each do |required_name|
844
- @budget&.checkpoint
845
- unless value.key?(required_name)
846
- return Evaluation.invalid unless @errors
847
-
848
- add_error("dependencies") { Internal::ErrorMessage.dependent_required(name, required_name) }
849
- end
850
- end
851
- else
852
- result = evaluate_at(dependency, value, MISSING_SEGMENT, "dependencies", name)
853
- if result.valid? && !result.evaluated_properties.empty?
854
- result.evaluated_properties.each do |property|
855
- @budget&.checkpoint
856
- (evaluation ||= tracked_evaluation).record_property(property)
857
- end
858
- end
859
- end
860
- end
861
- end
862
- if (dependent_required = rules.dependent_required)
863
- dependent_required.each do |name, required_names|
864
- @budget&.checkpoint
865
- next unless value.key?(name)
866
- required_names.each do |required_name|
867
- @budget&.checkpoint
868
- unless value.key?(required_name)
869
- return Evaluation.invalid unless @errors
870
-
871
- add_error("dependentRequired") { Internal::ErrorMessage.dependent_required(name, required_name) }
872
- end
873
- end
874
- end
875
- end
876
- if (dependent_schemas = rules.dependent_schemas)
877
- dependent_schemas.each do |name, child|
878
- @budget&.checkpoint
879
- next unless value.key?(name)
880
- result = evaluate_at(child, value, MISSING_SEGMENT, "dependentSchemas", name)
881
- if result.valid? && !result.evaluated_properties.empty?
882
- result.evaluated_properties.each do |property|
883
- @budget&.checkpoint
884
- (evaluation ||= tracked_evaluation).record_property(property)
885
- end
886
- end
887
- end
888
- end
889
- if (unevaluated = rules.unevaluated)
890
- prior_properties = prior_evaluation.evaluated_properties
891
- value.each_key do |name|
892
- @budget&.checkpoint
893
- next if prior_properties.include?(name) || evaluation&.evaluated_properties&.include?(name)
894
-
895
- valid = evaluate_child_at(unevaluated, value[name], name, "unevaluatedProperties")
896
- return Evaluation.invalid if !valid && !@errors
897
- (evaluation ||= tracked_evaluation).record_property(name)
898
- end
899
- end
900
- return Evaluation.valid unless evaluation
518
+ return false unless valid_container_bounds?(length, rules.max_properties, rules.min_properties)
519
+ return false unless valid_required_keys?(rules.required, value)
901
520
 
902
- evaluation
521
+ valid_object_children?(rules, value) { |child, item| evaluate_valid(child, item) }
903
522
  end
904
523
 
905
524
  private def check_combiner(opcode, operand, value)
@@ -975,6 +594,7 @@ module Schemurai
975
594
  ensure
976
595
  @errors = saved_errors
977
596
  @error_count = saved_count
597
+ @native_reports[0] = self if @native_reports
978
598
  if saved_instance_path
979
599
  @instance_path = saved_instance_path
980
600
  @schema_path = saved_schema_path
@@ -1021,85 +641,7 @@ module Schemurai
1021
641
  end
1022
642
 
1023
643
  private def number?(value)
1024
- value.is_a?(Numeric) && !value.is_a?(Complex)
1025
- end
1026
-
1027
- private def json_equal?(left, right)
1028
- if left.is_a?(Numeric) && !left.is_a?(Complex)
1029
- return right.is_a?(Numeric) && !right.is_a?(Complex) && left == right
1030
- end
1031
- return false unless left.instance_of?(right.class)
1032
-
1033
- case left
1034
- when Hash
1035
- left.length == right.length && left.all? do |key, value|
1036
- @budget&.checkpoint
1037
- right.key?(key) && json_equal?(value, right[key])
1038
- end
1039
- when Array
1040
- return false unless left.length == right.length
1041
-
1042
- index = 0
1043
- while index < left.length
1044
- @budget&.checkpoint
1045
- return false unless json_equal?(left[index], right[index])
1046
- index += 1
1047
- end
1048
- true
1049
- else
1050
- left == right
1051
- end
1052
- end
1053
-
1054
- private def decimal(value)
1055
- return value if value.is_a?(Integer) || value.is_a?(Rational)
1056
-
1057
- decimals = (@decimals ||= {})
1058
- return decimals[value] if decimals.key?(value)
1059
-
1060
- decimals.clear if decimals.length >= DECIMAL_CACHE_LIMIT
1061
- decimals[value] = Rational(value.to_s)
1062
- end
1063
-
1064
- private def ecma_regexp(pattern)
1065
- regexps = (@regexps ||= {})
1066
- return regexps[pattern] if regexps.key?(pattern)
1067
-
1068
- whitespace = "\\u0009-\\u000D\\u0020\\u00A0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000\\uFEFF"
1069
- translated = +""
1070
- escaped = false
1071
- in_class = false
1072
- pattern.each_char do |character|
1073
- @budget&.checkpoint
1074
- if escaped
1075
- translated << case character
1076
- when "d" then in_class ? "0-9" : "[0-9]"
1077
- when "D" then in_class ? "^0-9" : "[^0-9]"
1078
- when "w" then in_class ? "A-Za-z0-9_" : "[A-Za-z0-9_]"
1079
- when "W" then in_class ? "^A-Za-z0-9_" : "[^A-Za-z0-9_]"
1080
- when "s" then in_class ? whitespace : "[#{whitespace}]"
1081
- when "S" then in_class ? "^#{whitespace}" : "[^#{whitespace}]"
1082
- else "\\#{character}"
1083
- end
1084
- escaped = false
1085
- elsif character == "\\"
1086
- escaped = true
1087
- elsif character == "["
1088
- in_class = true
1089
- translated << character
1090
- elsif character == "]"
1091
- in_class = false
1092
- translated << character
1093
- elsif character == "^" && !in_class
1094
- translated << "\\A"
1095
- elsif character == "$" && !in_class
1096
- translated << "\\z"
1097
- else
1098
- translated << character
1099
- end
1100
- end
1101
- translated << "\\" if escaped
1102
- regexps[pattern] = Regexp.new(translated)
644
+ Internal::TypeChecks.number?(value)
1103
645
  end
1104
646
 
1105
647
  private def pointer(path, final_segment = MISSING_SEGMENT)
@@ -1110,10 +652,10 @@ module Schemurai
1110
652
  end
1111
653
 
1112
654
  private def append_pointer_segment(pointer, segment)
1113
- pointer << "/" << segment.to_s.gsub("~", "~0").gsub("/", "~1")
655
+ pointer << "/" << Internal::JsonPointer.escape(segment)
1114
656
  end
1115
657
 
1116
- private_constant :DECIMAL_CACHE_LIMIT, :MISSING_SEGMENT
658
+ private_constant :MISSING_SEGMENT
1117
659
  end
1118
660
  end
1119
661