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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +395 -0
- data/lib/schemurai/backend.rb +34 -0
- data/lib/schemurai/container_checks/array_diagnostics.rb +119 -0
- data/lib/schemurai/container_checks/object_diagnostics.rb +152 -0
- data/lib/schemurai/container_checks.rb +534 -0
- data/lib/schemurai/decimal.rb +11 -0
- data/lib/schemurai/dialect.rb +106 -0
- data/lib/schemurai/dialect_keywords.rb +91 -0
- data/lib/schemurai/dialects/draft2019_09.rb +27 -0
- data/lib/schemurai/dialects/draft2020_12.rb +27 -0
- data/lib/schemurai/dialects/draft7.rb +27 -0
- data/lib/schemurai/ecma_regexp.rb +46 -0
- data/lib/schemurai/error_message.rb +102 -0
- data/lib/schemurai/evaluation.rb +63 -0
- data/lib/schemurai/evaluation_limits.rb +65 -0
- data/lib/schemurai/evaluation_pool.rb +66 -0
- data/lib/schemurai/evaluator.rb +861 -0
- data/lib/schemurai/formats.rb +403 -0
- data/lib/schemurai/json_pointer.rb +11 -0
- data/lib/schemurai/limit_budget.rb +78 -0
- data/lib/schemurai/limit_exceeded.rb +18 -0
- data/lib/schemurai/limited_evaluator.rb +43 -0
- data/lib/schemurai/meta_schemas/draft2019_09.rb +149 -0
- data/lib/schemurai/meta_schemas/draft2020_12.rb +149 -0
- data/lib/schemurai/meta_schemas/draft7.rb +121 -0
- data/lib/schemurai/meta_schemas.rb +28 -0
- data/lib/schemurai/native/4.0/schemurai_native.so +0 -0
- data/lib/schemurai/native/diagnostics.rb +53 -0
- data/lib/schemurai/native/evaluator.rb +87 -0
- data/lib/schemurai/native/null_numeric.rb +23 -0
- data/lib/schemurai/number_checks.rb +128 -0
- data/lib/schemurai/schema_domain.rb +95 -0
- data/lib/schemurai/schema_graph.rb +538 -0
- data/lib/schemurai/schema_node.rb +55 -0
- data/lib/schemurai/string_checks.rb +166 -0
- data/lib/schemurai/type_checks.rb +59 -0
- data/lib/schemurai/unique_items.rb +68 -0
- data/lib/schemurai/value_equality.rb +72 -0
- data/lib/schemurai/version.rb +6 -0
- data/lib/schemurai/vm/compiler.rb +410 -0
- data/lib/schemurai/vm/evaluator.rb +663 -0
- data/lib/schemurai/vm/limited_evaluator.rb +27 -0
- data/lib/schemurai.rb +307 -0
- data/lib/schemurai_native.rb +4 -0
- data/sig/schemurai.rbs +108 -0
- metadata +121 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "decimal"
|
|
4
|
+
require_relative "type_checks"
|
|
5
|
+
|
|
6
|
+
module Schemurai
|
|
7
|
+
module Internal
|
|
8
|
+
module NumberChecks
|
|
9
|
+
MAXIMUM = 1 << 0
|
|
10
|
+
MINIMUM = 1 << 1
|
|
11
|
+
EXCLUSIVE_MAXIMUM = 1 << 2
|
|
12
|
+
EXCLUSIVE_MINIMUM = 1 << 3
|
|
13
|
+
MULTIPLE_OF = 1 << 4
|
|
14
|
+
|
|
15
|
+
class State
|
|
16
|
+
CACHE_LIMIT = 16
|
|
17
|
+
private_constant :CACHE_LIMIT
|
|
18
|
+
|
|
19
|
+
attr_writer :budget
|
|
20
|
+
|
|
21
|
+
def decimal(value)
|
|
22
|
+
@budget&.check_deadline!
|
|
23
|
+
result = cached_decimal(value)
|
|
24
|
+
@budget&.check_deadline!
|
|
25
|
+
result
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private def cached_decimal(value)
|
|
29
|
+
return value if value.is_a?(Integer) || value.is_a?(Rational)
|
|
30
|
+
|
|
31
|
+
decimals = (@decimals ||= {})
|
|
32
|
+
return decimals[value] if decimals.key?(value)
|
|
33
|
+
|
|
34
|
+
decimals.clear if decimals.length >= CACHE_LIMIT
|
|
35
|
+
decimals[value] = Decimal.parse(value)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def multiple?(rules, value)
|
|
39
|
+
results = (@multiple_results ||= {}.compare_by_identity)
|
|
40
|
+
values = (results[rules] ||= {})
|
|
41
|
+
return values[value] if values.key?(value)
|
|
42
|
+
|
|
43
|
+
values.clear if values.length >= CACHE_LIMIT
|
|
44
|
+
divisor = rules.multiple_of
|
|
45
|
+
values[value] = divisor.positive? && decimal(value).remainder(divisor).zero?
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
module_function def actual(rules, value, state)
|
|
50
|
+
(rules.mask & MULTIPLE_OF).zero? ? value : state.decimal(value)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
module_function def bound?(actual, bit, limit)
|
|
54
|
+
violated = case bit
|
|
55
|
+
when MAXIMUM then actual > limit
|
|
56
|
+
when MINIMUM then actual < limit
|
|
57
|
+
when EXCLUSIVE_MAXIMUM then actual >= limit
|
|
58
|
+
when EXCLUSIVE_MINIMUM then actual <= limit
|
|
59
|
+
else raise "unknown numeric bound"
|
|
60
|
+
end
|
|
61
|
+
violated ? false : true
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Both the VM and native validity frames use the same ordered rules.
|
|
65
|
+
module Evaluation
|
|
66
|
+
private def number_state
|
|
67
|
+
@number_state ||= State.new
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private def valid_number?(rules, value)
|
|
71
|
+
mask = rules.mask
|
|
72
|
+
actual = (mask & MULTIPLE_OF).zero? ? value : number_state.decimal(value)
|
|
73
|
+
return false if (mask & MAXIMUM) != 0 && actual > rules.maximum
|
|
74
|
+
return false if (mask & MINIMUM) != 0 && actual < rules.minimum
|
|
75
|
+
return false if (mask & EXCLUSIVE_MAXIMUM) != 0 && actual >= rules.exclusive_maximum
|
|
76
|
+
return false if (mask & EXCLUSIVE_MINIMUM) != 0 && actual <= rules.exclusive_minimum
|
|
77
|
+
return true if (mask & MULTIPLE_OF).zero?
|
|
78
|
+
|
|
79
|
+
valid_multiple?(rules, value)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private def valid_multiple?(rules, value)
|
|
83
|
+
number_state.multiple?(rules, value)
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# One native numeric instruction owns this frame while Rust yields between
|
|
88
|
+
# reached assertions. No instance method runs during construction.
|
|
89
|
+
class Frame
|
|
90
|
+
include Evaluation
|
|
91
|
+
|
|
92
|
+
def initialize(rules, value, state)
|
|
93
|
+
@rules = rules
|
|
94
|
+
@value = value
|
|
95
|
+
@number_state = state
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def numeric? = TypeChecks.number?(@value)
|
|
99
|
+
def integral? = TypeChecks.integral?(@value)
|
|
100
|
+
|
|
101
|
+
def valid?(required)
|
|
102
|
+
return required.zero? unless numeric?
|
|
103
|
+
if required == TypeChecks::INTEGER
|
|
104
|
+
return false unless integral?
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
valid_number?(@rules, @value)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def prepare
|
|
111
|
+
@actual = NumberChecks.actual(@rules, @value, @number_state)
|
|
112
|
+
true
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def check(bit)
|
|
116
|
+
case bit
|
|
117
|
+
when MAXIMUM then NumberChecks.bound?(@actual, bit, @rules.maximum)
|
|
118
|
+
when MINIMUM then NumberChecks.bound?(@actual, bit, @rules.minimum)
|
|
119
|
+
when EXCLUSIVE_MAXIMUM then NumberChecks.bound?(@actual, bit, @rules.exclusive_maximum)
|
|
120
|
+
when EXCLUSIVE_MINIMUM then NumberChecks.bound?(@actual, bit, @rules.exclusive_minimum)
|
|
121
|
+
when MULTIPLE_OF then @number_state.multiple?(@rules, @value)
|
|
122
|
+
else raise "unknown numeric assertion"
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Schemurai
|
|
4
|
+
module Internal
|
|
5
|
+
module SchemaDomain
|
|
6
|
+
module_function def validate!(schema, label: "schema")
|
|
7
|
+
unless schema.equal?(true) || schema.equal?(false) || schema.instance_of?(Hash)
|
|
8
|
+
invalid!(label, "must be a boolean or an object")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
validate_value!(schema, label, [], {}, {})
|
|
12
|
+
schema
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module_function def validate_registry!(schemas)
|
|
16
|
+
invalid!("schemas", "must be an object") unless schemas.instance_of?(Hash)
|
|
17
|
+
|
|
18
|
+
schemas.each do |uri, schema|
|
|
19
|
+
invalid!("schemas", "contains a non-string URI key") unless uri.instance_of?(String)
|
|
20
|
+
|
|
21
|
+
validate!(schema, label: "schemas[#{uri.inspect}]")
|
|
22
|
+
end
|
|
23
|
+
schemas
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
module_function def validate_value!(value, label, path, ancestors, completed)
|
|
27
|
+
case value
|
|
28
|
+
when nil, true, false
|
|
29
|
+
nil
|
|
30
|
+
when String, Integer
|
|
31
|
+
invalid_at!(label, path, "uses a subclass of #{value.class.superclass}") unless supported_exact_scalar?(value)
|
|
32
|
+
when Float
|
|
33
|
+
invalid_at!(label, path, "must contain only finite numbers") unless value.finite?
|
|
34
|
+
invalid_at!(label, path, "uses a Float subclass") unless value.instance_of?(Float)
|
|
35
|
+
when Array
|
|
36
|
+
invalid_at!(label, path, "uses an Array subclass") unless value.instance_of?(Array)
|
|
37
|
+
descend!(value, label, path, ancestors, completed) do
|
|
38
|
+
value.each_with_index do |item, index|
|
|
39
|
+
path << index
|
|
40
|
+
validate_value!(item, label, path, ancestors, completed)
|
|
41
|
+
path.pop
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
when Hash
|
|
45
|
+
invalid_at!(label, path, "uses a Hash subclass") unless value.instance_of?(Hash)
|
|
46
|
+
descend!(value, label, path, ancestors, completed) do
|
|
47
|
+
value.each do |key, item|
|
|
48
|
+
invalid_at!(label, path, "contains a non-string object key") unless key.instance_of?(String)
|
|
49
|
+
|
|
50
|
+
path << key
|
|
51
|
+
validate_value!(item, label, path, ancestors, completed)
|
|
52
|
+
path.pop
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
else
|
|
56
|
+
invalid_at!(label, path, "contains unsupported #{value.class}")
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
private_class_method :validate_value!
|
|
60
|
+
|
|
61
|
+
module_function def supported_exact_scalar?(value)
|
|
62
|
+
value.instance_of?(String) || value.instance_of?(Integer)
|
|
63
|
+
end
|
|
64
|
+
private_class_method :supported_exact_scalar?
|
|
65
|
+
|
|
66
|
+
module_function def descend!(value, label, path, ancestors, completed)
|
|
67
|
+
# A completed subtree cannot contain a cycle back to this path. Only
|
|
68
|
+
# publish after the entire subtree passes, preserving first-error paths.
|
|
69
|
+
return if completed.key?(value.object_id)
|
|
70
|
+
|
|
71
|
+
invalid_at!(label, path, "contains a recursive container") if ancestors.key?(value.object_id)
|
|
72
|
+
|
|
73
|
+
ancestors[value.object_id] = true
|
|
74
|
+
yield
|
|
75
|
+
completed[value.object_id] = true
|
|
76
|
+
ensure
|
|
77
|
+
ancestors.delete(value.object_id)
|
|
78
|
+
end
|
|
79
|
+
private_class_method :descend!
|
|
80
|
+
|
|
81
|
+
module_function def invalid_at!(label, path, reason)
|
|
82
|
+
location = path.reduce(label.dup) do |result, segment|
|
|
83
|
+
result << "/" << segment.to_s.gsub("~", "~0").gsub("/", "~1")
|
|
84
|
+
end
|
|
85
|
+
invalid!(location, reason)
|
|
86
|
+
end
|
|
87
|
+
private_class_method :invalid_at!
|
|
88
|
+
|
|
89
|
+
module_function def invalid!(path, reason)
|
|
90
|
+
raise Error, "invalid JSON-shaped #{path}: #{reason}"
|
|
91
|
+
end
|
|
92
|
+
private_class_method :invalid!
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|