json_schemer 1.0.3 → 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +2 -7
- data/CHANGELOG.md +51 -0
- data/Gemfile.lock +10 -3
- data/README.md +328 -14
- data/json_schemer.gemspec +3 -1
- data/lib/json_schemer/content.rb +18 -0
- data/lib/json_schemer/draft201909/meta.rb +320 -0
- data/lib/json_schemer/draft201909/vocab/applicator.rb +104 -0
- data/lib/json_schemer/draft201909/vocab/core.rb +45 -0
- data/lib/json_schemer/draft201909/vocab.rb +31 -0
- data/lib/json_schemer/draft202012/meta.rb +364 -0
- data/lib/json_schemer/draft202012/vocab/applicator.rb +382 -0
- data/lib/json_schemer/draft202012/vocab/content.rb +52 -0
- data/lib/json_schemer/draft202012/vocab/core.rb +160 -0
- data/lib/json_schemer/draft202012/vocab/format_annotation.rb +23 -0
- data/lib/json_schemer/draft202012/vocab/format_assertion.rb +23 -0
- data/lib/json_schemer/draft202012/vocab/meta_data.rb +30 -0
- data/lib/json_schemer/draft202012/vocab/unevaluated.rb +94 -0
- data/lib/json_schemer/draft202012/vocab/validation.rb +286 -0
- data/lib/json_schemer/draft202012/vocab.rb +105 -0
- data/lib/json_schemer/draft4/meta.rb +161 -0
- data/lib/json_schemer/draft4/vocab/validation.rb +39 -0
- data/lib/json_schemer/draft4/vocab.rb +18 -0
- data/lib/json_schemer/draft6/meta.rb +172 -0
- data/lib/json_schemer/draft6/vocab.rb +16 -0
- data/lib/json_schemer/draft7/meta.rb +183 -0
- data/lib/json_schemer/draft7/vocab/validation.rb +69 -0
- data/lib/json_schemer/draft7/vocab.rb +30 -0
- data/lib/json_schemer/errors.rb +1 -0
- data/lib/json_schemer/format/duration.rb +23 -0
- data/lib/json_schemer/format/json_pointer.rb +18 -0
- data/lib/json_schemer/format.rb +128 -106
- data/lib/json_schemer/keyword.rb +53 -0
- data/lib/json_schemer/location.rb +25 -0
- data/lib/json_schemer/openapi.rb +40 -0
- data/lib/json_schemer/openapi30/document.rb +1672 -0
- data/lib/json_schemer/openapi30/meta.rb +32 -0
- data/lib/json_schemer/openapi30/vocab/base.rb +18 -0
- data/lib/json_schemer/openapi30/vocab.rb +12 -0
- data/lib/json_schemer/openapi31/document.rb +1557 -0
- data/lib/json_schemer/openapi31/meta.rb +136 -0
- data/lib/json_schemer/openapi31/vocab/base.rb +127 -0
- data/lib/json_schemer/openapi31/vocab.rb +18 -0
- data/lib/json_schemer/output.rb +56 -0
- data/lib/json_schemer/result.rb +229 -0
- data/lib/json_schemer/schema.rb +423 -0
- data/lib/json_schemer/version.rb +1 -1
- data/lib/json_schemer.rb +198 -24
- metadata +71 -10
- data/lib/json_schemer/schema/base.rb +0 -677
- data/lib/json_schemer/schema/draft4.json +0 -149
- data/lib/json_schemer/schema/draft4.rb +0 -44
- data/lib/json_schemer/schema/draft6.json +0 -155
- data/lib/json_schemer/schema/draft6.rb +0 -25
- data/lib/json_schemer/schema/draft7.json +0 -172
- data/lib/json_schemer/schema/draft7.rb +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1981782f2342c7123beb0374680037bc11f3be72a8e4be0a213cfb7691643415
|
4
|
+
data.tar.gz: e8b6c605ba1ce2a2751ac90167f53c27fad975140ab9a0e8328c2b04ce8fe732
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4d4c6e6c3660560a602b5610559efe37b5839f1142af6486e350bd207cad49e37a00f397d555878957b14951da95745092240bd14417e2ffd02826f053099952
|
7
|
+
data.tar.gz: 8dc561414463ceeacf7bbd0be95be9c441a50919cdb839ff8366fa0268d96d91e4947985bd7bab406f3c163e913bf7ca923a38e22d777d2549af3329f91c14f2
|
data/.github/workflows/ci.yml
CHANGED
@@ -14,14 +14,9 @@ jobs:
|
|
14
14
|
ruby: truffleruby-head
|
15
15
|
runs-on: ${{ matrix.os }}
|
16
16
|
steps:
|
17
|
-
- uses: actions/checkout@
|
17
|
+
- uses: actions/checkout@v4
|
18
18
|
- uses: ruby/setup-ruby@v1
|
19
19
|
with:
|
20
20
|
ruby-version: ${{ matrix.ruby }}
|
21
21
|
bundler-cache: true
|
22
|
-
- run:
|
23
|
-
mkdir -p tmp/gems
|
24
|
-
gem build json_schemer.gemspec
|
25
|
-
gem install --local --ignore-dependencies --no-document --install-dir tmp/gems json_schemer-*.gem
|
26
|
-
rm json_schemer-*.gem
|
27
|
-
bin/rake test
|
22
|
+
- run: bin/rake test
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,56 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## [2.1.1] - 2023-11-28
|
4
|
+
|
5
|
+
### Bug Fixes
|
6
|
+
|
7
|
+
- Fix refs to/through keyword objects: https://github.com/davishmcclurg/json_schemer/pull/160
|
8
|
+
- Temporary fix for incorrect `uri-reference` format in OpenAPI 3.x: https://github.com/davishmcclurg/json_schemer/pull/161
|
9
|
+
|
10
|
+
[2.1.1]: https://github.com/davishmcclurg/json_schemer/releases/tag/v2.1.1
|
11
|
+
|
12
|
+
## [2.1.0] - 2023-11-17
|
13
|
+
|
14
|
+
### Bug Fixes
|
15
|
+
|
16
|
+
- Limit anyOf/oneOf discriminator to listed refs: https://github.com/davishmcclurg/json_schemer/pull/145
|
17
|
+
- Require discriminator `propertyName` property: https://github.com/davishmcclurg/json_schemer/pull/145
|
18
|
+
- Support `Schema#ref` in subschemas: https://github.com/davishmcclurg/json_schemer/pull/145
|
19
|
+
- Resolve JSON pointer refs using correct base URI: https://github.com/davishmcclurg/json_schemer/pull/147
|
20
|
+
- `date` format in OpenAPI 3.0: https://github.com/davishmcclurg/json_schemer/commit/69fe7a815ecf0cfb1c40ac402bf46a789c05e972
|
21
|
+
|
22
|
+
### Features
|
23
|
+
|
24
|
+
- Custom error messages with `x-error` keyword and I18n: https://github.com/davishmcclurg/json_schemer/pull/149
|
25
|
+
- Custom content encodings and media types: https://github.com/davishmcclurg/json_schemer/pull/148
|
26
|
+
|
27
|
+
[2.1.0]: https://github.com/davishmcclurg/json_schemer/releases/tag/v2.1.0
|
28
|
+
|
29
|
+
## [2.0.0] - 2023-08-20
|
30
|
+
|
31
|
+
For 2.0.0, much of the codebase was rewritten to simplify support for the two new JSON Schema draft versions (2019-09 and 2020-12). The major change is moving each keyword into its own class and organizing them into vocabularies. [Output formats](https://json-schema.org/draft/2020-12/json-schema-core.html#section-12) and [annotations](https://json-schema.org/draft/2020-12/json-schema-core.html#section-7.7) from the new drafts are also supported. The known breaking changes are listed below, but there may be others that haven't been identified.
|
32
|
+
|
33
|
+
### Breaking Changes
|
34
|
+
|
35
|
+
- The default meta schema is now Draft 2020-12. Other meta schemas can be specified using `meta_schema`.
|
36
|
+
- Schemas use `json-schemer://schema` as the default base URI. Relative `$id` and `$ref` values are joined to the default base URI and are always absolute. For example, the schema `{ '$id' => 'foo', '$ref' => 'bar' }` uses `json-schemer://schema/foo` as the base URI and passes `json-schemer://schema/bar` to the ref resolver. For relative refs, `URI#path` can be used in the ref resolver to access the relative portion, ie: `URI('json-schemer://schema/bar').path => "/bar"`.
|
37
|
+
- Property validation hooks (`before_property_validation` and `after_property_validation`) run immediately before and after `properties` validation. Previously, `before_property_validation` ran before all "object" validations (`dependencies`, `patternProperties`, `additionalProperties`, etc) and `after_property_validation` was called after them.
|
38
|
+
- `insert_property_defaults` now inserts defaults in conditional subschemas when possible (if there's only one default or if there's only one unique default from a valid subtree).
|
39
|
+
- Error output
|
40
|
+
- Special characters in `schema_pointer` are no longer percent encoded (eg, `definitions/foo\"bar` instead of `/definitions/foo%22bar`)
|
41
|
+
- Keyword validation order changed so errors may be returned in a different order (eg, `items` errors before `contains`).
|
42
|
+
- Array `dependencies` return `"type": "dependencies"` errors instead of `"required"` and point to the schema that contains the `dependencies` keyword.
|
43
|
+
- `not` errors point to the schema that contains the `not` keyword (instead of the schema defined by the `not` keyword).
|
44
|
+
- Custom keyword errors are now always wrapped in regular error hashes. Returned strings are used to set `type`:
|
45
|
+
```
|
46
|
+
>> JSONSchemer.schema({ 'x' => 'y' }, :keywords => { 'x' => proc { false } }).validate({}).to_a
|
47
|
+
=> [{"data"=>{}, "data_pointer"=>"", "schema"=>{"x"=>"y"}, "schema_pointer"=>"", "root_schema"=>{"x"=>"y"}, "type"=>"x"}]
|
48
|
+
>> JSONSchemer.schema({ 'x' => 'y' }, :keywords => { 'x' => proc { 'wrong!' } }).validate({}).to_a
|
49
|
+
=> [{"data"=>{}, "data_pointer"=>"", "schema"=>{"x"=>"y"}, "schema_pointer"=>"", "root_schema"=>{"x"=>"y"}, "type"=>"wrong!"}]
|
50
|
+
```
|
51
|
+
|
52
|
+
[2.0.0]: https://github.com/davishmcclurg/json_schemer/releases/tag/v2.0.0
|
53
|
+
|
3
54
|
## [1.0.0] - 2023-05-26
|
4
55
|
|
5
56
|
### Breaking Changes
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
json_schemer (1.
|
4
|
+
json_schemer (2.1.1)
|
5
5
|
hana (~> 1.3)
|
6
6
|
regexp_parser (~> 2.0)
|
7
7
|
simpleidn (~> 0.2)
|
@@ -9,11 +9,16 @@ PATH
|
|
9
9
|
GEM
|
10
10
|
remote: https://rubygems.org/
|
11
11
|
specs:
|
12
|
+
concurrent-ruby (1.2.2)
|
12
13
|
docile (1.4.0)
|
13
14
|
hana (1.3.7)
|
15
|
+
i18n (1.14.1)
|
16
|
+
concurrent-ruby (~> 1.0)
|
17
|
+
i18n-debug (1.2.0)
|
18
|
+
i18n (< 2)
|
14
19
|
minitest (5.15.0)
|
15
20
|
rake (13.0.6)
|
16
|
-
regexp_parser (2.8.
|
21
|
+
regexp_parser (2.8.2)
|
17
22
|
simplecov (0.22.0)
|
18
23
|
docile (~> 1.1)
|
19
24
|
simplecov-html (~> 0.11)
|
@@ -25,7 +30,7 @@ GEM
|
|
25
30
|
unf (0.1.4)
|
26
31
|
unf_ext
|
27
32
|
unf (0.1.4-java)
|
28
|
-
unf_ext (0.0.
|
33
|
+
unf_ext (0.0.9.1)
|
29
34
|
|
30
35
|
PLATFORMS
|
31
36
|
java
|
@@ -33,6 +38,8 @@ PLATFORMS
|
|
33
38
|
|
34
39
|
DEPENDENCIES
|
35
40
|
bundler (~> 2.0)
|
41
|
+
i18n
|
42
|
+
i18n-debug
|
36
43
|
json_schemer!
|
37
44
|
minitest (~> 5.0)
|
38
45
|
rake (~> 13.0)
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# JSONSchemer
|
2
2
|
|
3
|
-
JSON Schema validator. Supports drafts 4, 6, and
|
3
|
+
JSON Schema validator. Supports drafts 4, 6, 7, 2019-09, 2020-12, OpenAPI 3.0, and OpenAPI 3.1.
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -50,7 +50,8 @@ schemer.validate({ 'abc' => 10 }).to_a
|
|
50
50
|
# "schema"=>{"type"=>"integer", "minimum"=>11},
|
51
51
|
# "schema_pointer"=>"/properties/abc",
|
52
52
|
# "root_schema"=>{"type"=>"object", "properties"=>{"abc"=>{"type"=>"integer", "minimum"=>11}}},
|
53
|
-
# "type"=>"minimum"
|
53
|
+
# "type"=>"minimum",
|
54
|
+
# "error"=>"number at `/abc` is less than: 11"}]
|
54
55
|
|
55
56
|
# default property values
|
56
57
|
|
@@ -82,27 +83,77 @@ schemer = JSONSchemer.schema(schema)
|
|
82
83
|
|
83
84
|
# schema validation
|
84
85
|
|
85
|
-
JSONSchemer.valid_schema?({ '$id' => '
|
86
|
+
JSONSchemer.valid_schema?({ '$id' => 'valid' })
|
86
87
|
# => true
|
87
88
|
|
88
|
-
JSONSchemer.validate_schema({ '$id' =>
|
89
|
-
# => [{"data"=>
|
89
|
+
JSONSchemer.validate_schema({ '$id' => '#invalid' }).to_a
|
90
|
+
# => [{"data"=>"#invalid",
|
90
91
|
# "data_pointer"=>"/$id",
|
91
|
-
# "schema"=>{"
|
92
|
+
# "schema"=>{"$ref"=>"#/$defs/uriReferenceString", "$comment"=>"Non-empty fragments not allowed.", "pattern"=>"^[^#]*#?$"},
|
92
93
|
# "schema_pointer"=>"/properties/$id",
|
93
94
|
# "root_schema"=>{...meta schema},
|
94
|
-
# "type"=>"
|
95
|
+
# "type"=>"pattern",
|
96
|
+
# "error"=>"string at `/$id` does not match pattern: ^[^#]*#?$"}]
|
95
97
|
|
96
|
-
JSONSchemer.schema({ '$id' => '
|
98
|
+
JSONSchemer.schema({ '$id' => 'valid' }).valid_schema?
|
97
99
|
# => true
|
98
100
|
|
99
|
-
JSONSchemer.schema({ '$id' =>
|
100
|
-
# => [{"data"=>
|
101
|
+
JSONSchemer.schema({ '$id' => '#invalid' }).validate_schema.to_a
|
102
|
+
# => [{"data"=>"#invalid",
|
101
103
|
# "data_pointer"=>"/$id",
|
102
|
-
# "schema"=>{"
|
104
|
+
# "schema"=>{"$ref"=>"#/$defs/uriReferenceString", "$comment"=>"Non-empty fragments not allowed.", "pattern"=>"^[^#]*#?$"},
|
103
105
|
# "schema_pointer"=>"/properties/$id",
|
104
106
|
# "root_schema"=>{...meta schema},
|
105
|
-
# "type"=>"
|
107
|
+
# "type"=>"pattern",
|
108
|
+
# "error"=>"string at `/$id` does not match pattern: ^[^#]*#?$"}]
|
109
|
+
|
110
|
+
# subschemas
|
111
|
+
|
112
|
+
schema = {
|
113
|
+
'type' => 'integer',
|
114
|
+
'$defs' => {
|
115
|
+
'foo' => {
|
116
|
+
'type' => 'string'
|
117
|
+
}
|
118
|
+
}
|
119
|
+
}
|
120
|
+
schemer = JSONSchemer.schema(schema)
|
121
|
+
|
122
|
+
schemer.ref('#/$defs/foo').validate(1).to_a
|
123
|
+
# => [{"data"=>1,
|
124
|
+
# "data_pointer"=>"",
|
125
|
+
# "schema"=>{"type"=>"string"},
|
126
|
+
# "schema_pointer"=>"/$defs/foo",
|
127
|
+
# "root_schema"=>{"type"=>"integer", "$defs"=>{"foo"=>{"type"=>"string"}}},
|
128
|
+
# "type"=>"string",
|
129
|
+
# "error"=>"value at root is not a string"}]
|
130
|
+
|
131
|
+
# schema bundling (https://json-schema.org/draft/2020-12/json-schema-core.html#section-9.3)
|
132
|
+
|
133
|
+
schema = {
|
134
|
+
'$id' => 'http://example.com/schema',
|
135
|
+
'allOf' => [
|
136
|
+
{ '$ref' => 'schema/one' },
|
137
|
+
{ '$ref' => 'schema/two' }
|
138
|
+
]
|
139
|
+
}
|
140
|
+
refs = {
|
141
|
+
URI('http://example.com/schema/one') => {
|
142
|
+
'type' => 'integer'
|
143
|
+
},
|
144
|
+
URI('http://example.com/schema/two') => {
|
145
|
+
'minimum' => 11
|
146
|
+
}
|
147
|
+
}
|
148
|
+
schemer = JSONSchemer.schema(schema, :ref_resolver => refs.to_proc)
|
149
|
+
|
150
|
+
schemer.bundle
|
151
|
+
# => {"$id"=>"http://example.com/schema",
|
152
|
+
# "allOf"=>[{"$ref"=>"schema/one"}, {"$ref"=>"schema/two"}],
|
153
|
+
# "$schema"=>"https://json-schema.org/draft/2020-12/schema",
|
154
|
+
# "$defs"=>
|
155
|
+
# {"http://example.com/schema/one"=>{"type"=>"integer", "$id"=>"http://example.com/schema/one", "$schema"=>"https://json-schema.org/draft/2020-12/schema"},
|
156
|
+
# "http://example.com/schema/two"=>{"minimum"=>11, "$id"=>"http://example.com/schema/two", "$schema"=>"https://json-schema.org/draft/2020-12/schema"}}}
|
106
157
|
```
|
107
158
|
|
108
159
|
## Options
|
@@ -111,11 +162,55 @@ JSONSchemer.schema({ '$id' => nil }).validate_schema.to_a
|
|
111
162
|
JSONSchemer.schema(
|
112
163
|
schema,
|
113
164
|
|
114
|
-
#
|
165
|
+
# meta schema to use for vocabularies (keyword behavior) and schema validation
|
166
|
+
# String/JSONSchemer::Schema
|
167
|
+
# 'https://json-schema.org/draft/2020-12/schema': JSONSchemer.draft202012
|
168
|
+
# 'https://json-schema.org/draft/2019-09/schema': JSONSchemer.draft201909
|
169
|
+
# 'http://json-schema.org/draft-07/schema#': JSONSchemer.draft7
|
170
|
+
# 'http://json-schema.org/draft-06/schema#': JSONSchemer.draft6
|
171
|
+
# 'http://json-schema.org/draft-04/schema#': JSONSchemer.draft4
|
172
|
+
# 'http://json-schema.org/schema#': JSONSchemer.draft4
|
173
|
+
# 'https://spec.openapis.org/oas/3.1/dialect/base': JSONSchemer.openapi31
|
174
|
+
# 'json-schemer://openapi30/schema': JSONSchemer.openapi30
|
175
|
+
# default: JSONSchemer.draft202012
|
176
|
+
meta_schema: 'https://json-schema.org/draft/2020-12/schema',
|
177
|
+
|
178
|
+
# validate `format` (https://json-schema.org/draft/2020-12/json-schema-validation.html#section-7)
|
115
179
|
# true/false
|
116
180
|
# default: true
|
117
181
|
format: true,
|
118
182
|
|
183
|
+
# custom formats
|
184
|
+
formats: {
|
185
|
+
'int32' => proc do |instance, _format|
|
186
|
+
instance.is_a?(Integer) && instance.bit_length <= 32
|
187
|
+
end,
|
188
|
+
# disable specific format
|
189
|
+
'email' => false
|
190
|
+
},
|
191
|
+
|
192
|
+
# custom content encodings
|
193
|
+
# only `base64` is available by default
|
194
|
+
content_encodings: {
|
195
|
+
# return [success, annotation] tuple
|
196
|
+
'urlsafe_base64' => proc do |instance|
|
197
|
+
[true, Base64.urlsafe_decode64(instance)]
|
198
|
+
rescue
|
199
|
+
[false, nil]
|
200
|
+
end
|
201
|
+
},
|
202
|
+
|
203
|
+
# custom content media types
|
204
|
+
# only `application/json` is available by default
|
205
|
+
content_media_types: {
|
206
|
+
# return [success, annotation] tuple
|
207
|
+
'text/csv' => proc do |instance|
|
208
|
+
[true, CSV.parse(instance)]
|
209
|
+
rescue
|
210
|
+
[false, nil]
|
211
|
+
end
|
212
|
+
},
|
213
|
+
|
119
214
|
# insert default property values during validation
|
120
215
|
# true/false
|
121
216
|
# default: false
|
@@ -147,10 +242,229 @@ JSONSchemer.schema(
|
|
147
242
|
# default: 'ruby'
|
148
243
|
regexp_resolver: proc do |pattern|
|
149
244
|
RE2::Regexp.new(pattern)
|
150
|
-
end
|
245
|
+
end,
|
246
|
+
|
247
|
+
# output formatting (https://json-schema.org/draft/2020-12/json-schema-core.html#section-12)
|
248
|
+
# 'classic'/'flag'/'basic'/'detailed'/'verbose'
|
249
|
+
# default: 'classic'
|
250
|
+
output_format: 'basic',
|
251
|
+
|
252
|
+
# validate `readOnly`/`writeOnly` keywords (https://spec.openapis.org/oas/v3.0.3#fixed-fields-19)
|
253
|
+
# 'read'/'write'/nil
|
254
|
+
# default: nil
|
255
|
+
access_mode: 'read'
|
151
256
|
)
|
152
257
|
```
|
153
258
|
|
259
|
+
## Custom Error Messages
|
260
|
+
|
261
|
+
Error messages can be customized using the `x-error` keyword and/or [I18n](https://github.com/ruby-i18n/i18n) translations. `x-error` takes precedence if both are defined.
|
262
|
+
|
263
|
+
### `x-error` Keyword
|
264
|
+
|
265
|
+
```ruby
|
266
|
+
# override all errors for a schema
|
267
|
+
schemer = JSONSchemer.schema({
|
268
|
+
'type' => 'string',
|
269
|
+
'x-error' => 'custom error for schema and all keywords'
|
270
|
+
})
|
271
|
+
|
272
|
+
schemer.validate(1).first
|
273
|
+
# => {"data"=>1,
|
274
|
+
# "data_pointer"=>"",
|
275
|
+
# "schema"=>{"type"=>"string", "x-error"=>"custom error for schema and all keywords"},
|
276
|
+
# "schema_pointer"=>"",
|
277
|
+
# "root_schema"=>{"type"=>"string", "x-error"=>"custom error for schema and all keywords"},
|
278
|
+
# "type"=>"string",
|
279
|
+
# "error"=>"custom error for schema and all keywords",
|
280
|
+
# "x-error"=>true}
|
281
|
+
|
282
|
+
schemer.validate(1, :output_format => 'basic')
|
283
|
+
# => {"valid"=>false,
|
284
|
+
# "keywordLocation"=>"",
|
285
|
+
# "absoluteKeywordLocation"=>"json-schemer://schema#",
|
286
|
+
# "instanceLocation"=>"",
|
287
|
+
# "error"=>"custom error for schema and all keywords",
|
288
|
+
# "x-error"=>true,
|
289
|
+
# "errors"=>#<Enumerator: ...>}
|
290
|
+
|
291
|
+
# keyword-specific errors
|
292
|
+
schemer = JSONSchemer.schema({
|
293
|
+
'type' => 'string',
|
294
|
+
'minLength' => 10,
|
295
|
+
'x-error' => {
|
296
|
+
'type' => 'custom error for `type` keyword',
|
297
|
+
# special `^` keyword for schema-level error
|
298
|
+
'^' => 'custom error for schema',
|
299
|
+
# same behavior as when `x-error` is a string
|
300
|
+
'*' => 'fallback error for schema and all keywords'
|
301
|
+
}
|
302
|
+
})
|
303
|
+
|
304
|
+
schemer.validate(1).map { _1.fetch('error') }
|
305
|
+
# => ["custom error for `type` keyword"]
|
306
|
+
|
307
|
+
schemer.validate('1').map { _1.fetch('error') }
|
308
|
+
# => ["custom error for schema and all keywords"]
|
309
|
+
|
310
|
+
schemer.validate(1, :output_format => 'basic').fetch('error')
|
311
|
+
# => "custom error for schema"
|
312
|
+
|
313
|
+
# variable interpolation (instance/instanceLocation/keywordLocation/absoluteKeywordLocation)
|
314
|
+
schemer = JSONSchemer.schema({
|
315
|
+
'$id' => 'https://example.com/schema',
|
316
|
+
'properties' => {
|
317
|
+
'abc' => {
|
318
|
+
'type' => 'string',
|
319
|
+
'x-error' => <<~ERROR
|
320
|
+
instance: %{instance}
|
321
|
+
instance location: %{instanceLocation}
|
322
|
+
keyword location: %{keywordLocation}
|
323
|
+
absolute keyword location: %{absoluteKeywordLocation}
|
324
|
+
ERROR
|
325
|
+
}
|
326
|
+
}
|
327
|
+
})
|
328
|
+
|
329
|
+
puts schemer.validate({ 'abc' => 1 }).first.fetch('error')
|
330
|
+
# instance: 1
|
331
|
+
# instance location: /abc
|
332
|
+
# keyword location: /properties/abc/type
|
333
|
+
# absolute keyword location: https://example.com/schema#/properties/abc/type
|
334
|
+
```
|
335
|
+
|
336
|
+
### I18n
|
337
|
+
|
338
|
+
When the [I18n gem](https://github.com/ruby-i18n/i18n) is loaded, custom error messages are looked up under the `json_schemer` key. It may be necessary to restart your application after adding the root key because the existence check is cached for performance reasons.
|
339
|
+
|
340
|
+
Translation keys are looked up in this order:
|
341
|
+
|
342
|
+
1. `$LOCALE.json_schemer.errors.$ABSOLUTE_KEYWORD_LOCATION`
|
343
|
+
2. `$LOCALE.json_schemer.errors.$SCHEMA_ID.$KEYWORD_LOCATION`
|
344
|
+
3. `$LOCALE.json_schemer.errors.$KEYWORD_LOCATION`
|
345
|
+
4. `$LOCALE.json_schemer.errors.$SCHEMA_ID.$KEYWORD`
|
346
|
+
5. `$LOCALE.json_schemer.errors.$SCHEMA_ID.*`
|
347
|
+
6. `$LOCALE.json_schemer.errors.$META_SCHEMA_ID.$KEYWORD`
|
348
|
+
7. `$LOCALE.json_schemer.errors.$META_SCHEMA_ID.*`
|
349
|
+
8. `$LOCALE.json_schemer.errors.$KEYWORD`
|
350
|
+
9. `$LOCALE.json_schemer.errors.*`
|
351
|
+
|
352
|
+
Example translations file:
|
353
|
+
|
354
|
+
```yaml
|
355
|
+
en:
|
356
|
+
json_schemer:
|
357
|
+
errors:
|
358
|
+
'https://example.com/schema#/properties/abc/type': custom error for absolute keyword location
|
359
|
+
'https://example.com/schema':
|
360
|
+
'#/properties/abc/type': custom error for keyword location, nested under schema $id
|
361
|
+
'type': custom error for `type` keyword, nested under schema $id
|
362
|
+
'^': custom error for schema, nested under schema $id
|
363
|
+
'*': fallback error for schema and all keywords, nested under schema $id
|
364
|
+
'#/properties/abc/type': custom error for keyword location
|
365
|
+
'http://json-schema.org/draft-07/schema#':
|
366
|
+
'type': custom error for `type` keyword, nested under meta-schema $id ($schema)
|
367
|
+
'^': custom error for schema, nested under meta-schema $id
|
368
|
+
'*': fallback error for schema and all keywords, nested under meta-schema $id ($schema)
|
369
|
+
'type': custom error for `type` keyword
|
370
|
+
'^': custom error for schema
|
371
|
+
# variable interpolation (instance/instanceLocation/keywordLocation/absoluteKeywordLocation)
|
372
|
+
'*': |
|
373
|
+
fallback error for schema and all keywords
|
374
|
+
instance: %{instance}
|
375
|
+
instance location: %{instanceLocation}
|
376
|
+
keyword location: %{keywordLocation}
|
377
|
+
absolute keyword location: %{absoluteKeywordLocation}
|
378
|
+
```
|
379
|
+
|
380
|
+
And output:
|
381
|
+
|
382
|
+
```ruby
|
383
|
+
require 'i18n'
|
384
|
+
I18n.locale = :en # $LOCALE=en
|
385
|
+
|
386
|
+
schemer = JSONSchemer.schema({
|
387
|
+
'$id' => 'https://example.com/schema', # $SCHEMA_ID=https://example.com/schema
|
388
|
+
'$schema' => 'http://json-schema.org/draft-07/schema#', # $META_SCHEMA_ID=http://json-schema.org/draft-07/schema#
|
389
|
+
'properties' => {
|
390
|
+
'abc' => {
|
391
|
+
'type' => 'integer' # $KEYWORD=type
|
392
|
+
} # $KEYWORD_LOCATION=#/properties/abc/type
|
393
|
+
} # $ABSOLUTE_KEYWORD_LOCATION=https://example.com/schema#/properties/abc/type
|
394
|
+
})
|
395
|
+
|
396
|
+
schemer.validate({ 'abc' => 'not-an-integer' }).first
|
397
|
+
# => {"data"=>"not-an-integer",
|
398
|
+
# "data_pointer"=>"/abc",
|
399
|
+
# "schema"=>{"type"=>"integer"},
|
400
|
+
# "schema_pointer"=>"/properties/abc",
|
401
|
+
# "root_schema"=>{"$id"=>"https://example.com/schema", "$schema"=>"http://json-schema.org/draft-07/schema#", "properties"=>{"abc"=>{"type"=>"integer"}}},
|
402
|
+
# "type"=>"integer",
|
403
|
+
# "error"=>"custom error for absolute keyword location",
|
404
|
+
# "i18n"=>true
|
405
|
+
```
|
406
|
+
|
407
|
+
In the example above, custom error messsages are looked up using the following keys (in order until one is found):
|
408
|
+
|
409
|
+
1. `en.json_schemer.errors.'https://example.com/schema#/properties/abc/type'`
|
410
|
+
2. `en.json_schemer.errors.'https://example.com/schema'.'#/properties/abc/type'`
|
411
|
+
3. `en.json_schemer.errors.'#/properties/abc/type'`
|
412
|
+
4. `en.json_schemer.errors.'https://example.com/schema'.type`
|
413
|
+
5. `en.json_schemer.errors.'https://example.com/schema'.*`
|
414
|
+
6. `en.json_schemer.errors.'http://json-schema.org/draft-07/schema#'.type`
|
415
|
+
7. `en.json_schemer.errors.'http://json-schema.org/draft-07/schema#'.*`
|
416
|
+
8. `en.json_schemer.errors.type`
|
417
|
+
9. `en.json_schemer.errors.*`
|
418
|
+
|
419
|
+
## OpenAPI
|
420
|
+
|
421
|
+
```ruby
|
422
|
+
document = JSONSchemer.openapi({
|
423
|
+
'openapi' => '3.1.0',
|
424
|
+
'info' => {
|
425
|
+
'title' => 'example'
|
426
|
+
},
|
427
|
+
'components' => {
|
428
|
+
'schemas' => {
|
429
|
+
'example' => {
|
430
|
+
'type' => 'integer'
|
431
|
+
}
|
432
|
+
}
|
433
|
+
}
|
434
|
+
})
|
435
|
+
|
436
|
+
# document validation using meta schema
|
437
|
+
|
438
|
+
document.valid?
|
439
|
+
# => false
|
440
|
+
|
441
|
+
document.validate.to_a
|
442
|
+
# => [{"data"=>{"title"=>"example"},
|
443
|
+
# "data_pointer"=>"/info",
|
444
|
+
# "schema"=>{...info schema},
|
445
|
+
# "schema_pointer"=>"/$defs/info",
|
446
|
+
# "root_schema"=>{...meta schema},
|
447
|
+
# "type"=>"required",
|
448
|
+
# "details"=>{"missing_keys"=>["version"]}},
|
449
|
+
# ...]
|
450
|
+
|
451
|
+
# data validation using schema by name (in `components/schemas`)
|
452
|
+
|
453
|
+
document.schema('example').valid?(1)
|
454
|
+
# => true
|
455
|
+
|
456
|
+
document.schema('example').valid?('one')
|
457
|
+
# => false
|
458
|
+
|
459
|
+
# data validation using schema by ref
|
460
|
+
|
461
|
+
document.ref('#/components/schemas/example').valid?(1)
|
462
|
+
# => true
|
463
|
+
|
464
|
+
document.ref('#/components/schemas/example').valid?('one')
|
465
|
+
# => false
|
466
|
+
```
|
467
|
+
|
154
468
|
## CLI
|
155
469
|
|
156
470
|
The `json_schemer` executable takes a JSON schema file as the first argument followed by one or more JSON data files to validate. If there are any validation errors, it outputs them and returns an error code.
|
data/json_schemer.gemspec
CHANGED
@@ -9,7 +9,7 @@ Gem::Specification.new do |spec|
|
|
9
9
|
spec.authors = ["David Harsha"]
|
10
10
|
spec.email = ["davishmcclurg@gmail.com"]
|
11
11
|
|
12
|
-
spec.summary = "JSON Schema validator. Supports drafts 4, 6, and
|
12
|
+
spec.summary = "JSON Schema validator. Supports drafts 4, 6, 7, 2019-09, 2020-12, OpenAPI 3.0, and OpenAPI 3.1."
|
13
13
|
spec.homepage = "https://github.com/davishmcclurg/json_schemer"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
@@ -26,6 +26,8 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.add_development_dependency "rake", "~> 13.0"
|
27
27
|
spec.add_development_dependency "minitest", "~> 5.0"
|
28
28
|
spec.add_development_dependency "simplecov", "~> 0.22"
|
29
|
+
spec.add_development_dependency "i18n"
|
30
|
+
spec.add_development_dependency "i18n-debug"
|
29
31
|
|
30
32
|
spec.add_runtime_dependency "hana", "~> 1.3"
|
31
33
|
spec.add_runtime_dependency "regexp_parser", "~> 2.0"
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module JSONSchemer
|
3
|
+
module ContentEncoding
|
4
|
+
BASE64 = proc do |instance|
|
5
|
+
[true, Base64.strict_decode64(instance)]
|
6
|
+
rescue
|
7
|
+
[false, nil]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module ContentMediaType
|
12
|
+
JSON = proc do |instance|
|
13
|
+
[true, ::JSON.parse(instance)]
|
14
|
+
rescue
|
15
|
+
[false, nil]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|