json_schemer 1.0.3 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -7
  3. data/CHANGELOG.md +42 -0
  4. data/Gemfile.lock +10 -3
  5. data/README.md +328 -14
  6. data/json_schemer.gemspec +3 -1
  7. data/lib/json_schemer/content.rb +18 -0
  8. data/lib/json_schemer/draft201909/meta.rb +320 -0
  9. data/lib/json_schemer/draft201909/vocab/applicator.rb +104 -0
  10. data/lib/json_schemer/draft201909/vocab/core.rb +45 -0
  11. data/lib/json_schemer/draft201909/vocab.rb +31 -0
  12. data/lib/json_schemer/draft202012/meta.rb +364 -0
  13. data/lib/json_schemer/draft202012/vocab/applicator.rb +382 -0
  14. data/lib/json_schemer/draft202012/vocab/content.rb +52 -0
  15. data/lib/json_schemer/draft202012/vocab/core.rb +160 -0
  16. data/lib/json_schemer/draft202012/vocab/format_annotation.rb +23 -0
  17. data/lib/json_schemer/draft202012/vocab/format_assertion.rb +23 -0
  18. data/lib/json_schemer/draft202012/vocab/meta_data.rb +30 -0
  19. data/lib/json_schemer/draft202012/vocab/unevaluated.rb +94 -0
  20. data/lib/json_schemer/draft202012/vocab/validation.rb +286 -0
  21. data/lib/json_schemer/draft202012/vocab.rb +105 -0
  22. data/lib/json_schemer/draft4/meta.rb +161 -0
  23. data/lib/json_schemer/draft4/vocab/validation.rb +39 -0
  24. data/lib/json_schemer/draft4/vocab.rb +18 -0
  25. data/lib/json_schemer/draft6/meta.rb +172 -0
  26. data/lib/json_schemer/draft6/vocab.rb +16 -0
  27. data/lib/json_schemer/draft7/meta.rb +183 -0
  28. data/lib/json_schemer/draft7/vocab/validation.rb +69 -0
  29. data/lib/json_schemer/draft7/vocab.rb +30 -0
  30. data/lib/json_schemer/errors.rb +1 -0
  31. data/lib/json_schemer/format/duration.rb +23 -0
  32. data/lib/json_schemer/format/json_pointer.rb +18 -0
  33. data/lib/json_schemer/format.rb +128 -106
  34. data/lib/json_schemer/keyword.rb +45 -0
  35. data/lib/json_schemer/location.rb +25 -0
  36. data/lib/json_schemer/openapi.rb +40 -0
  37. data/lib/json_schemer/openapi30/document.rb +1673 -0
  38. data/lib/json_schemer/openapi30/meta.rb +32 -0
  39. data/lib/json_schemer/openapi30/vocab/base.rb +18 -0
  40. data/lib/json_schemer/openapi30/vocab.rb +12 -0
  41. data/lib/json_schemer/openapi31/document.rb +1559 -0
  42. data/lib/json_schemer/openapi31/meta.rb +136 -0
  43. data/lib/json_schemer/openapi31/vocab/base.rb +127 -0
  44. data/lib/json_schemer/openapi31/vocab.rb +18 -0
  45. data/lib/json_schemer/output.rb +56 -0
  46. data/lib/json_schemer/result.rb +229 -0
  47. data/lib/json_schemer/schema.rb +424 -0
  48. data/lib/json_schemer/version.rb +1 -1
  49. data/lib/json_schemer.rb +198 -24
  50. metadata +71 -10
  51. data/lib/json_schemer/schema/base.rb +0 -677
  52. data/lib/json_schemer/schema/draft4.json +0 -149
  53. data/lib/json_schemer/schema/draft4.rb +0 -44
  54. data/lib/json_schemer/schema/draft6.json +0 -155
  55. data/lib/json_schemer/schema/draft6.rb +0 -25
  56. data/lib/json_schemer/schema/draft7.json +0 -172
  57. data/lib/json_schemer/schema/draft7.rb +0 -32
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '03538ce8b7525466940396aecbfffeac23f2f942af4e11023ea370d73045ec8b'
4
- data.tar.gz: a06e7a91e8c851dffac5b9ecab91e47b6e89d46173abc8f95619dc4eca7f938d
3
+ metadata.gz: bd2081bcd9677b1615f3076f9c83171bba0f4a9c7030b94f175ea0b350b75b7c
4
+ data.tar.gz: 920692383076ade224cb1b1f02f1579a824792c07333ebdee467596a0c3e3b6a
5
5
  SHA512:
6
- metadata.gz: 87f3d572f430bc5b64f25ffc96bf5fcf979fa53eea9e0d325e6d0b853b4f653d2264e151ab312da4e472a02342eb162e9ffdfa3b06c3ec29fa09e39123412f3a
7
- data.tar.gz: fbbed8f04fea519b2264075051e64a7f6af2ade9b92600a490a2291065ed9a13eb15fb73cf356a0caff5ea3f63065d4c7a50ff93b293441273ed98708429cfe8
6
+ metadata.gz: 84f674bd5560c44cbc752a18365cfbff341bb4e94a7cae43674d5591bb2229e0bd5d096346bca9e8ef54313c500496195ebabcb3d3100a39346e23be4d984f20
7
+ data.tar.gz: 77a8391db7a554ec8ff93b155c77f0945984a194a3f64178d3f184fdbfc21806736f18788295579812ddc138c6b5a1dde977a16e8aa3b8c7edf7545c15eae3fa
@@ -14,14 +14,9 @@ jobs:
14
14
  ruby: truffleruby-head
15
15
  runs-on: ${{ matrix.os }}
16
16
  steps:
17
- - uses: actions/checkout@v2
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,47 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.1.0] - XXXX-XX-XX
4
+
5
+ ### Bug Fixes
6
+
7
+ - Limit anyOf/oneOf discriminator to listed refs: https://github.com/davishmcclurg/json_schemer/pull/145
8
+ - Require discriminator `propertyName` property: https://github.com/davishmcclurg/json_schemer/pull/145
9
+ - Support `Schema#ref` in subschemas: https://github.com/davishmcclurg/json_schemer/pull/145
10
+ - Resolve JSON pointer refs using correct base URI: https://github.com/davishmcclurg/json_schemer/pull/147
11
+ - `date` format in OpenAPI 3.0: https://github.com/davishmcclurg/json_schemer/commit/69fe7a815ecf0cfb1c40ac402bf46a789c05e972
12
+
13
+ ### Features
14
+
15
+ - Custom error messages with `x-error` keyword and I18n: https://github.com/davishmcclurg/json_schemer/pull/149
16
+ - Custom content encodings and media types: https://github.com/davishmcclurg/json_schemer/pull/148
17
+
18
+ [2.1.0]: https://github.com/davishmcclurg/json_schemer/releases/tag/v2.1.0
19
+
20
+ ## [2.0.0] - 2023-08-20
21
+
22
+ 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.
23
+
24
+ ### Breaking Changes
25
+
26
+ - The default meta schema is now Draft 2020-12. Other meta schemas can be specified using `meta_schema`.
27
+ - 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"`.
28
+ - 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.
29
+ - `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).
30
+ - Error output
31
+ - Special characters in `schema_pointer` are no longer percent encoded (eg, `definitions/foo\"bar` instead of `/definitions/foo%22bar`)
32
+ - Keyword validation order changed so errors may be returned in a different order (eg, `items` errors before `contains`).
33
+ - Array `dependencies` return `"type": "dependencies"` errors instead of `"required"` and point to the schema that contains the `dependencies` keyword.
34
+ - `not` errors point to the schema that contains the `not` keyword (instead of the schema defined by the `not` keyword).
35
+ - Custom keyword errors are now always wrapped in regular error hashes. Returned strings are used to set `type`:
36
+ ```
37
+ >> JSONSchemer.schema({ 'x' => 'y' }, :keywords => { 'x' => proc { false } }).validate({}).to_a
38
+ => [{"data"=>{}, "data_pointer"=>"", "schema"=>{"x"=>"y"}, "schema_pointer"=>"", "root_schema"=>{"x"=>"y"}, "type"=>"x"}]
39
+ >> JSONSchemer.schema({ 'x' => 'y' }, :keywords => { 'x' => proc { 'wrong!' } }).validate({}).to_a
40
+ => [{"data"=>{}, "data_pointer"=>"", "schema"=>{"x"=>"y"}, "schema_pointer"=>"", "root_schema"=>{"x"=>"y"}, "type"=>"wrong!"}]
41
+ ```
42
+
43
+ [2.0.0]: https://github.com/davishmcclurg/json_schemer/releases/tag/v2.0.0
44
+
3
45
  ## [1.0.0] - 2023-05-26
4
46
 
5
47
  ### Breaking Changes
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- json_schemer (1.0.3)
4
+ json_schemer (2.1.0)
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.1)
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.8.2)
33
+ unf_ext (0.0.9)
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 7.
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' => '#valid' })
86
+ JSONSchemer.valid_schema?({ '$id' => 'valid' })
86
87
  # => true
87
88
 
88
- JSONSchemer.validate_schema({ '$id' => nil }).to_a
89
- # => [{"data"=>nil,
89
+ JSONSchemer.validate_schema({ '$id' => '#invalid' }).to_a
90
+ # => [{"data"=>"#invalid",
90
91
  # "data_pointer"=>"/$id",
91
- # "schema"=>{"type"=>"string", "format"=>"uri-reference"},
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"=>"string"}]
95
+ # "type"=>"pattern",
96
+ # "error"=>"string at `/$id` does not match pattern: ^[^#]*#?$"}]
95
97
 
96
- JSONSchemer.schema({ '$id' => '#valid' }).valid_schema?
98
+ JSONSchemer.schema({ '$id' => 'valid' }).valid_schema?
97
99
  # => true
98
100
 
99
- JSONSchemer.schema({ '$id' => nil }).validate_schema.to_a
100
- # => [{"data"=>nil,
101
+ JSONSchemer.schema({ '$id' => '#invalid' }).validate_schema.to_a
102
+ # => [{"data"=>"#invalid",
101
103
  # "data_pointer"=>"/$id",
102
- # "schema"=>{"type"=>"string", "format"=>"uri-reference"},
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"=>"string"}]
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
- # validate `format` (https://tools.ietf.org/html/draft-handrews-json-schema-validation-00#section-7)
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 7."
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