schemurai 1.0.0 → 2.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 +59 -3
- data/lib/schemurai/backend.rb +32 -0
- data/lib/schemurai/evaluator.rb +6 -2
- data/lib/schemurai/meta_schemas/draft2019_09.rb +103 -2
- data/lib/schemurai/meta_schemas/draft2020_12.rb +103 -2
- data/lib/schemurai/schema_domain.rb +82 -0
- data/lib/schemurai/schema_graph.rb +21 -6
- data/lib/schemurai/version.rb +2 -1
- data/lib/schemurai/vm/compiler.rb +377 -0
- data/lib/schemurai/vm/evaluator.rb +798 -0
- data/lib/schemurai.rb +164 -18
- metadata +20 -5
data/lib/schemurai.rb
CHANGED
|
@@ -1,28 +1,62 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "schemurai/version"
|
|
4
|
+
require_relative "schemurai/backend"
|
|
4
5
|
require_relative "schemurai/formats"
|
|
5
6
|
require_relative "schemurai/schema_graph"
|
|
6
7
|
require_relative "schemurai/evaluator"
|
|
7
8
|
|
|
9
|
+
# Public API for compiling and evaluating JSON Schemas.
|
|
8
10
|
module Schemurai
|
|
11
|
+
# Base class for errors raised by Schemurai.
|
|
9
12
|
class Error < StandardError; end
|
|
13
|
+
|
|
14
|
+
# Raised when a schema or URI cannot be resolved from a registry.
|
|
10
15
|
class ResolutionError < Error; end
|
|
16
|
+
|
|
17
|
+
# Raised when a schema requires a format assertion Schemurai does not support.
|
|
11
18
|
class UnsupportedFormatError < Error; end
|
|
12
19
|
|
|
13
|
-
|
|
20
|
+
# Raised when a schema does not conform to its declared meta-schema.
|
|
21
|
+
class InvalidSchemaError < Error
|
|
22
|
+
# The detailed meta-schema validation result.
|
|
23
|
+
attr_reader :result
|
|
24
|
+
|
|
25
|
+
def initialize(result)
|
|
26
|
+
@result = result
|
|
27
|
+
first_error = result.errors.first
|
|
28
|
+
message = if first_error
|
|
29
|
+
"invalid schema at #{first_error.instance_path.inspect}: #{first_error.message}"
|
|
30
|
+
else
|
|
31
|
+
"invalid schema"
|
|
32
|
+
end
|
|
33
|
+
super(message)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Describes one failed JSON Schema assertion.
|
|
38
|
+
#
|
|
39
|
+
# +instance_path+ and +schema_path+ are JSON Pointers. An empty string points
|
|
40
|
+
# to the root instance or schema.
|
|
41
|
+
class ValidationError < Data.define(:keyword, :instance_path, :schema_path, :message)
|
|
42
|
+
# Returns the error fields as a Hash.
|
|
14
43
|
def to_h
|
|
15
44
|
{keyword: keyword, instance_path: instance_path, schema_path: schema_path, message: message}
|
|
16
45
|
end
|
|
17
46
|
end
|
|
18
47
|
|
|
48
|
+
# The detailed result of validating one instance.
|
|
19
49
|
class Result
|
|
50
|
+
# The frozen array of ValidationError objects. It is empty when validation
|
|
51
|
+
# succeeds.
|
|
20
52
|
attr_reader :errors
|
|
21
53
|
|
|
54
|
+
# Creates a result from an array of ValidationError objects.
|
|
22
55
|
def initialize(errors)
|
|
23
56
|
@errors = errors.freeze
|
|
24
57
|
end
|
|
25
58
|
|
|
59
|
+
# Returns whether the instance passed validation.
|
|
26
60
|
def valid?
|
|
27
61
|
errors.empty?
|
|
28
62
|
end
|
|
@@ -30,57 +64,144 @@ module Schemurai
|
|
|
30
64
|
alias_method :success?, :valid?
|
|
31
65
|
end
|
|
32
66
|
|
|
67
|
+
# Compiles schemas that share registered external schemas and backend state.
|
|
68
|
+
#
|
|
69
|
+
# Register every external schema with +schemas:+ before compiling. A registry
|
|
70
|
+
# can be made shareable once registration and compilation are complete.
|
|
33
71
|
class SchemaRegistry
|
|
34
|
-
|
|
72
|
+
# The selected backend, either +:ruby+ or +:vm+.
|
|
73
|
+
attr_reader :backend
|
|
74
|
+
|
|
75
|
+
# Creates a registry.
|
|
76
|
+
#
|
|
77
|
+
# +schemas+ maps absolute URI strings to JSON-shaped Ruby schemas. Pass
|
|
78
|
+
# +validate_schema: true+ to validate every registered and compiled schema
|
|
79
|
+
# against its meta-schema. +backend+ accepts +:ruby+, +:vm+, or +:default+.
|
|
80
|
+
def initialize(schemas: {}, validate_schema: false, backend: Backend.requested)
|
|
81
|
+
@backend = Backend.resolve(backend)
|
|
82
|
+
@validate_schema = validate_schema
|
|
35
83
|
@graph = Internal::SchemaGraph.new(schemas: schemas)
|
|
36
|
-
@
|
|
84
|
+
@compiler = VM::Compiler.new(@graph) if @backend == :vm
|
|
85
|
+
schemas.each_value { |schema| validate_schema!(schema) } if validate_schema?
|
|
37
86
|
end
|
|
38
87
|
|
|
88
|
+
# Returns whether this registry validates every schema against its meta-schema.
|
|
89
|
+
def validate_schema?
|
|
90
|
+
@validate_schema
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Compiles +schema+ and returns a Validator.
|
|
94
|
+
#
|
|
95
|
+
# +base_uri+ supplies the schema's base URI. Pass +content: true+ or
|
|
96
|
+
# +format: true+ to enable the corresponding optional assertions.
|
|
39
97
|
def compile(schema, base_uri: nil, content: false, format: false)
|
|
40
|
-
raise Error, "cannot compile schemas after the registry is made shareable" if
|
|
98
|
+
raise Error, "cannot compile schemas after the registry is made shareable" if shareable?
|
|
41
99
|
|
|
100
|
+
validate_schema!(schema) if validate_schema?
|
|
42
101
|
root = @graph.compile(schema, base_uri: base_uri)
|
|
43
|
-
|
|
102
|
+
build_validator(root, content: content, format: format)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Validates +schema+ against its declared meta-schema and returns a Result.
|
|
106
|
+
# Schemas without +$schema+ are validated as Draft 7.
|
|
107
|
+
def validate_schema(schema)
|
|
108
|
+
raise Error, "cannot validate schemas after the registry is made shareable" if shareable?
|
|
109
|
+
|
|
110
|
+
root = @graph.meta_schema_root(schema)
|
|
111
|
+
build_validator(root, content: false, format: false).validate(schema)
|
|
44
112
|
end
|
|
45
113
|
|
|
114
|
+
# Returns whether +schema+ conforms to its declared meta-schema.
|
|
115
|
+
def valid_schema?(schema)
|
|
116
|
+
validate_schema(schema).valid?
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Resolves and freezes registered schemas, then makes the registry Ractor-shareable.
|
|
120
|
+
#
|
|
121
|
+
# Returns +self+. No more schemas can be compiled after this transition.
|
|
46
122
|
def make_shareable
|
|
47
|
-
return self if
|
|
123
|
+
return self if shareable?
|
|
48
124
|
|
|
49
125
|
@graph.make_shareable
|
|
50
|
-
@
|
|
126
|
+
@compiler&.compile_all
|
|
51
127
|
Ractor.make_shareable(self)
|
|
52
128
|
end
|
|
53
129
|
|
|
130
|
+
# Returns whether this registry is Ractor-shareable.
|
|
54
131
|
def shareable?
|
|
55
|
-
|
|
132
|
+
Ractor.shareable?(self)
|
|
56
133
|
end
|
|
57
134
|
|
|
135
|
+
# Returns a Validator for the registered resource at +uri+.
|
|
136
|
+
#
|
|
137
|
+
# The registry must first be made shareable with #make_shareable.
|
|
58
138
|
def validator_for(uri, content: false, format: false)
|
|
59
|
-
raise Error, "make_shareable must be called before retrieving validators by URI" unless
|
|
139
|
+
raise Error, "make_shareable must be called before retrieving validators by URI" unless shareable?
|
|
60
140
|
|
|
61
141
|
root = @graph.node_at(uri)
|
|
62
142
|
raise ResolutionError, "unregistered schema URI #{uri.inspect}" unless root
|
|
63
143
|
|
|
64
|
-
|
|
144
|
+
build_validator(root, content: content, format: format)
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
private def build_validator(root, content:, format:)
|
|
148
|
+
evaluator = if @compiler
|
|
149
|
+
program = @compiler.compile(root)
|
|
150
|
+
VM::Evaluator.new(@graph, @compiler, program, content: content, format: format)
|
|
151
|
+
else
|
|
152
|
+
Internal::Evaluator.new(@graph, root, content: content, format: format)
|
|
153
|
+
end
|
|
154
|
+
Validator.new(evaluator)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
private def validate_schema!(schema)
|
|
158
|
+
result = validate_schema(schema)
|
|
159
|
+
raise InvalidSchemaError, result unless result.valid?
|
|
65
160
|
end
|
|
66
161
|
end
|
|
67
162
|
|
|
163
|
+
# A reusable compiled JSON Schema validator.
|
|
164
|
+
#
|
|
165
|
+
# Create validators through Schemurai.compile or SchemaRegistry#compile.
|
|
166
|
+
# Validator instances contain mutable evaluation state and must not be used
|
|
167
|
+
# concurrently or shared between Ractors.
|
|
68
168
|
class Validator
|
|
69
|
-
|
|
70
|
-
|
|
169
|
+
# Wraps an evaluator implementation.
|
|
170
|
+
#
|
|
171
|
+
# Applications should normally create validators through Schemurai.compile
|
|
172
|
+
# or SchemaRegistry#compile.
|
|
173
|
+
def initialize(evaluator)
|
|
174
|
+
@evaluator = evaluator
|
|
71
175
|
end
|
|
72
176
|
|
|
177
|
+
# The backend reported by the evaluator, either +:ruby+ or +:vm+.
|
|
178
|
+
def backend
|
|
179
|
+
@evaluator.backend
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Validates +instance+ and returns a Result containing all reported errors.
|
|
73
183
|
def validate(instance)
|
|
74
184
|
@evaluator.validate(instance)
|
|
75
185
|
end
|
|
76
186
|
|
|
187
|
+
# Returns whether +instance+ is valid without building a detailed error list.
|
|
77
188
|
def valid?(instance)
|
|
78
189
|
@evaluator.valid?(instance)
|
|
79
190
|
end
|
|
80
191
|
end
|
|
81
192
|
|
|
82
|
-
|
|
83
|
-
|
|
193
|
+
# Returns the backend selected by SCHEMURAI_BACKEND, either +:ruby+ or +:vm+.
|
|
194
|
+
module_function def backend
|
|
195
|
+
Backend.resolve
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Compiles +schema+ and returns a reusable Validator.
|
|
199
|
+
#
|
|
200
|
+
# +schemas+ maps external URI strings to schemas. +base_uri+ supplies the
|
|
201
|
+
# schema's base URI. Optional content and format assertions are disabled by
|
|
202
|
+
# default. +backend+ accepts +:ruby+, +:vm+, or +:default+.
|
|
203
|
+
module_function def compile(schema, schemas: {}, base_uri: nil, content: false, format: false, validate_schema: false, backend: Backend.requested)
|
|
204
|
+
SchemaRegistry.new(schemas: schemas, validate_schema: validate_schema, backend: backend).compile(
|
|
84
205
|
schema,
|
|
85
206
|
base_uri: base_uri,
|
|
86
207
|
content: content,
|
|
@@ -88,23 +209,48 @@ module Schemurai
|
|
|
88
209
|
)
|
|
89
210
|
end
|
|
90
211
|
|
|
91
|
-
|
|
212
|
+
# Validates +schema+ against its declared meta-schema and returns a Result.
|
|
213
|
+
# Schemas without +$schema+ are validated as Draft 7.
|
|
214
|
+
module_function def validate_schema(schema, schemas: {}, backend: Backend.requested)
|
|
215
|
+
SchemaRegistry.new(schemas: schemas, backend: backend).validate_schema(schema)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Returns whether +schema+ conforms to its declared meta-schema.
|
|
219
|
+
module_function def valid_schema?(schema, schemas: {}, backend: Backend.requested)
|
|
220
|
+
validate_schema(schema, schemas: schemas, backend: backend).valid?
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Validates +instance+ against +schema+ and returns a Result.
|
|
224
|
+
#
|
|
225
|
+
# This convenience method compiles the schema for each call. Use .compile
|
|
226
|
+
# when validating multiple instances against the same schema.
|
|
227
|
+
module_function def validate(schema, instance, schemas: {}, base_uri: nil, content: false, format: false, validate_schema: false, backend: Backend.requested)
|
|
92
228
|
compile(
|
|
93
229
|
schema,
|
|
94
230
|
schemas: schemas,
|
|
95
231
|
base_uri: base_uri,
|
|
96
232
|
content: content,
|
|
97
|
-
format: format
|
|
233
|
+
format: format,
|
|
234
|
+
validate_schema: validate_schema,
|
|
235
|
+
backend: backend
|
|
98
236
|
).validate(instance)
|
|
99
237
|
end
|
|
100
238
|
|
|
101
|
-
|
|
239
|
+
# Returns whether +instance+ is valid against +schema+.
|
|
240
|
+
#
|
|
241
|
+
# This convenience method compiles the schema for each call. Use .compile
|
|
242
|
+
# when validating multiple instances against the same schema.
|
|
243
|
+
module_function def valid?(schema, instance, schemas: {}, base_uri: nil, content: false, format: false, validate_schema: false, backend: Backend.requested)
|
|
102
244
|
compile(
|
|
103
245
|
schema,
|
|
104
246
|
schemas: schemas,
|
|
105
247
|
base_uri: base_uri,
|
|
106
248
|
content: content,
|
|
107
|
-
format: format
|
|
249
|
+
format: format,
|
|
250
|
+
validate_schema: validate_schema,
|
|
251
|
+
backend: backend
|
|
108
252
|
).valid?(instance)
|
|
109
253
|
end
|
|
254
|
+
|
|
255
|
+
private_constant :Backend
|
|
110
256
|
end
|
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:
|
|
4
|
+
version: 2.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kuya KOHARA (oakcask)
|
|
@@ -29,11 +29,14 @@ email:
|
|
|
29
29
|
- kuya.kohara@gmail.com
|
|
30
30
|
executables: []
|
|
31
31
|
extensions: []
|
|
32
|
-
extra_rdoc_files:
|
|
32
|
+
extra_rdoc_files:
|
|
33
|
+
- LICENSE
|
|
34
|
+
- README.md
|
|
33
35
|
files:
|
|
34
36
|
- LICENSE
|
|
35
37
|
- README.md
|
|
36
38
|
- lib/schemurai.rb
|
|
39
|
+
- lib/schemurai/backend.rb
|
|
37
40
|
- lib/schemurai/dialect.rb
|
|
38
41
|
- lib/schemurai/dialect_keywords.rb
|
|
39
42
|
- lib/schemurai/dialects/draft2019_09.rb
|
|
@@ -46,24 +49,36 @@ files:
|
|
|
46
49
|
- lib/schemurai/meta_schemas/draft2019_09.rb
|
|
47
50
|
- lib/schemurai/meta_schemas/draft2020_12.rb
|
|
48
51
|
- lib/schemurai/meta_schemas/draft7.rb
|
|
52
|
+
- lib/schemurai/schema_domain.rb
|
|
49
53
|
- lib/schemurai/schema_graph.rb
|
|
50
54
|
- lib/schemurai/schema_node.rb
|
|
51
55
|
- lib/schemurai/version.rb
|
|
56
|
+
- lib/schemurai/vm/compiler.rb
|
|
57
|
+
- lib/schemurai/vm/evaluator.rb
|
|
52
58
|
homepage: https://github.com/oakcask/schemurai
|
|
53
59
|
licenses:
|
|
54
60
|
- MIT
|
|
55
61
|
metadata:
|
|
56
62
|
homepage_uri: https://github.com/oakcask/schemurai
|
|
57
63
|
source_code_uri: https://github.com/oakcask/schemurai/tree/main
|
|
64
|
+
documentation_uri: https://rubydoc.info/gems/schemurai/2.0.0
|
|
58
65
|
rubygems_mfa_required: 'true'
|
|
59
|
-
rdoc_options:
|
|
66
|
+
rdoc_options:
|
|
67
|
+
- "--main"
|
|
68
|
+
- README.md
|
|
69
|
+
- "--title"
|
|
70
|
+
- Schemurai API Documentation
|
|
71
|
+
- "--visibility"
|
|
72
|
+
- public
|
|
73
|
+
- "--exclude"
|
|
74
|
+
- lib/schemurai/(?!version\.rb)
|
|
60
75
|
require_paths:
|
|
61
76
|
- lib
|
|
62
77
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
63
78
|
requirements:
|
|
64
79
|
- - ">="
|
|
65
80
|
- !ruby/object:Gem::Version
|
|
66
|
-
version: '3.
|
|
81
|
+
version: '3.4'
|
|
67
82
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
68
83
|
requirements:
|
|
69
84
|
- - ">="
|
|
@@ -72,5 +87,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
72
87
|
requirements: []
|
|
73
88
|
rubygems_version: 4.0.16
|
|
74
89
|
specification_version: 4
|
|
75
|
-
summary: A
|
|
90
|
+
summary: A fast, Ractor-safe, pure Ruby JSON Schema Draft 7+ validator
|
|
76
91
|
test_files: []
|