jsonschema_rs 0.42.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 +7 -0
- data/CHANGELOG.md +8 -0
- data/Cargo.toml +24 -0
- data/LICENSE +21 -0
- data/MIGRATION.md +87 -0
- data/README.md +489 -0
- data/ext/jsonschema/Cargo.lock +2063 -0
- data/ext/jsonschema/Cargo.toml +20 -0
- data/ext/jsonschema/extconf.rb +23 -0
- data/lib/jsonschema/version.rb +5 -0
- data/lib/jsonschema_rs.rb +10 -0
- data/sig/jsonschema.rbs +385 -0
- data/src/error_kind.rs +302 -0
- data/src/evaluation.rs +116 -0
- data/src/lib.rs +1349 -0
- data/src/options.rs +1054 -0
- data/src/registry.rs +138 -0
- data/src/retriever.rs +100 -0
- data/src/ser.rs +724 -0
- data/src/static_id.rs +93 -0
- metadata +102 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 612136d54e48cbe70a384939de4ee760c240ebf37ffbb40924d8eb1bccf4e382
|
|
4
|
+
data.tar.gz: a3b9968753b319ef31129311c9598be508dc331eca081386eb8de8f645a40cf1
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 93e07f2476489fcef0315b06b187895449435b072a269ce9cf29f302a481ba5a6e5ac4cf6bd75f79002f3aa5409d28283e76e6739bb7ce7578db7b2dd002e1dd
|
|
7
|
+
data.tar.gz: 032c5b841a42b9e3ee5e4a3a4c03826074cabfcc4d24d3b9d71c3e85eed7b0c417c352e7b95f5d0e6a9ac87c888df0520961f45298095c17df26117f0b6cbd5c
|
data/CHANGELOG.md
ADDED
data/Cargo.toml
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "jsonschema-rb"
|
|
3
|
+
version = "0.42.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
authors = ["Dmitry Dygalo <dmitry@dygalo.dev>"]
|
|
6
|
+
license = "MIT"
|
|
7
|
+
description = "A high-performance JSON Schema validator for Ruby"
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
repository = "https://github.com/Stranger6667/jsonschema"
|
|
10
|
+
publish = false
|
|
11
|
+
|
|
12
|
+
[lib]
|
|
13
|
+
crate-type = ["cdylib"]
|
|
14
|
+
|
|
15
|
+
[dependencies]
|
|
16
|
+
jsonschema = { version = "0.42.0", default-features = false, features = ["arbitrary-precision", "resolve-http", "resolve-file", "tls-ring"] }
|
|
17
|
+
magnus = { version = "0.8", features = ["rb-sys"] }
|
|
18
|
+
rb-sys = "0.9"
|
|
19
|
+
serde = { workspace = true }
|
|
20
|
+
serde_json = { workspace = true, features = ["arbitrary_precision"] }
|
|
21
|
+
referencing = { version = "*", path = "../jsonschema-referencing" }
|
|
22
|
+
|
|
23
|
+
[lints]
|
|
24
|
+
workspace = true
|
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
|
+
[](https://github.com/Stranger6667/jsonschema/actions)
|
|
4
|
+
[](https://rubygems.org/gems/jsonschema_rs)
|
|
5
|
+
[](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
|
+
- [](https://bowtie.report/#/implementations/rust-jsonschema)
|
|
59
|
+
- [](https://bowtie.report/#/implementations/rust-jsonschema)
|
|
60
|
+
- [](https://bowtie.report/#/implementations/rust-jsonschema)
|
|
61
|
+
- [](https://bowtie.report/#/implementations/rust-jsonschema)
|
|
62
|
+
- [](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).
|