jsonschema_rs 0.42.0-aarch64-linux

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 29e5bf1c85875f60a2e1d02f769b203839c377a3d72c63f8ae0aecf3c47ed7b3
4
+ data.tar.gz: 30e5f0a7e3e427eb2b1d4f5d34b995cf79c2b3fe06649b1a3d3a804d74f85e1f
5
+ SHA512:
6
+ metadata.gz: 226a01d9b43a84e8e29cd2c373ff133a79fb0c9b7ff151d760040b5b8314a22efb873748f95e5a72afdcfd980099cedcdc8aed3004379d8920248912876c0b13
7
+ data.tar.gz: 921ca6bae055f06fbd00f9e680102b67ff69f87ee8b69e7f1cbb29235e02917b894b652a60676429e65831062259f063eb07968eef72356d255f48521dfe9a5c
data/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ## 0.42.0 - 2026-02-15
6
+
7
+ - Initial public release
8
+
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020-2026 Dmitry Dygalo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/MIGRATION.md ADDED
@@ -0,0 +1,87 @@
1
+ # Migrating from json_schemer
2
+
3
+ ## Quick Reference
4
+
5
+ | json_schemer | jsonschema |
6
+ |---|---|
7
+ | `JSONSchemer.schema(s)` | `JSONSchema.validator_for(s)` |
8
+ | `JSONSchemer.schema(s, meta_schema: 'draft7')` | `JSONSchema::Draft7Validator.new(s)` |
9
+ | `schemer.valid?(d)` | `validator.valid?(d)` |
10
+ | `schemer.validate(d)` | `validator.each_error(d)` |
11
+ | `schemer.validate!(d)` | `validator.validate!(d)` |
12
+ | `JSONSchemer.valid?(s, d)` | `JSONSchema.valid?(s, d)` |
13
+ | `JSONSchemer.valid_schema?(s)` | `JSONSchema::Meta.valid?(s)` |
14
+ | `error["data_pointer"]` | `error.instance_path_pointer` |
15
+ | `error["schema_pointer"]` | `error.schema_path_pointer` |
16
+ | `error["type"]` | `error.kind.name` |
17
+ | `error["error"]` | `error.message` |
18
+ | `ref_resolver: proc` | `retriever: proc` |
19
+ | `format: true` | `validate_formats: true` |
20
+
21
+ Draft-specific validators are also available: `JSONSchema::Draft7Validator.new(schema)`
22
+
23
+ ## What Stays the Same
24
+
25
+ - JSON Schema documents work as-is
26
+ - `valid?` and `validate!` method names
27
+ - Custom format validators via `formats:` with the same proc syntax
28
+ - One-off validation: `JSONSchema.valid?(schema, data)`
29
+
30
+ ## Error Objects
31
+
32
+ json_schemer returns hashes, jsonschema returns `ValidationError` objects:
33
+
34
+ ```ruby
35
+ # json_schemer
36
+ error["data_pointer"] # => "/foo/bar"
37
+ error["schema_pointer"] # => "/properties/foo/minimum"
38
+ error["type"] # => "minimum"
39
+ error["error"] # => "value is less than 10"
40
+
41
+ # jsonschema
42
+ error.instance_path # => ["foo", "bar"]
43
+ error.instance_path_pointer # => "/foo/bar" (same format as data_pointer)
44
+ error.schema_path # => ["properties", "foo", "minimum"]
45
+ error.schema_path_pointer # => "/properties/foo/minimum"
46
+ error.kind.name # => "minimum"
47
+ error.message # => "value is less than 10"
48
+
49
+ # Need a hash? Use to_h on error kind
50
+ error.kind.to_h # => { "name" => "minimum", "value" => { "limit" => 10 } }
51
+ ```
52
+
53
+ ## Reference Resolution
54
+
55
+ ```ruby
56
+ # json_schemer
57
+ JSONSchemer.schema(schema, ref_resolver: refs.to_proc)
58
+
59
+ # jsonschema — retriever
60
+ JSONSchema.validator_for(schema, retriever: ->(uri) { fetch_schema(uri) })
61
+
62
+ # jsonschema — registry
63
+ registry = JSONSchema::Registry.new([["http://example.com/s", sub_schema]])
64
+ JSONSchema.validator_for(schema, registry: registry)
65
+ ```
66
+
67
+ ## What You Gain
68
+
69
+ - **Structured output** — `evaluate` API with flag, list, and hierarchical output formats
70
+ - **Custom keywords** — extend JSON Schema with domain-specific validation rules
71
+ - **Error masking** — hide sensitive data in error messages with `mask:`
72
+ - **Regex engine configuration** — choose between fancy-regex (default) and linear-time regex
73
+ - **Email validation options** — fine-grained control over email format validation
74
+
75
+ ## Not Supported
76
+
77
+ - `insert_property_defaults` — jsonschema is a validator, not a data transformer
78
+ - OpenAPI document parsing — use a dedicated OpenAPI library
79
+
80
+ ## Migration Checklist
81
+
82
+ - [ ] Replace `JSONSchemer.schema` with `JSONSchema.validator_for`
83
+ - [ ] Replace `validate` (error iteration) with `each_error`
84
+ - [ ] Replace `ref_resolver:` with `retriever:` or use `Registry`
85
+ - [ ] Replace `format: true` with `validate_formats: true`
86
+ - [ ] Replace `meta_schema: 'draft7'` with `JSONSchema::Draft7Validator.new(s)` or `draft: :draft7` on one-off functions
87
+ - [ ] Update error handling from hash access to `ValidationError` attributes
data/README.md ADDED
@@ -0,0 +1,489 @@
1
+ # jsonschema_rs
2
+
3
+ [![Build](https://img.shields.io/github/actions/workflow/status/Stranger6667/jsonschema/ci.yml?branch=master&style=flat-square)](https://github.com/Stranger6667/jsonschema/actions)
4
+ [![Version](https://img.shields.io/gem/v/jsonschema_rs.svg?style=flat-square)](https://rubygems.org/gems/jsonschema_rs)
5
+ [![Ruby versions](https://img.shields.io/badge/ruby-3.2%20%7C%203.4%20%7C%204.0-blue?style=flat-square)](https://rubygems.org/gems/jsonschema_rs)
6
+ [<img alt="Supported Dialects" src="https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Frust-jsonschema%2Fsupported_versions.json&style=flat-square">](https://bowtie.report/#/implementations/rust-jsonschema)
7
+
8
+ A high-performance JSON Schema validator for Ruby.
9
+
10
+ ```ruby
11
+ require 'jsonschema_rs'
12
+
13
+ schema = { "maxLength" => 5 }
14
+ instance = "foo"
15
+
16
+ # One-off validation
17
+ JSONSchema.valid?(schema, instance) # => true
18
+
19
+ begin
20
+ JSONSchema.validate!(schema, "incorrect")
21
+ rescue JSONSchema::ValidationError => e
22
+ puts e.message # => "\"incorrect\" is longer than 5 characters"
23
+ end
24
+
25
+ # Build & reuse (faster)
26
+ validator = JSONSchema.validator_for(schema)
27
+
28
+ # Iterate over errors
29
+ validator.each_error(instance) do |error|
30
+ puts "Error: #{error.message}"
31
+ puts "Location: #{error.instance_path}"
32
+ end
33
+
34
+ # Boolean result
35
+ validator.valid?(instance) # => true
36
+
37
+ # Structured output (JSON Schema Output v1)
38
+ evaluation = validator.evaluate(instance)
39
+ evaluation.annotations.each do |ann|
40
+ puts "Annotation at #{ann[:schemaLocation]}: #{ann[:annotations]}"
41
+ end
42
+ ```
43
+
44
+ > **Migrating from `json_schemer`?** See the [migration guide](MIGRATION.md).
45
+
46
+ ## Highlights
47
+
48
+ - 📚 Full support for popular JSON Schema drafts
49
+ - 🌐 Remote reference fetching (network/file)
50
+ - 🔧 Custom keywords and format validators
51
+ - ✨ Meta-schema validation for schema documents
52
+ - ♦️ Supports Ruby 3.2, 3.4 and 4.0
53
+
54
+ ### Supported drafts
55
+
56
+ The following drafts are supported:
57
+
58
+ - [![Draft 2020-12](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Frust-jsonschema%2Fcompliance%2Fdraft2020-12.json)](https://bowtie.report/#/implementations/rust-jsonschema)
59
+ - [![Draft 2019-09](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Frust-jsonschema%2Fcompliance%2Fdraft2019-09.json)](https://bowtie.report/#/implementations/rust-jsonschema)
60
+ - [![Draft 7](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Frust-jsonschema%2Fcompliance%2Fdraft7.json)](https://bowtie.report/#/implementations/rust-jsonschema)
61
+ - [![Draft 6](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Frust-jsonschema%2Fcompliance%2Fdraft6.json)](https://bowtie.report/#/implementations/rust-jsonschema)
62
+ - [![Draft 4](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Frust-jsonschema%2Fcompliance%2Fdraft4.json)](https://bowtie.report/#/implementations/rust-jsonschema)
63
+
64
+ You can check the current status on the [Bowtie Report](https://bowtie.report/#/implementations/rust-jsonschema).
65
+
66
+ ## Installation
67
+
68
+ Add to your Gemfile:
69
+
70
+ ```
71
+ gem 'jsonschema_rs'
72
+ ```
73
+
74
+ Pre-built native gems are available for:
75
+
76
+ - **Linux**: `x86_64`, `aarch64` (glibc and musl)
77
+ - **macOS**: `x86_64`, `arm64`
78
+ - **Windows**: `x64` (mingw-ucrt)
79
+
80
+ If no pre-built gem is available for your platform, it will be compiled from source during installation. You'll need:
81
+ - Ruby 3.2+
82
+ - Rust toolchain ([rustup](https://rustup.rs/))
83
+
84
+ ## Usage
85
+
86
+ ### Reusable validators
87
+
88
+ For validating multiple instances against the same schema, create a reusable validator.
89
+ `validator_for` automatically detects the draft version from the `$schema` keyword in the schema:
90
+
91
+ ```ruby
92
+ validator = JSONSchema.validator_for({
93
+ "type" => "object",
94
+ "properties" => {
95
+ "name" => { "type" => "string" },
96
+ "age" => { "type" => "integer", "minimum" => 0 }
97
+ },
98
+ "required" => ["name"]
99
+ })
100
+
101
+ validator.valid?({ "name" => "Alice", "age" => 30 }) # => true
102
+ validator.valid?({ "age" => 30 }) # => false
103
+ ```
104
+
105
+ You can use draft-specific validators for different JSON Schema versions:
106
+
107
+ ```ruby
108
+ validator = JSONSchema::Draft7Validator.new(schema)
109
+
110
+ # Available: Draft4Validator, Draft6Validator, Draft7Validator,
111
+ # Draft201909Validator, Draft202012Validator
112
+ ```
113
+
114
+ ### Custom format validators
115
+
116
+ ```ruby
117
+ phone_format = ->(value) { value.match?(/^\+?[1-9]\d{1,14}$/) }
118
+
119
+ validator = JSONSchema.validator_for(
120
+ { "type" => "string", "format" => "phone" },
121
+ validate_formats: true,
122
+ formats: { "phone" => phone_format }
123
+ )
124
+ ```
125
+
126
+ ### Custom keyword validators
127
+
128
+ ```ruby
129
+ class EvenValidator
130
+ def initialize(parent_schema, value, schema_path)
131
+ @enabled = value
132
+ end
133
+
134
+ def validate(instance)
135
+ return unless @enabled && instance.is_a?(Integer)
136
+ raise "#{instance} is not even" if instance.odd?
137
+ end
138
+ end
139
+
140
+ validator = JSONSchema.validator_for(
141
+ { "type" => "integer", "even" => true },
142
+ keywords: { "even" => EvenValidator }
143
+ )
144
+ ```
145
+
146
+ Each custom keyword class must implement:
147
+ - `initialize(parent_schema, value, schema_path)` - called during schema compilation
148
+ - `validate(instance)` - raise on failure, return normally on success
149
+
150
+ ### Structured evaluation output
151
+
152
+ When you need more than a boolean result, use the `evaluate` API to access the JSON Schema Output v1 formats:
153
+
154
+ ```ruby
155
+ schema = {
156
+ "type" => "object",
157
+ "properties" => {
158
+ "name" => { "type" => "string" },
159
+ "age" => { "type" => "integer" }
160
+ },
161
+ "required" => ["name"]
162
+ }
163
+ validator = JSONSchema.validator_for(schema)
164
+
165
+ evaluation = validator.evaluate({ "age" => "not_an_integer" })
166
+
167
+ evaluation.valid? # => false
168
+
169
+ # Flag output (simplest)
170
+ evaluation.flag
171
+ # => { "valid" => false }
172
+
173
+ # List output (flat)
174
+ evaluation.list
175
+ # => { "valid" => false, "details" => [
176
+ # { "valid" => false, "evaluationPath" => "", "instanceLocation" => "", "schemaLocation" => "" },
177
+ # { "valid" => false, "evaluationPath" => "/required", "instanceLocation" => "", "schemaLocation" => "/required",
178
+ # "errors" => { "required" => "\"name\" is a required property" } },
179
+ # ...
180
+ # ] }
181
+
182
+ # Hierarchical output (nested tree following schema structure)
183
+ evaluation.hierarchical
184
+ # => { "valid" => false, "evaluationPath" => "", "instanceLocation" => "", "schemaLocation" => "",
185
+ # "details" => [ ... ] }
186
+
187
+ # Collected errors across all nodes
188
+ evaluation.errors
189
+ # => [{ "schemaLocation" => "/required", "instanceLocation" => "",
190
+ # "absoluteKeywordLocation" => nil, "error" => "\"name\" is a required property" }, ...]
191
+
192
+ # Collected annotations
193
+ evaluation.annotations
194
+ # => [{ "schemaLocation" => "/properties", "instanceLocation" => "",
195
+ # "absoluteKeywordLocation" => nil, "annotations" => ["age"] }]
196
+ ```
197
+
198
+ ## Meta-Schema Validation
199
+
200
+ Validate that a JSON Schema document is itself valid:
201
+
202
+ ```ruby
203
+ JSONSchema::Meta.valid?({ "type" => "string" }) # => true
204
+ JSONSchema::Meta.valid?({ "type" => "invalid_type" }) # => false
205
+
206
+ begin
207
+ JSONSchema::Meta.validate!({ "type" => 123 })
208
+ rescue JSONSchema::ValidationError => e
209
+ e.message # => "123 is not valid under any of the schemas listed in the 'anyOf' keyword"
210
+ end
211
+ ```
212
+
213
+ ## External References
214
+
215
+ By default, `jsonschema` resolves HTTP references and file references from the local file system. You can implement a custom retriever to handle external references:
216
+
217
+ ```ruby
218
+ schemas = {
219
+ "https://example.com/person.json" => {
220
+ "type" => "object",
221
+ "properties" => {
222
+ "name" => { "type" => "string" },
223
+ "age" => { "type" => "integer" }
224
+ },
225
+ "required" => ["name", "age"]
226
+ }
227
+ }
228
+
229
+ retriever = ->(uri) { schemas[uri] }
230
+
231
+ schema = { "$ref" => "https://example.com/person.json" }
232
+ validator = JSONSchema.validator_for(schema, retriever: retriever)
233
+
234
+ validator.valid?({ "name" => "Alice", "age" => 30 }) # => true
235
+ validator.valid?({ "name" => "Bob" }) # => false (missing "age")
236
+ ```
237
+
238
+ ## Schema Registry
239
+
240
+ For applications that frequently use the same schemas, create a registry to store and reference them:
241
+
242
+ ```ruby
243
+ registry = JSONSchema::Registry.new([
244
+ ["https://example.com/address.json", {
245
+ "type" => "object",
246
+ "properties" => {
247
+ "street" => { "type" => "string" },
248
+ "city" => { "type" => "string" }
249
+ }
250
+ }],
251
+ ["https://example.com/person.json", {
252
+ "type" => "object",
253
+ "properties" => {
254
+ "name" => { "type" => "string" },
255
+ "address" => { "$ref" => "https://example.com/address.json" }
256
+ }
257
+ }]
258
+ ])
259
+
260
+ validator = JSONSchema.validator_for(
261
+ { "$ref" => "https://example.com/person.json" },
262
+ registry: registry
263
+ )
264
+
265
+ validator.valid?({
266
+ "name" => "John",
267
+ "address" => { "street" => "Main St", "city" => "Boston" }
268
+ }) # => true
269
+ ```
270
+
271
+ The registry also accepts `draft:` and `retriever:` options:
272
+
273
+ ```ruby
274
+ registry = JSONSchema::Registry.new(
275
+ [["https://example.com/person.json", schemas["https://example.com/person.json"]]],
276
+ draft: :draft7,
277
+ retriever: retriever
278
+ )
279
+ ```
280
+
281
+ ## Regular Expression Configuration
282
+
283
+ When validating schemas with regex patterns (in `pattern` or `patternProperties`), you can configure the underlying regex engine:
284
+
285
+ ```ruby
286
+ # Default fancy-regex engine with backtracking limits
287
+ # (supports lookaround and backreferences but needs protection against DoS)
288
+ validator = JSONSchema.validator_for(
289
+ { "type" => "string", "pattern" => "^(a+)+$" },
290
+ pattern_options: JSONSchema::FancyRegexOptions.new(backtrack_limit: 10_000)
291
+ )
292
+
293
+ # Standard regex engine for guaranteed linear-time matching
294
+ # (prevents regex DoS attacks but supports fewer features)
295
+ validator = JSONSchema.validator_for(
296
+ { "type" => "string", "pattern" => "^a+$" },
297
+ pattern_options: JSONSchema::RegexOptions.new
298
+ )
299
+
300
+ # Both engines support memory usage configuration
301
+ validator = JSONSchema.validator_for(
302
+ { "type" => "string", "pattern" => "^a+$" },
303
+ pattern_options: JSONSchema::RegexOptions.new(
304
+ size_limit: 1024 * 1024, # Maximum compiled pattern size
305
+ dfa_size_limit: 10240 # Maximum DFA cache size
306
+ )
307
+ )
308
+ ```
309
+
310
+ The available options:
311
+
312
+ - `FancyRegexOptions`: Default engine with lookaround and backreferences support
313
+
314
+ - `backtrack_limit`: Maximum backtracking steps
315
+ - `size_limit`: Maximum compiled regex size in bytes
316
+ - `dfa_size_limit`: Maximum DFA cache size in bytes
317
+
318
+ - `RegexOptions`: Safer engine with linear-time guarantee
319
+
320
+ - `size_limit`: Maximum compiled regex size in bytes
321
+ - `dfa_size_limit`: Maximum DFA cache size in bytes
322
+
323
+ This configuration is crucial when working with untrusted schemas where attackers might craft malicious regex patterns.
324
+
325
+ ## Email Format Configuration
326
+
327
+ When validating email addresses using `{"format": "email"}`, you can customize the validation behavior:
328
+
329
+ ```ruby
330
+ # Require a top-level domain (reject "user@localhost")
331
+ validator = JSONSchema.validator_for(
332
+ { "format" => "email", "type" => "string" },
333
+ validate_formats: true,
334
+ email_options: JSONSchema::EmailOptions.new(require_tld: true)
335
+ )
336
+ validator.valid?("user@localhost") # => false
337
+ validator.valid?("user@example.com") # => true
338
+
339
+ # Disallow IP address literals and display names
340
+ validator = JSONSchema.validator_for(
341
+ { "format" => "email", "type" => "string" },
342
+ validate_formats: true,
343
+ email_options: JSONSchema::EmailOptions.new(
344
+ allow_domain_literal: false, # Reject "user@[127.0.0.1]"
345
+ allow_display_text: false # Reject "Name <user@example.com>"
346
+ )
347
+ )
348
+
349
+ # Require minimum domain segments
350
+ validator = JSONSchema.validator_for(
351
+ { "format" => "email", "type" => "string" },
352
+ validate_formats: true,
353
+ email_options: JSONSchema::EmailOptions.new(minimum_sub_domains: 3) # e.g., user@sub.example.com
354
+ )
355
+ ```
356
+
357
+ Available options:
358
+
359
+ - `require_tld`: Require a top-level domain (e.g., reject "user@localhost")
360
+ - `allow_domain_literal`: Allow IP address literals like "user@[127.0.0.1]" (default: true)
361
+ - `allow_display_text`: Allow display names like "Name <user@example.com>" (default: true)
362
+ - `minimum_sub_domains`: Minimum number of domain segments required
363
+
364
+ ## Error Handling
365
+
366
+ `jsonschema` provides detailed validation errors through the `ValidationError` class:
367
+
368
+ ```ruby
369
+ schema = { "type" => "string", "maxLength" => 5 }
370
+
371
+ begin
372
+ JSONSchema.validate!(schema, "too long")
373
+ rescue JSONSchema::ValidationError => error
374
+ # Basic error information
375
+ error.message # => '"too long" is longer than 5 characters'
376
+ error.verbose_message # => Full context with schema path and instance
377
+ error.instance_path # => Location in the instance that failed
378
+ error.schema_path # => Location in the schema that failed
379
+
380
+ # Detailed error information via `kind`
381
+ error.kind.name # => "maxLength"
382
+ error.kind.value # => { "limit" => 5 }
383
+ error.kind.to_h # => { "name" => "maxLength", "value" => { "limit" => 5 } }
384
+ end
385
+ ```
386
+
387
+ ### Error Kind Properties
388
+
389
+ Each error has a `kind` property with convenient accessors:
390
+
391
+ ```ruby
392
+ JSONSchema.each_error({ "minimum" => 5 }, 3).each do |error|
393
+ error.kind.name # => "minimum"
394
+ error.kind.value # => { "limit" => 5 }
395
+ error.kind.to_h # => { "name" => "minimum", "value" => { "limit" => 5 } }
396
+ error.kind.to_s # => "minimum"
397
+ end
398
+ ```
399
+
400
+ ### Error Message Masking
401
+
402
+ When working with sensitive data, you can mask instance values in error messages:
403
+
404
+ ```ruby
405
+ schema = {
406
+ "type" => "object",
407
+ "properties" => {
408
+ "password" => { "type" => "string", "minLength" => 8 },
409
+ "api_key" => { "type" => "string", "pattern" => "^[A-Z0-9]{32}$" }
410
+ }
411
+ }
412
+
413
+ validator = JSONSchema.validator_for(schema, mask: "[REDACTED]")
414
+
415
+ begin
416
+ validator.validate!({ "password" => "123", "api_key" => "secret_key_123" })
417
+ rescue JSONSchema::ValidationError => exc
418
+ puts exc.message
419
+ # => '[REDACTED] does not match "^[A-Z0-9]{32}$"'
420
+ puts exc.verbose_message
421
+ # => '[REDACTED] does not match "^[A-Z0-9]{32}$"\n\nFailed validating...\nOn instance["api_key"]:\n [REDACTED]'
422
+ end
423
+ ```
424
+
425
+ ### Exception Classes
426
+
427
+ - **`JSONSchema::ValidationError`** - raised on validation failure
428
+ - `message`, `verbose_message`, `instance_path`, `schema_path`, `evaluation_path`, `kind`, `instance`
429
+ - JSON Pointer helpers: `instance_path_pointer`, `schema_path_pointer`, `evaluation_path_pointer`
430
+ - **`JSONSchema::ReferencingError`** - raised when `$ref` cannot be resolved
431
+
432
+ ## Options Reference
433
+
434
+ One-off validation methods (`valid?`, `validate!`, `each_error`, `evaluate`) accept these keyword arguments:
435
+
436
+ ```ruby
437
+ JSONSchema.valid?(schema, instance,
438
+ draft: :draft7, # Specific draft version (symbol)
439
+ validate_formats: true, # Enable format validation (default: false)
440
+ ignore_unknown_formats: true, # Don't error on unknown formats (default: true)
441
+ base_uri: "https://example.com", # Base URI for reference resolution
442
+ mask: "[REDACTED]", # Mask sensitive data in error messages
443
+ retriever: ->(uri) { ... }, # Custom schema retriever for $ref
444
+ formats: { "name" => proc }, # Custom format validators
445
+ keywords: { "name" => Klass }, # Custom keyword validators
446
+ registry: registry, # Pre-registered schemas
447
+ pattern_options: opts, # RegexOptions or FancyRegexOptions
448
+ email_options: opts, # EmailOptions
449
+ http_options: opts # HttpOptions
450
+ )
451
+ ```
452
+
453
+ `evaluate` accepts the same options except `mask` (currently unsupported for evaluation output).
454
+
455
+ `validator_for` accepts the same options except `draft:` — use draft-specific validators (`Draft7Validator.new`, etc.) to pin a draft version.
456
+
457
+ Valid draft symbols: `:draft4`, `:draft6`, `:draft7`, `:draft201909`, `:draft202012`.
458
+
459
+ ## Performance
460
+
461
+ `jsonschema` is designed for high performance, outperforming other Ruby JSON Schema validators in most scenarios:
462
+
463
+ - **26-141x** faster than `json_schemer` for complex schemas and large instances
464
+ - **174-535x** faster than `json-schema` where supported
465
+ - **7-125x** faster than `rj_schema` (RapidJSON/C++)
466
+
467
+ For detailed benchmarks, see our [full performance comparison](https://github.com/Stranger6667/jsonschema/blob/master/crates/jsonschema-rb/BENCHMARKS.md).
468
+
469
+ **Tips:** Reuse validators. Use `valid?` for boolean checks (short-circuits on first error).
470
+
471
+ ## Acknowledgements
472
+
473
+ This library draws API design inspiration from the Python [`jsonschema`](https://github.com/python-jsonschema/jsonschema) package. We're grateful to the Python `jsonschema` maintainers and contributors for their pioneering work in JSON Schema validation.
474
+
475
+ ## Support
476
+
477
+ If you have questions, need help, or want to suggest improvements, please use [GitHub Discussions](https://github.com/Stranger6667/jsonschema/discussions).
478
+
479
+ ## Sponsorship
480
+
481
+ If you find `jsonschema` useful, please consider [sponsoring its development](https://github.com/sponsors/Stranger6667).
482
+
483
+ ## Contributing
484
+
485
+ See [CONTRIBUTING.md](https://github.com/Stranger6667/jsonschema/blob/master/CONTRIBUTING.md) for details.
486
+
487
+ ## License
488
+
489
+ Licensed under [MIT License](https://github.com/Stranger6667/jsonschema/blob/master/LICENSE).
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "mkmf"
4
+ require "rb_sys/mkmf"
5
+
6
+ create_rust_makefile("jsonschema/jsonschema_rb") do |r|
7
+ r.auto_install_rust_toolchain = false
8
+
9
+ musl_target =
10
+ ENV["CARGO_BUILD_TARGET"]&.include?("musl") ||
11
+ File.exist?("/etc/alpine-release") ||
12
+ begin
13
+ `ldd --version 2>&1` =~ /musl/
14
+ rescue StandardError
15
+ false
16
+ end ||
17
+ `rustc -vV 2>/dev/null`[/host: (.+)/, 1]&.include?("musl")
18
+
19
+ if musl_target
20
+ # Disable static CRT on musl.
21
+ r.extra_rustflags = ["-C", "target-feature=-crt-static"]
22
+ end
23
+ end
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONSchema
4
+ VERSION = "0.42.0"
5
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "jsonschema/version"
4
+
5
+ begin
6
+ RUBY_VERSION =~ /(\d+\.\d+)/
7
+ require "jsonschema/#{Regexp.last_match(1)}/jsonschema_rb"
8
+ rescue LoadError
9
+ require "jsonschema/jsonschema_rb"
10
+ end
@@ -0,0 +1,385 @@
1
+ # Type definitions for jsonschema gem
2
+ # High-performance JSON Schema validation for Ruby
3
+
4
+ module JSONSchema
5
+ VERSION: String
6
+
7
+ # Valid draft version symbols
8
+ type draft = :draft4 | :draft6 | :draft7 | :draft201909 | :draft202012
9
+
10
+ # Create a validator with auto-detected draft version.
11
+ #
12
+ # @param schema The JSON Schema (Hash or JSON string)
13
+ # @param validate_formats Enable format validation
14
+ # @param ignore_unknown_formats Ignore unknown formats
15
+ # @param base_uri Base URI for reference resolution
16
+ # @param retriever Custom schema retriever proc
17
+ # @param mask Mask sensitive data in error messages
18
+ # @param formats Custom format validators (Hash of format_name => proc)
19
+ # @param keywords Custom keyword validators (Hash of keyword_name => class)
20
+ def self.validator_for: (
21
+ untyped schema,
22
+ ?validate_formats: bool?,
23
+ ?ignore_unknown_formats: bool?,
24
+ ?base_uri: String?,
25
+ ?retriever: (^(String) -> untyped)?,
26
+ ?mask: String?,
27
+ ?formats: Hash[String, ^(String) -> bool]?,
28
+ ?keywords: Hash[String, Class]?,
29
+ ?registry: Registry?,
30
+ ?pattern_options: (RegexOptions | FancyRegexOptions)?,
31
+ ?email_options: EmailOptions?,
32
+ ?http_options: HttpOptions?
33
+ ) -> Validator
34
+
35
+ # One-off validation returning boolean.
36
+ def self.valid?: (
37
+ untyped schema,
38
+ untyped instance,
39
+ ?draft: draft?,
40
+ ?validate_formats: bool?,
41
+ ?ignore_unknown_formats: bool?,
42
+ ?base_uri: String?,
43
+ ?retriever: (^(String) -> untyped)?,
44
+ ?mask: String?,
45
+ ?formats: Hash[String, ^(String) -> bool]?,
46
+ ?keywords: Hash[String, Class]?,
47
+ ?registry: Registry?,
48
+ ?pattern_options: (RegexOptions | FancyRegexOptions)?,
49
+ ?email_options: EmailOptions?,
50
+ ?http_options: HttpOptions?
51
+ ) -> bool
52
+
53
+ # One-off validation raising on error.
54
+ def self.validate!: (
55
+ untyped schema,
56
+ untyped instance,
57
+ ?draft: draft?,
58
+ ?validate_formats: bool?,
59
+ ?ignore_unknown_formats: bool?,
60
+ ?base_uri: String?,
61
+ ?retriever: (^(String) -> untyped)?,
62
+ ?mask: String?,
63
+ ?formats: Hash[String, ^(String) -> bool]?,
64
+ ?keywords: Hash[String, Class]?,
65
+ ?registry: Registry?,
66
+ ?pattern_options: (RegexOptions | FancyRegexOptions)?,
67
+ ?email_options: EmailOptions?,
68
+ ?http_options: HttpOptions?
69
+ ) -> nil
70
+
71
+ # Iterate validation errors. Returns Array when no block given, nil when block given.
72
+ def self.each_error: (
73
+ untyped schema,
74
+ untyped instance,
75
+ ?draft: draft?,
76
+ ?validate_formats: bool?,
77
+ ?ignore_unknown_formats: bool?,
78
+ ?base_uri: String?,
79
+ ?retriever: (^(String) -> untyped)?,
80
+ ?mask: String?,
81
+ ?formats: Hash[String, ^(String) -> bool]?,
82
+ ?keywords: Hash[String, Class]?,
83
+ ?registry: Registry?,
84
+ ?pattern_options: (RegexOptions | FancyRegexOptions)?,
85
+ ?email_options: EmailOptions?,
86
+ ?http_options: HttpOptions?
87
+ ) -> Array[ValidationError]
88
+ | (
89
+ untyped schema,
90
+ untyped instance,
91
+ ?draft: draft?,
92
+ ?validate_formats: bool?,
93
+ ?ignore_unknown_formats: bool?,
94
+ ?base_uri: String?,
95
+ ?retriever: (^(String) -> untyped)?,
96
+ ?mask: String?,
97
+ ?formats: Hash[String, ^(String) -> bool]?,
98
+ ?keywords: Hash[String, Class]?,
99
+ ?registry: Registry?,
100
+ ?pattern_options: (RegexOptions | FancyRegexOptions)?,
101
+ ?email_options: EmailOptions?,
102
+ ?http_options: HttpOptions?
103
+ ) { (ValidationError) -> void } -> nil
104
+
105
+ # One-off evaluation returning structured output.
106
+ def self.evaluate: (
107
+ untyped schema,
108
+ untyped instance,
109
+ ?draft: draft?,
110
+ ?validate_formats: bool?,
111
+ ?ignore_unknown_formats: bool?,
112
+ ?base_uri: String?,
113
+ ?retriever: (^(String) -> untyped)?,
114
+ ?formats: Hash[String, ^(String) -> bool]?,
115
+ ?keywords: Hash[String, Class]?,
116
+ ?registry: Registry?,
117
+ ?pattern_options: (RegexOptions | FancyRegexOptions)?,
118
+ ?email_options: EmailOptions?,
119
+ ?http_options: HttpOptions?
120
+ ) -> Evaluation
121
+
122
+ # Reusable JSON Schema validator.
123
+ private
124
+ class Validator
125
+ # Fast validation returning boolean.
126
+ def valid?: (untyped instance) -> bool
127
+
128
+ # Validate and raise on first error.
129
+ def validate!: (untyped instance) -> nil
130
+
131
+ # Iterate validation errors. Returns Array when no block given, nil when block given.
132
+ def each_error: (untyped instance) -> Array[ValidationError]
133
+ | (untyped instance) { (ValidationError) -> void } -> nil
134
+
135
+ # Evaluate instance and return structured output.
136
+ def evaluate: (untyped instance) -> Evaluation
137
+
138
+ def inspect: () -> String
139
+ end
140
+ public
141
+
142
+ # JSON Schema Draft 4 validator.
143
+ class Draft4Validator < Validator
144
+ end
145
+
146
+ # JSON Schema Draft 6 validator.
147
+ class Draft6Validator < Validator
148
+ end
149
+
150
+ # JSON Schema Draft 7 validator.
151
+ class Draft7Validator < Validator
152
+ end
153
+
154
+ # JSON Schema Draft 2019-09 validator.
155
+ class Draft201909Validator < Validator
156
+ end
157
+
158
+ # JSON Schema Draft 2020-12 validator.
159
+ class Draft202012Validator < Validator
160
+ end
161
+
162
+ # Validation error with detailed information.
163
+ class ValidationError < StandardError
164
+ # Brief error message.
165
+ attr_reader message: String
166
+
167
+ # Detailed error message with full context.
168
+ attr_reader verbose_message: String
169
+
170
+ # Path to the failing instance location.
171
+ attr_reader instance_path: Array[String | Integer]
172
+
173
+ # Path to the failing schema location.
174
+ attr_reader schema_path: Array[String | Integer]
175
+
176
+ # Evaluation path.
177
+ attr_reader evaluation_path: Array[String | Integer]
178
+
179
+ # The specific kind of validation error.
180
+ attr_reader kind: ValidationErrorKind
181
+
182
+ # The failing instance value.
183
+ attr_reader instance: untyped
184
+
185
+ def initialize: (
186
+ message: String,
187
+ ?verbose_message: String?,
188
+ ?instance_path: Array[String | Integer],
189
+ ?schema_path: Array[String | Integer],
190
+ ?evaluation_path: Array[String | Integer],
191
+ ?kind: ValidationErrorKind?,
192
+ ?instance: untyped
193
+ ) -> void
194
+
195
+ def to_s: () -> String
196
+ def inspect: () -> String
197
+
198
+ # Compare two validation errors by message, schema_path, and instance_path.
199
+ def ==: (untyped other) -> bool
200
+ alias eql? ==
201
+
202
+ # Hash code based on message, schema_path, and instance_path.
203
+ def hash: () -> Integer
204
+
205
+ # Convert instance_path to JSON Pointer format (RFC 6901).
206
+ def instance_path_pointer: () -> String
207
+
208
+ # Convert schema_path to JSON Pointer format (RFC 6901).
209
+ def schema_path_pointer: () -> String
210
+
211
+ # Evaluation path as JSON Pointer string (RFC 6901), pre-computed in Rust.
212
+ def evaluation_path_pointer: () -> String
213
+ end
214
+
215
+ # Type of validation error with contextual data.
216
+ class ValidationErrorKind
217
+ # Keyword name that caused the error (e.g., "type", "required", "format").
218
+ def name: () -> String
219
+
220
+ # Associated value for the error (varies by error type).
221
+ def value: () -> untyped
222
+
223
+ # Convert to hash representation.
224
+ def to_h: () -> Hash[Symbol, untyped]
225
+
226
+ def inspect: () -> String
227
+ def to_s: () -> String
228
+ end
229
+
230
+ # Structured evaluation output following JSON Schema specification.
231
+ class Evaluation
232
+ # Whether the instance is valid.
233
+ def valid?: () -> bool
234
+
235
+ # Flag output format (simplest).
236
+ # Returns { valid: true/false }
237
+ def flag: () -> Hash[Symbol, untyped]
238
+
239
+ # List output format (flat).
240
+ # Contains { valid: bool, details: [...] }
241
+ def list: () -> Hash[Symbol, untyped]
242
+
243
+ # Hierarchical output format (nested).
244
+ # Contains nested structure following schema hierarchy
245
+ def hierarchical: () -> Hash[Symbol, untyped]
246
+
247
+ # Collected annotations from all evaluated nodes.
248
+ # Each entry contains schemaLocation, instanceLocation, and annotations
249
+ def annotations: () -> Array[Hash[Symbol, untyped]]
250
+
251
+ # Collected errors from all evaluated nodes.
252
+ # Each entry contains schemaLocation, instanceLocation, error, and absoluteKeywordLocation
253
+ def errors: () -> Array[Hash[Symbol, untyped]]
254
+
255
+ def inspect: () -> String
256
+ end
257
+
258
+ # Schema registry for reference resolution.
259
+ class Registry
260
+ # Create a registry from URI/schema pairs.
261
+ # @param resources Array of [uri, schema] pairs
262
+ # @param draft Optional draft version constant
263
+ # @param retriever Custom schema retriever proc for dynamic resolution
264
+ def initialize: (Array[[String, untyped]] resources, ?draft: draft?, ?retriever: (^(String) -> untyped)?) -> void
265
+
266
+ def inspect: () -> String
267
+ end
268
+
269
+ # Raised when a reference cannot be resolved.
270
+ class ReferencingError < StandardError
271
+ attr_reader message: String
272
+ def initialize: (String message) -> void
273
+ end
274
+
275
+ # Meta-schema validation.
276
+ module Meta
277
+ # Validate a schema against its meta-schema.
278
+ def self.valid?: (untyped schema, ?registry: Registry?) -> bool
279
+
280
+ # Validate a schema and raise on error.
281
+ def self.validate!: (untyped schema, ?registry: Registry?) -> nil
282
+ end
283
+
284
+ # Email format validation options.
285
+ class EmailOptions
286
+ # Create email validation options.
287
+ # @param require_tld Require top-level domain (default: false)
288
+ # @param allow_domain_literal Allow domain literals like [127.0.0.1] (default: true)
289
+ # @param allow_display_text Allow display names like "Name <email>" (default: true)
290
+ # @param minimum_sub_domains Minimum number of subdomains required (default: nil)
291
+ def initialize: (
292
+ ?require_tld: bool?,
293
+ ?allow_domain_literal: bool?,
294
+ ?allow_display_text: bool?,
295
+ ?minimum_sub_domains: Integer?
296
+ ) -> void
297
+
298
+ # Whether TLD is required.
299
+ def require_tld: () -> bool
300
+
301
+ # Whether domain literals are allowed.
302
+ def allow_domain_literal: () -> bool
303
+
304
+ # Whether display text is allowed.
305
+ def allow_display_text: () -> bool
306
+
307
+ # Minimum number of subdomains.
308
+ def minimum_sub_domains: () -> Integer?
309
+
310
+ def inspect: () -> String
311
+ def to_s: () -> String
312
+ end
313
+
314
+ # Regex engine options.
315
+ class RegexOptions
316
+ # Create regex engine options.
317
+ # @param size_limit Maximum compiled regex size in bytes
318
+ # @param dfa_size_limit Maximum DFA size in bytes
319
+ def initialize: (?size_limit: Integer?, ?dfa_size_limit: Integer?) -> void
320
+
321
+ # Maximum compiled regex size.
322
+ def size_limit: () -> Integer?
323
+
324
+ # Maximum DFA size.
325
+ def dfa_size_limit: () -> Integer?
326
+
327
+ def inspect: () -> String
328
+ def to_s: () -> String
329
+ end
330
+
331
+ # Fancy regex engine options (ECMA-262 patterns).
332
+ class FancyRegexOptions
333
+ # Create fancy regex engine options.
334
+ # @param backtrack_limit Maximum backtracking steps
335
+ # @param size_limit Maximum compiled regex size
336
+ # @param dfa_size_limit Maximum DFA size
337
+ def initialize: (
338
+ ?backtrack_limit: Integer?,
339
+ ?size_limit: Integer?,
340
+ ?dfa_size_limit: Integer?
341
+ ) -> void
342
+
343
+ # Maximum backtracking steps.
344
+ def backtrack_limit: () -> Integer?
345
+
346
+ # Maximum compiled regex size.
347
+ def size_limit: () -> Integer?
348
+
349
+ # Maximum DFA size.
350
+ def dfa_size_limit: () -> Integer?
351
+
352
+ def inspect: () -> String
353
+ def to_s: () -> String
354
+ end
355
+
356
+ # HTTP client options for remote schema retrieval.
357
+ class HttpOptions
358
+ # Create HTTP client options.
359
+ # @param timeout Request timeout in seconds
360
+ # @param connect_timeout Connection timeout in seconds
361
+ # @param tls_verify Verify TLS certificates (default: true)
362
+ # @param ca_cert Path to CA certificate file
363
+ def initialize: (
364
+ ?timeout: Float?,
365
+ ?connect_timeout: Float?,
366
+ ?tls_verify: bool?,
367
+ ?ca_cert: String?
368
+ ) -> void
369
+
370
+ # Request timeout in seconds.
371
+ def timeout: () -> Float?
372
+
373
+ # Connection timeout in seconds.
374
+ def connect_timeout: () -> Float?
375
+
376
+ # Whether to verify TLS certificates.
377
+ def tls_verify: () -> bool
378
+
379
+ # Path to CA certificate file.
380
+ def ca_cert: () -> String?
381
+
382
+ def inspect: () -> String
383
+ def to_s: () -> String
384
+ end
385
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jsonschema_rs
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.42.0
5
+ platform: aarch64-linux
6
+ authors:
7
+ - Dmitry Dygalo
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-02-15 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bigdecimal
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3.1'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '5'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '3.1'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '5'
33
+ description: High-performance JSON Schema validator with support for Draft 4, 6, 7,
34
+ 2019-09, and 2020-12.
35
+ email:
36
+ - dmitry@dygalo.dev
37
+ executables: []
38
+ extensions: []
39
+ extra_rdoc_files: []
40
+ files:
41
+ - CHANGELOG.md
42
+ - LICENSE
43
+ - MIGRATION.md
44
+ - README.md
45
+ - ext/jsonschema/extconf.rb
46
+ - lib/jsonschema/3.2/jsonschema_rb.so
47
+ - lib/jsonschema/3.3/jsonschema_rb.so
48
+ - lib/jsonschema/3.4/jsonschema_rb.so
49
+ - lib/jsonschema/4.0/jsonschema_rb.so
50
+ - lib/jsonschema/version.rb
51
+ - lib/jsonschema_rs.rb
52
+ - sig/jsonschema.rbs
53
+ homepage: https://github.com/Stranger6667/jsonschema/tree/master/crates/jsonschema-rb
54
+ licenses:
55
+ - MIT
56
+ metadata:
57
+ homepage_uri: https://github.com/Stranger6667/jsonschema/tree/master/crates/jsonschema-rb
58
+ source_code_uri: https://github.com/Stranger6667/jsonschema
59
+ changelog_uri: https://github.com/Stranger6667/jsonschema/blob/master/crates/jsonschema-rb/CHANGELOG.md
60
+ documentation_uri: https://github.com/Stranger6667/jsonschema/tree/master/crates/jsonschema-rb#readme
61
+ bug_tracker_uri: https://github.com/Stranger6667/jsonschema/issues
62
+ funding_uri: https://github.com/sponsors/Stranger6667
63
+ rubygems_mfa_required: 'true'
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '3.2'
73
+ - - "<"
74
+ - !ruby/object:Gem::Version
75
+ version: 4.1.dev
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: 3.3.11
81
+ requirements: []
82
+ rubygems_version: 3.5.23
83
+ signing_key:
84
+ specification_version: 4
85
+ summary: A high-performance JSON Schema validator for Ruby
86
+ test_files: []