schemurai 2.2.1 → 2.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ad9362bcb75b8b6f51fb74a2d15858766793620d348fc4924746c44eaa210fc3
4
- data.tar.gz: ba30c2b4f45525b6953019dda82a3f4b1cbcc41405925e19c2f4550b58dfccd1
3
+ metadata.gz: 163dbc9593611cce4f28183766bd5e17a08387a54883c17a502a198eb010ad36
4
+ data.tar.gz: aa3d5f90db227194179023b31043cf36b4597e4af5f1e2209b2846e9c36f9044
5
5
  SHA512:
6
- metadata.gz: 1773b839b4b8de56dc0f0087e1070f6236602f3ad9a5aec39bcebe2586c5632a45865a6326365bba505f49728d006e1c9c6dbe63f9f1075d00a3398241d953d0
7
- data.tar.gz: bfd5d96cd648547bfc1a97bd7d0f566ba5d86a4f39e8ceb89a27ebc536ce45ac8563614024a2d10c73cb6c61dd568a0f271069a7525510d6bd214de52ba53a3c
6
+ metadata.gz: 5f5ba215dde91e1bf364ff6ebbbebb7ea40b284e3908cfcb578d48aad2174c34e5cf6d9fd121947bb54f8b2cf0fad626e629ed94d6b4a52ab41ba84ad7dfbc55
7
+ data.tar.gz: 4efa97250aad095e414eb230310ae580bbd029b834aa11322cab8da0b19df7a1fb6703240ee0439fd4abe788fcfa47c918ac92806bef174d38d0110bb35cc934
@@ -4,10 +4,13 @@ require "json"
4
4
  require "base64"
5
5
  require_relative "evaluation"
6
6
  require_relative "error_message"
7
+ require_relative "unique_items"
7
8
 
8
9
  module Schemurai
9
10
  module Internal
10
11
  class Evaluator
12
+ include UniqueItems
13
+
11
14
  MISSING_SEGMENT = Object.new.freeze
12
15
 
13
16
  def backend = :ruby
@@ -226,11 +229,7 @@ module Schemurai
226
229
  length = value.length
227
230
  return false if schema.key?("maxItems") && length > schema["maxItems"]
228
231
  return false if schema.key?("minItems") && length < schema["minItems"]
229
- if schema["uniqueItems"]
230
- value.each_with_index do |item, index|
231
- return false if value[0...index].any? { |previous| json_equal?(previous, item) }
232
- end
233
- end
232
+ return false if schema["uniqueItems"] && !unique_items?(value)
234
233
 
235
234
  prefix_items = schema["prefixItems"]
236
235
  if prefix_items.is_a?(Array)
@@ -555,12 +554,7 @@ module Schemurai
555
554
  limit(schema, "maxItems", value.length) { |actual, expected| actual <= expected }
556
555
  limit(schema, "minItems", value.length) { |actual, expected| actual >= expected }
557
556
 
558
- if schema["uniqueItems"]
559
- duplicate = value.each_with_index.any? do |item, index|
560
- value[0...index].any? { |previous| json_equal?(previous, item) }
561
- end
562
- add_error("uniqueItems") { ErrorMessage.unique_items } if duplicate
563
- end
557
+ add_error("uniqueItems") { ErrorMessage.unique_items } if schema["uniqueItems"] && !unique_items?(value)
564
558
 
565
559
  prefix_items = schema["prefixItems"]
566
560
  if prefix_items.is_a?(Array)
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Schemurai
4
+ module Internal
5
+ module UniqueItems
6
+ private def unique_items?(values)
7
+ # Avoid building an index for small arrays and early duplicates.
8
+ if values.length <= 16
9
+ values.each_with_index do |item, index|
10
+ index.times { |previous| return false if json_equal?(values[previous], item) }
11
+ end
12
+ return true
13
+ end
14
+
15
+ buckets = {}
16
+ # Store a single index until a collision needs a bucket array.
17
+ index = 0
18
+ while index < values.length
19
+ item = values[index]
20
+ fingerprint = json_fingerprint(item)
21
+ if (bucket = buckets[fingerprint])
22
+ if bucket.is_a?(Integer)
23
+ return false if json_equal?(values[bucket], item)
24
+
25
+ buckets[fingerprint] = [bucket, index]
26
+ else
27
+ return false if bucket.any? { |previous| json_equal?(values[previous], item) }
28
+
29
+ bucket << index
30
+ end
31
+ else
32
+ buckets[fingerprint] = index
33
+ end
34
+ index += 1
35
+ end
36
+ true
37
+ end
38
+
39
+ private def json_fingerprint(value)
40
+ case value
41
+ when Numeric
42
+ # JSON numbers compare across Integer/Float. Converting integral
43
+ # floats to integers also preserves precision for large integers.
44
+ return value.hash if value.is_a?(Integer)
45
+
46
+ number = value.to_f
47
+ (number.finite? && number == number.to_i) ? number.to_i.hash : number.hash
48
+ when Array
49
+ # Mask before multiplication to keep intermediate values immediate
50
+ # integers; hash each step to retain order without temporary arrays.
51
+ fingerprint = value.class.hash
52
+ value.each { |item| fingerprint = (((fingerprint & 0x1fffffff) * 31) ^ json_fingerprint(item)).hash }
53
+ fingerprint
54
+ when Hash
55
+ # Object key order is immaterial. Hash collisions are resolved using
56
+ # json_equal?, so this fingerprint never decides equality itself.
57
+ entries = 0
58
+ value.each_pair { |key, item| entries ^= (((key.hash & 0x1fffffff) * 31) ^ json_fingerprint(item)).hash }
59
+ (value.class.hash ^ value.length ^ entries).hash
60
+ else
61
+ value.hash
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Schemurai
4
4
  # The installed Schemurai version.
5
- VERSION = "2.2.1"
5
+ VERSION = "2.2.2"
6
6
  end
@@ -4,6 +4,7 @@ require "base64"
4
4
  require "json"
5
5
  require_relative "../evaluation"
6
6
  require_relative "../error_message"
7
+ require_relative "../unique_items"
7
8
  require_relative "compiler"
8
9
 
9
10
  module Schemurai
@@ -49,6 +50,8 @@ module Schemurai
49
50
  private_constant :EvaluationBuffer
50
51
 
51
52
  class Evaluator
53
+ include Internal::UniqueItems
54
+
52
55
  MISSING_SEGMENT = Object.new.freeze
53
56
  DECIMAL_CACHE_LIMIT = 16
54
57
 
@@ -552,17 +555,7 @@ module Schemurai
552
555
  length = value.length
553
556
  return false if rules.max_items && length > rules.max_items
554
557
  return false if rules.min_items && length < rules.min_items
555
- if rules.unique
556
- index = 1
557
- while index < length
558
- previous_index = 0
559
- while previous_index < index
560
- return false if json_equal?(value[previous_index], value[index])
561
- previous_index += 1
562
- end
563
- index += 1
564
- end
565
- end
558
+ return false if rules.unique && !unique_items?(value)
566
559
 
567
560
  if (prefix_items = rules.prefix_items)
568
561
  prefix_items.each_with_index do |child, index|
@@ -616,25 +609,10 @@ module Schemurai
616
609
 
617
610
  add_error("minItems") { Internal::ErrorMessage.size("minItems", rules.min_items, value.length) }
618
611
  end
619
- if rules.unique
620
- duplicate = false
621
- index = 1
622
- while index < value.length && !duplicate
623
- previous_index = 0
624
- while previous_index < index
625
- if json_equal?(value[previous_index], value[index])
626
- duplicate = true
627
- break
628
- end
629
- previous_index += 1
630
- end
631
- index += 1
632
- end
633
- if duplicate
634
- return Evaluation.invalid unless @errors
612
+ if rules.unique && !unique_items?(value)
613
+ return Evaluation.invalid unless @errors
635
614
 
636
- add_error("uniqueItems") { Internal::ErrorMessage.unique_items }
637
- end
615
+ add_error("uniqueItems") { Internal::ErrorMessage.unique_items }
638
616
  end
639
617
 
640
618
  if (prefix_items = rules.prefix_items)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: schemurai
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.2.1
4
+ version: 2.2.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kuya KOHARA (oakcask)
@@ -53,6 +53,7 @@ files:
53
53
  - lib/schemurai/schema_domain.rb
54
54
  - lib/schemurai/schema_graph.rb
55
55
  - lib/schemurai/schema_node.rb
56
+ - lib/schemurai/unique_items.rb
56
57
  - lib/schemurai/version.rb
57
58
  - lib/schemurai/vm/compiler.rb
58
59
  - lib/schemurai/vm/evaluator.rb
@@ -63,7 +64,7 @@ licenses:
63
64
  metadata:
64
65
  homepage_uri: https://github.com/oakcask/schemurai
65
66
  source_code_uri: https://github.com/oakcask/schemurai/tree/main
66
- documentation_uri: https://rubydoc.info/gems/schemurai/2.2.1
67
+ documentation_uri: https://rubydoc.info/gems/schemurai/2.2.2
67
68
  rubygems_mfa_required: 'true'
68
69
  rdoc_options:
69
70
  - "--main"
@@ -87,7 +88,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
87
88
  - !ruby/object:Gem::Version
88
89
  version: '0'
89
90
  requirements: []
90
- rubygems_version: 4.0.16
91
+ rubygems_version: 4.0.20
91
92
  specification_version: 4
92
93
  summary: A fast, Ractor-safe, pure Ruby JSON Schema Draft 7+ validator
93
94
  test_files: []