schemurai 3.1.0-aarch64-linux-gnu

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 (48) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +395 -0
  4. data/lib/schemurai/backend.rb +34 -0
  5. data/lib/schemurai/container_checks/array_diagnostics.rb +119 -0
  6. data/lib/schemurai/container_checks/object_diagnostics.rb +152 -0
  7. data/lib/schemurai/container_checks.rb +534 -0
  8. data/lib/schemurai/decimal.rb +11 -0
  9. data/lib/schemurai/dialect.rb +106 -0
  10. data/lib/schemurai/dialect_keywords.rb +91 -0
  11. data/lib/schemurai/dialects/draft2019_09.rb +27 -0
  12. data/lib/schemurai/dialects/draft2020_12.rb +27 -0
  13. data/lib/schemurai/dialects/draft7.rb +27 -0
  14. data/lib/schemurai/ecma_regexp.rb +46 -0
  15. data/lib/schemurai/error_message.rb +102 -0
  16. data/lib/schemurai/evaluation.rb +63 -0
  17. data/lib/schemurai/evaluation_limits.rb +65 -0
  18. data/lib/schemurai/evaluation_pool.rb +66 -0
  19. data/lib/schemurai/evaluator.rb +861 -0
  20. data/lib/schemurai/formats.rb +403 -0
  21. data/lib/schemurai/json_pointer.rb +11 -0
  22. data/lib/schemurai/limit_budget.rb +78 -0
  23. data/lib/schemurai/limit_exceeded.rb +18 -0
  24. data/lib/schemurai/limited_evaluator.rb +43 -0
  25. data/lib/schemurai/meta_schemas/draft2019_09.rb +149 -0
  26. data/lib/schemurai/meta_schemas/draft2020_12.rb +149 -0
  27. data/lib/schemurai/meta_schemas/draft7.rb +121 -0
  28. data/lib/schemurai/meta_schemas.rb +28 -0
  29. data/lib/schemurai/native/4.0/schemurai_native.so +0 -0
  30. data/lib/schemurai/native/diagnostics.rb +53 -0
  31. data/lib/schemurai/native/evaluator.rb +87 -0
  32. data/lib/schemurai/native/null_numeric.rb +23 -0
  33. data/lib/schemurai/number_checks.rb +128 -0
  34. data/lib/schemurai/schema_domain.rb +95 -0
  35. data/lib/schemurai/schema_graph.rb +538 -0
  36. data/lib/schemurai/schema_node.rb +55 -0
  37. data/lib/schemurai/string_checks.rb +166 -0
  38. data/lib/schemurai/type_checks.rb +59 -0
  39. data/lib/schemurai/unique_items.rb +68 -0
  40. data/lib/schemurai/value_equality.rb +72 -0
  41. data/lib/schemurai/version.rb +6 -0
  42. data/lib/schemurai/vm/compiler.rb +410 -0
  43. data/lib/schemurai/vm/evaluator.rb +663 -0
  44. data/lib/schemurai/vm/limited_evaluator.rb +27 -0
  45. data/lib/schemurai.rb +307 -0
  46. data/lib/schemurai_native.rb +4 -0
  47. data/sig/schemurai.rbs +108 -0
  48. metadata +121 -0
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "base64"
4
+ require "json"
5
+ require "ipaddr"
6
+ require_relative "ecma_regexp"
7
+ require_relative "error_message"
8
+
9
+ module Schemurai
10
+ module Internal
11
+ module StringChecks
12
+ module Regexps
13
+ private def ecma_regexp(pattern)
14
+ regexps = regexp_cache
15
+ return regexps[pattern] if regexps.key?(pattern)
16
+
17
+ regexps[pattern] = EcmaRegexp.compile(pattern, @budget)
18
+ end
19
+
20
+ private def regexp_cache = (@regexps ||= {})
21
+ end
22
+
23
+ module TimedRegexps
24
+ private def regexp_budget = @budget
25
+
26
+ private def ecma_regexp(pattern)
27
+ budget = regexp_budget
28
+ budget.check_deadline!
29
+ result = super
30
+ budget.check_deadline!
31
+ result
32
+ end
33
+ end
34
+
35
+ module Evaluation
36
+ private def valid_string?(rules, value)
37
+ length = value.length
38
+ return false if rules.max_length && length > rules.max_length
39
+ return false if rules.min_length && length < rules.min_length
40
+ return false if rules.pattern && !ecma_regexp(rules.pattern).match?(value)
41
+ if rules.format && (@validate_format || rules.format_assertion)
42
+ return false unless rules.format.call(value)
43
+ end
44
+ if @validate_content && (rules.decode_base64 || rules.parse_json)
45
+ return valid_content?(rules, value)
46
+ end
47
+
48
+ true
49
+ rescue RegexpError, IPAddr::InvalidAddressError
50
+ false
51
+ end
52
+
53
+ private def valid_content?(rules, value)
54
+ decoded = rules.decode_base64 ? Base64.strict_decode64(value) : value
55
+ JSON.parse(decoded) if rules.parse_json
56
+ true
57
+ rescue ArgumentError, JSON::ParserError
58
+ false
59
+ end
60
+ end
61
+
62
+ class State
63
+ include Regexps
64
+
65
+ attr_reader :budget
66
+
67
+ def ecma_regexp(pattern)
68
+ @budget&.check_deadline!
69
+ result = super
70
+ @budget&.check_deadline!
71
+ result
72
+ end
73
+
74
+ def initialize(regexps, budget)
75
+ @regexps = regexps
76
+ @budget = budget
77
+ end
78
+ end
79
+
80
+ # Rust owns traversal and error limits; this frame retains arbitrary Ruby
81
+ # length results and the VM's rescue state between reached assertions.
82
+ class Frame
83
+ include Evaluation
84
+
85
+ def initialize(rules, value, state, flags)
86
+ @rules = rules
87
+ @value = value
88
+ @state = state
89
+ @budget = state.budget
90
+ @validate_format = (flags & 1) != 0
91
+ @validate_content = (flags & 2) != 0
92
+ @required = (flags & 64) != 0
93
+ @invalid_pattern = false
94
+ end
95
+
96
+ def string? = @value.is_a?(String)
97
+ def invalid_pattern? = @invalid_pattern
98
+
99
+ def valid?
100
+ return !@required unless string?
101
+
102
+ valid_string?(@rules, @value)
103
+ end
104
+
105
+ def prepare
106
+ @budget&.check_deadline!
107
+ check(2)
108
+ end
109
+
110
+ def check(step)
111
+ case step
112
+ when 2
113
+ @length = @value.length
114
+ true
115
+ when 3
116
+ exceeded = @length > @rules.max_length
117
+ exceeded ? false : true
118
+ when 4
119
+ below = @length < @rules.min_length
120
+ below ? false : true
121
+ when 5 then (!ecma_regexp(@rules.pattern).match?(@value)) ? false : true
122
+ when 6 then (!@rules.format.call(@value)) ? false : true
123
+ when 7 then valid_content?(@rules, @value)
124
+ else raise "unknown string assertion"
125
+ end
126
+ rescue RegexpError
127
+ @invalid_pattern = true
128
+ false
129
+ end
130
+
131
+ def message(keyword, rescued)
132
+ return ErrorMessage.invalid_pattern(@rules.pattern) if rescued
133
+
134
+ case keyword
135
+ when "type" then ErrorMessage.type("string", @value)
136
+ when "maxLength" then ErrorMessage.size(keyword, @rules.max_length, @length)
137
+ when "minLength" then ErrorMessage.size(keyword, @rules.min_length, @length)
138
+ when "pattern" then @invalid_pattern ? ErrorMessage.invalid_pattern(@rules.pattern) : ErrorMessage.pattern(@rules.pattern)
139
+ when "format" then ErrorMessage.format(@rules.format.name)
140
+ when "contentEncoding" then ErrorMessage.content_encoding
141
+ when "contentMediaType" then ErrorMessage.content_media_type
142
+ else raise "unknown string diagnostic"
143
+ end
144
+ end
145
+
146
+ private def valid_string?(rules, value)
147
+ @budget&.check_deadline!
148
+ result = super
149
+ @budget&.check_deadline!
150
+ result
151
+ end
152
+
153
+ private def valid_content?(rules, value)
154
+ @budget&.check_deadline!
155
+ result = super
156
+ @budget&.check_deadline!
157
+ result
158
+ end
159
+
160
+ private def ecma_regexp(pattern)
161
+ @state.ecma_regexp(pattern)
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Schemurai
4
+ module Internal
5
+ module TypeChecks
6
+ NULL = 1 << 0
7
+ BOOLEAN = 1 << 1
8
+ OBJECT = 1 << 2
9
+ ARRAY = 1 << 3
10
+ NUMBER = 1 << 4
11
+ INTEGER = 1 << 5
12
+ STRING = 1 << 6
13
+ BITS = {"null" => NULL, "boolean" => BOOLEAN, "object" => OBJECT, "array" => ARRAY,
14
+ "number" => NUMBER, "integer" => INTEGER, "string" => STRING}.freeze
15
+
16
+ module_function def number?(value)
17
+ value.is_a?(Numeric) && !value.is_a?(Complex)
18
+ end
19
+
20
+ module_function def integer?(value)
21
+ number?(value) && integral?(value)
22
+ end
23
+
24
+ module_function def integral?(value)
25
+ value.finite? && value.to_i == value
26
+ end
27
+
28
+ module_function def single?(value, mask)
29
+ case mask
30
+ when NULL then value.nil?
31
+ when BOOLEAN then value == true || value == false
32
+ when OBJECT then value.is_a?(Hash)
33
+ when ARRAY then value.is_a?(Array)
34
+ when NUMBER then number?(value)
35
+ when INTEGER then integer?(value)
36
+ when STRING then value.is_a?(String)
37
+ else false
38
+ end
39
+ end
40
+
41
+ # The VM's union opcode classifies in this order, even for a one-name union.
42
+ module_function def instance_mask(value, integer:)
43
+ return NULL if value.nil?
44
+ return BOOLEAN if value == true || value == false
45
+ return OBJECT if value.is_a?(Hash)
46
+ return ARRAY if value.is_a?(Array)
47
+ return STRING if value.is_a?(String)
48
+ return 0 unless number?(value)
49
+ return NUMBER unless integer
50
+
51
+ integral?(value) ? NUMBER | INTEGER : NUMBER
52
+ end
53
+
54
+ module_function def valid?(value, mask, union)
55
+ union ? (mask & instance_mask(value, integer: (mask & INTEGER) != 0)) != 0 : single?(value, mask)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,68 @@
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
+ @budget&.checkpoint
11
+ index.times { |previous| return false if json_equal?(values[previous], item) }
12
+ end
13
+ return true
14
+ end
15
+
16
+ buckets = {}
17
+ # Store a single index until a collision needs a bucket array.
18
+ index = 0
19
+ while index < values.length
20
+ @budget&.checkpoint
21
+ item = values[index]
22
+ fingerprint = json_fingerprint(item)
23
+ if (bucket = buckets[fingerprint])
24
+ if bucket.is_a?(Integer)
25
+ return false if json_equal?(values[bucket], item)
26
+
27
+ buckets[fingerprint] = [bucket, index]
28
+ else
29
+ return false if bucket.any? { |previous| json_equal?(values[previous], item) }
30
+
31
+ bucket << index
32
+ end
33
+ else
34
+ buckets[fingerprint] = index
35
+ end
36
+ index += 1
37
+ end
38
+ true
39
+ end
40
+
41
+ private def json_fingerprint(value)
42
+ case value
43
+ when Numeric
44
+ # JSON numbers compare across Integer/Float. Converting integral
45
+ # floats to integers also preserves precision for large integers.
46
+ return value.hash if value.is_a?(Integer)
47
+
48
+ number = value.to_f
49
+ (number.finite? && number == number.to_i) ? number.to_i.hash : number.hash
50
+ when Array
51
+ # Mask before multiplication to keep intermediate values immediate
52
+ # integers; hash each step to retain order without temporary arrays.
53
+ fingerprint = value.class.hash
54
+ value.each { |item| fingerprint = (((fingerprint & 0x1fffffff) * 31) ^ json_fingerprint(item)).hash }
55
+ fingerprint
56
+ when Hash
57
+ # Object key order is immaterial. Hash collisions are resolved using
58
+ # json_equal?, so this fingerprint never decides equality itself.
59
+ entries = 0
60
+ value.each_pair { |key, item| entries ^= (((key.hash & 0x1fffffff) * 31) ^ json_fingerprint(item)).hash }
61
+ (value.class.hash ^ value.length ^ entries).hash
62
+ else
63
+ value.hash
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "unique_items"
4
+
5
+ module Schemurai
6
+ module Internal
7
+ # Ruby method order for VM/native value comparisons. This traverses values,
8
+ # not schema instructions, and retains custom collection/equality methods.
9
+ module ValueEquality
10
+ private def json_equal?(left, right)
11
+ if left.is_a?(Numeric) && !left.is_a?(Complex)
12
+ return right.is_a?(Numeric) && !right.is_a?(Complex) && left == right
13
+ end
14
+ return false unless left.instance_of?(right.class)
15
+
16
+ case left
17
+ when Hash
18
+ left.length == right.length && left.all? do |key, value|
19
+ @budget&.checkpoint
20
+ right.key?(key) && json_equal?(value, right[key])
21
+ end
22
+ when Array
23
+ return false unless left.length == right.length
24
+
25
+ index = 0
26
+ while index < left.length
27
+ @budget&.checkpoint
28
+ return false unless json_equal?(left[index], right[index])
29
+ index += 1
30
+ end
31
+ true
32
+ else
33
+ left == right
34
+ end
35
+ end
36
+
37
+ module Checkpoints
38
+ private def json_equal?(left, right)
39
+ @budget.checkpoint
40
+ super
41
+ end
42
+
43
+ private def json_fingerprint(value)
44
+ @budget.checkpoint
45
+ super
46
+ end
47
+ end
48
+
49
+ class Runtime
50
+ include ValueEquality
51
+ include UniqueItems
52
+
53
+ def initialize(budget)
54
+ @budget = budget
55
+ end
56
+
57
+ def evaluate(operation, operand, instance)
58
+ case operation
59
+ when 0 then json_equal?(operand, instance)
60
+ when 1 then operand.any? { |candidate| json_equal?(candidate, instance) }
61
+ when 2 then unique_items?(instance)
62
+ else raise "unknown equality operation"
63
+ end
64
+ end
65
+ end
66
+
67
+ class LimitedRuntime < Runtime
68
+ include Checkpoints
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Schemurai
4
+ # The installed Schemurai version.
5
+ VERSION = "3.1.0"
6
+ end