json_schemer 2.0.0 → 2.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ed0173ca9146797b3576cf21870b4d7163ea05354912d0d0be21d6e89d54d16c
4
- data.tar.gz: 1913f580bebb4d874be1a8d196a7b197156613b4240407855b9145fe03acf38b
3
+ metadata.gz: bd2081bcd9677b1615f3076f9c83171bba0f4a9c7030b94f175ea0b350b75b7c
4
+ data.tar.gz: 920692383076ade224cb1b1f02f1579a824792c07333ebdee467596a0c3e3b6a
5
5
  SHA512:
6
- metadata.gz: 8ae6cbd519a54752d739192b3438c61abd81db1f11af409a48fe77b37205c395ca577f7fadbf2819134e942d5708d4a80a59f9d39c5e0e3d42a0144b78b70426
7
- data.tar.gz: 216ac23667163998671a455298cc699a170452095acd0ef661b69a63a879f54d6c12f2676e5ab919472bd66794c6b7ab68687153f2c45ee7898e12be4ff7368c
6
+ metadata.gz: 84f674bd5560c44cbc752a18365cfbff341bb4e94a7cae43674d5591bb2229e0bd5d096346bca9e8ef54313c500496195ebabcb3d3100a39346e23be4d984f20
7
+ data.tar.gz: 77a8391db7a554ec8ff93b155c77f0945984a194a3f64178d3f184fdbfc21806736f18788295579812ddc138c6b5a1dde977a16e8aa3b8c7edf7545c15eae3fa
@@ -14,7 +14,7 @@ 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 }}
data/CHANGELOG.md CHANGED
@@ -1,6 +1,23 @@
1
1
  # Changelog
2
2
 
3
- ## [2.0.0] - XXXX-XX-XX
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
4
21
 
5
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.
6
23
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- json_schemer (2.0.0)
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
@@ -126,7 +126,7 @@ schemer.ref('#/$defs/foo').validate(1).to_a
126
126
  # "schema_pointer"=>"/$defs/foo",
127
127
  # "root_schema"=>{"type"=>"integer", "$defs"=>{"foo"=>{"type"=>"string"}}},
128
128
  # "type"=>"string",
129
- # "error"=>"instance at root is not a string"}]
129
+ # "error"=>"value at root is not a string"}]
130
130
 
131
131
  # schema bundling (https://json-schema.org/draft/2020-12/json-schema-core.html#section-9.3)
132
132
 
@@ -180,6 +180,37 @@ JSONSchemer.schema(
180
180
  # default: true
181
181
  format: true,
182
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
+
183
214
  # insert default property values during validation
184
215
  # true/false
185
216
  # default: false
@@ -225,6 +256,166 @@ JSONSchemer.schema(
225
256
  )
226
257
  ```
227
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
+
228
419
  ## OpenAPI
229
420
 
230
421
  ```ruby
data/json_schemer.gemspec CHANGED
@@ -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
@@ -2,6 +2,9 @@
2
2
  module JSONSchemer
3
3
  module Draft201909
4
4
  BASE_URI = URI('https://json-schema.org/draft/2019-09/schema')
5
+ FORMATS = Draft202012::FORMATS
6
+ CONTENT_ENCODINGS = Draft202012::CONTENT_ENCODINGS
7
+ CONTENT_MEDIA_TYPES = Draft202012::CONTENT_MEDIA_TYPES
5
8
  SCHEMA = {
6
9
  '$schema' => 'https://json-schema.org/draft/2019-09/schema',
7
10
  '$id' => 'https://json-schema.org/draft/2019-09/schema',
@@ -48,9 +51,6 @@ module JSONSchemer
48
51
  CORE = {
49
52
  '$schema' => 'https://json-schema.org/draft/2019-09/schema',
50
53
  '$id' => 'https://json-schema.org/draft/2019-09/meta/core',
51
- '$vocabulary' => {
52
- 'https://json-schema.org/draft/2019-09/vocab/core' => true
53
- },
54
54
  '$recursiveAnchor' => true,
55
55
  'title' => 'Core vocabulary meta-schema',
56
56
  'type' => ['object', 'boolean'],
@@ -105,9 +105,6 @@ module JSONSchemer
105
105
  APPLICATOR = {
106
106
  '$schema' => 'https://json-schema.org/draft/2019-09/schema',
107
107
  '$id' => 'https://json-schema.org/draft/2019-09/meta/applicator',
108
- '$vocabulary' => {
109
- 'https://json-schema.org/draft/2019-09/vocab/applicator' => true
110
- },
111
108
  '$recursiveAnchor' => true,
112
109
  'title' => 'Applicator vocabulary meta-schema',
113
110
  'type' => ['object', 'boolean'],
@@ -161,9 +158,6 @@ module JSONSchemer
161
158
  VALIDATION = {
162
159
  '$schema' => 'https://json-schema.org/draft/2019-09/schema',
163
160
  '$id' => 'https://json-schema.org/draft/2019-09/meta/validation',
164
- '$vocabulary' => {
165
- 'https://json-schema.org/draft/2019-09/vocab/validation' => true
166
- },
167
161
  '$recursiveAnchor' => true,
168
162
  'title' => 'Validation vocabulary meta-schema',
169
163
  'type' => ['object', 'boolean'],
@@ -259,9 +253,6 @@ module JSONSchemer
259
253
  META_DATA = {
260
254
  '$schema' => 'https://json-schema.org/draft/2019-09/schema',
261
255
  '$id' => 'https://json-schema.org/draft/2019-09/meta/meta-data',
262
- '$vocabulary' => {
263
- 'https://json-schema.org/draft/2019-09/vocab/meta-data' => true
264
- },
265
256
  '$recursiveAnchor' => true,
266
257
  'title' => 'Meta-data vocabulary meta-schema',
267
258
  'type' => ['object', 'boolean'],
@@ -295,9 +286,6 @@ module JSONSchemer
295
286
  FORMAT = {
296
287
  '$schema' => 'https://json-schema.org/draft/2019-09/schema',
297
288
  '$id' => 'https://json-schema.org/draft/2019-09/meta/format',
298
- '$vocabulary' => {
299
- 'https://json-schema.org/draft/2019-09/vocab/format' => true
300
- },
301
289
  '$recursiveAnchor' => true,
302
290
  'title' => 'Format vocabulary meta-schema',
303
291
  'type' => ['object', 'boolean'],
@@ -309,9 +297,6 @@ module JSONSchemer
309
297
  CONTENT = {
310
298
  '$schema' => 'https://json-schema.org/draft/2019-09/schema',
311
299
  '$id' => 'https://json-schema.org/draft/2019-09/meta/content',
312
- '$vocabulary' => {
313
- 'https://json-schema.org/draft/2019-09/vocab/content' => true
314
- },
315
300
  '$recursiveAnchor' => true,
316
301
  'title' => 'Content vocabulary meta-schema',
317
302
  'type' => ['object', 'boolean'],
@@ -2,6 +2,33 @@
2
2
  module JSONSchemer
3
3
  module Draft202012
4
4
  BASE_URI = URI('https://json-schema.org/draft/2020-12/schema')
5
+ FORMATS = {
6
+ 'date-time' => Format::DATE_TIME,
7
+ 'date' => Format::DATE,
8
+ 'time' => Format::TIME,
9
+ 'duration' => Format::DURATION,
10
+ 'email' => Format::EMAIL,
11
+ 'idn-email' => Format::IDN_EMAIL,
12
+ 'hostname' => Format::HOSTNAME,
13
+ 'idn-hostname' => Format::IDN_HOSTNAME,
14
+ 'ipv4' => Format::IPV4,
15
+ 'ipv6' => Format::IPV6,
16
+ 'uri' => Format::URI,
17
+ 'uri-reference' => Format::URI_REFERENCE,
18
+ 'iri' => Format::IRI,
19
+ 'iri-reference' => Format::IRI_REFERENCE,
20
+ 'uuid' => Format::UUID,
21
+ 'uri-template' => Format::URI_TEMPLATE,
22
+ 'json-pointer' => Format::JSON_POINTER,
23
+ 'relative-json-pointer' => Format::RELATIVE_JSON_POINTER,
24
+ 'regex' => Format::REGEX
25
+ }
26
+ CONTENT_ENCODINGS = {
27
+ 'base64' => ContentEncoding::BASE64
28
+ }
29
+ CONTENT_MEDIA_TYPES = {
30
+ 'application/json' => ContentMediaType::JSON
31
+ }
5
32
  SCHEMA = {
6
33
  '$schema' => 'https://json-schema.org/draft/2020-12/schema',
7
34
  '$id' => 'https://json-schema.org/draft/2020-12/schema',
@@ -64,9 +91,6 @@ module JSONSchemer
64
91
  CORE = {
65
92
  '$schema' => 'https://json-schema.org/draft/2020-12/schema',
66
93
  '$id' => 'https://json-schema.org/draft/2020-12/meta/core',
67
- '$vocabulary' => {
68
- 'https://json-schema.org/draft/2020-12/vocab/core' => true
69
- },
70
94
  '$dynamicAnchor' => 'meta',
71
95
  'title' => 'Core vocabulary meta-schema',
72
96
  'type' => ['object', 'boolean'],
@@ -114,9 +138,6 @@ module JSONSchemer
114
138
  APPLICATOR = {
115
139
  '$schema' => 'https://json-schema.org/draft/2020-12/schema',
116
140
  '$id' => 'https://json-schema.org/draft/2020-12/meta/applicator',
117
- '$vocabulary' => {
118
- 'https://json-schema.org/draft/2020-12/vocab/applicator' => true
119
- },
120
141
  '$dynamicAnchor' => 'meta',
121
142
  'title' => 'Applicator vocabulary meta-schema',
122
143
  'type' => ['object', 'boolean'],
@@ -161,9 +182,6 @@ module JSONSchemer
161
182
  UNEVALUATED = {
162
183
  '$schema' => 'https://json-schema.org/draft/2020-12/schema',
163
184
  '$id' => 'https://json-schema.org/draft/2020-12/meta/unevaluated',
164
- '$vocabulary' => {
165
- 'https://json-schema.org/draft/2020-12/vocab/unevaluated' => true
166
- },
167
185
  '$dynamicAnchor' => 'meta',
168
186
  'title' => 'Unevaluated applicator vocabulary meta-schema',
169
187
  'type' => ['object', 'boolean'],
@@ -175,9 +193,6 @@ module JSONSchemer
175
193
  VALIDATION = {
176
194
  '$schema' => 'https://json-schema.org/draft/2020-12/schema',
177
195
  '$id' => 'https://json-schema.org/draft/2020-12/meta/validation',
178
- '$vocabulary' => {
179
- 'https://json-schema.org/draft/2020-12/vocab/validation' => true
180
- },
181
196
  '$dynamicAnchor' => 'meta',
182
197
  'title' => 'Validation vocabulary meta-schema',
183
198
  'type' => ['object', 'boolean'],
@@ -272,9 +287,6 @@ module JSONSchemer
272
287
  META_DATA = {
273
288
  '$schema' => 'https://json-schema.org/draft/2020-12/schema',
274
289
  '$id' => 'https://json-schema.org/draft/2020-12/meta/meta-data',
275
- '$vocabulary' => {
276
- 'https://json-schema.org/draft/2020-12/vocab/meta-data' => true
277
- },
278
290
  '$dynamicAnchor' => 'meta',
279
291
  'title' => 'Meta-data vocabulary meta-schema',
280
292
  'type' => ['object', 'boolean'],
@@ -307,9 +319,6 @@ module JSONSchemer
307
319
  FORMAT_ANNOTATION = {
308
320
  '$schema' => 'https://json-schema.org/draft/2020-12/schema',
309
321
  '$id' => 'https://json-schema.org/draft/2020-12/meta/format-annotation',
310
- '$vocabulary' => {
311
- 'https://json-schema.org/draft/2020-12/vocab/format-annotation' => true
312
- },
313
322
  '$dynamicAnchor' => 'meta',
314
323
  'title' => 'Format vocabulary meta-schema for annotation results',
315
324
  'type' => ['object', 'boolean'],
@@ -320,9 +329,6 @@ module JSONSchemer
320
329
  FORMAT_ASSERTION = {
321
330
  '$schema' => 'https://json-schema.org/draft/2020-12/schema',
322
331
  '$id' => 'https://json-schema.org/draft/2020-12/meta/format-assertion',
323
- '$vocabulary' => {
324
- 'https://json-schema.org/draft/2020-12/vocab/format-assertion' => true
325
- },
326
332
  '$dynamicAnchor' => 'meta',
327
333
  'title' => 'Format vocabulary meta-schema for assertion results',
328
334
  'type' => ['object', 'boolean'],
@@ -333,9 +339,6 @@ module JSONSchemer
333
339
  CONTENT = {
334
340
  '$schema' => 'https://json-schema.org/draft/2020-12/schema',
335
341
  '$id' => 'https://json-schema.org/draft/2020-12/meta/content',
336
- '$vocabulary' => {
337
- 'https://json-schema.org/draft/2020-12/vocab/content' => true
338
- },
339
342
  '$dynamicAnchor' => 'meta',
340
343
  'title' => 'Content vocabulary meta-schema',
341
344
  'type' => ['object', 'boolean'],
@@ -4,21 +4,29 @@ module JSONSchemer
4
4
  module Vocab
5
5
  module Content
6
6
  class ContentEncoding < Keyword
7
+ def parse
8
+ root.fetch_content_encoding(value) { raise UnknownContentEncoding, value }
9
+ end
10
+
7
11
  def validate(instance, instance_location, keyword_location, _context)
8
12
  return result(instance, instance_location, keyword_location, true) unless instance.is_a?(String)
9
13
 
10
- _valid, annotation = Format.decode_content_encoding(instance, value)
14
+ _valid, annotation = parsed.call(instance)
11
15
 
12
16
  result(instance, instance_location, keyword_location, true, :annotation => annotation)
13
17
  end
14
18
  end
15
19
 
16
20
  class ContentMediaType < Keyword
21
+ def parse
22
+ root.fetch_content_media_type(value) { raise UnknownContentMediaType, value }
23
+ end
24
+
17
25
  def validate(instance, instance_location, keyword_location, context)
18
26
  return result(instance, instance_location, keyword_location, true) unless instance.is_a?(String)
19
27
 
20
28
  decoded_instance = context.adjacent_results[ContentEncoding]&.annotation || instance
21
- _valid, annotation = Format.parse_content_media_type(decoded_instance, value)
29
+ _valid, annotation = parsed.call(decoded_instance)
22
30
 
23
31
  result(instance, instance_location, keyword_location, true, :annotation => annotation)
24
32
  end
@@ -119,6 +119,12 @@ module JSONSchemer
119
119
 
120
120
  class Comment < Keyword; end
121
121
 
122
+ class XError < Keyword
123
+ def message(error_key)
124
+ value.is_a?(Hash) ? (value[error_key] || value[CATCHALL]) : value
125
+ end
126
+ end
127
+
122
128
  class UnknownKeyword < Keyword
123
129
  def parse
124
130
  if value.is_a?(Hash)
@@ -4,20 +4,12 @@ module JSONSchemer
4
4
  module Vocab
5
5
  module FormatAnnotation
6
6
  class Format < Keyword
7
- extend JSONSchemer::Format
8
-
9
- DEFAULT_FORMAT = proc do |instance, value|
10
- !instance.is_a?(String) || valid_spec_format?(instance, value)
11
- rescue UnknownFormat
12
- true
13
- end
14
-
15
7
  def error(formatted_instance_location:, **)
16
8
  "value at #{formatted_instance_location} does not match format: #{value}"
17
9
  end
18
10
 
19
11
  def parse
20
- root.format && root.formats.fetch(value) { root.meta_schema.formats.fetch(value, DEFAULT_FORMAT) }
12
+ root.format && root.fetch_format(value, false)
21
13
  end
22
14
 
23
15
  def validate(instance, instance_location, keyword_location, _context)
@@ -4,18 +4,12 @@ module JSONSchemer
4
4
  module Vocab
5
5
  module FormatAssertion
6
6
  class Format < Keyword
7
- extend JSONSchemer::Format
8
-
9
- DEFAULT_FORMAT = proc do |instance, value|
10
- !instance.is_a?(String) || valid_spec_format?(instance, value)
11
- end
12
-
13
7
  def error(formatted_instance_location:, **)
14
8
  "value at #{formatted_instance_location} does not match format: #{value}"
15
9
  end
16
10
 
17
11
  def parse
18
- root.format && root.formats.fetch(value) { root.meta_schema.formats.fetch(value, DEFAULT_FORMAT) }
12
+ root.format && root.fetch_format(value) { raise UnknownFormat, value }
19
13
  end
20
14
 
21
15
  def validate(instance, instance_location, keyword_location, _context)
@@ -16,7 +16,9 @@ module JSONSchemer
16
16
  '$defs' => Core::Defs,
17
17
  'definitions' => Core::Defs,
18
18
  # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-8.3
19
- '$comment' => Core::Comment
19
+ '$comment' => Core::Comment,
20
+ # https://github.com/orgs/json-schema-org/discussions/329
21
+ 'x-error' => Core::XError
20
22
  }
21
23
  # https://datatracker.ietf.org/doc/html/draft-bhutton-json-schema-01#section-10
22
24
  APPLICATOR = {
@@ -2,6 +2,12 @@
2
2
  module JSONSchemer
3
3
  module Draft4
4
4
  BASE_URI = URI('http://json-schema.org/draft-04/schema#')
5
+ FORMATS = Draft6::FORMATS.dup
6
+ FORMATS.delete('uri-reference')
7
+ FORMATS.delete('uri-template')
8
+ FORMATS.delete('json-pointer')
9
+ CONTENT_ENCODINGS = Draft6::CONTENT_ENCODINGS
10
+ CONTENT_MEDIA_TYPES = Draft6::CONTENT_MEDIA_TYPES
5
11
  SCHEMA = {
6
12
  'id' => 'http://json-schema.org/draft-04/schema#',
7
13
  '$schema' => 'http://json-schema.org/draft-04/schema#',
@@ -2,6 +2,17 @@
2
2
  module JSONSchemer
3
3
  module Draft6
4
4
  BASE_URI = URI('http://json-schema.org/draft-06/schema#')
5
+ FORMATS = Draft7::FORMATS.dup
6
+ FORMATS.delete('date')
7
+ FORMATS.delete('time')
8
+ FORMATS.delete('idn-email')
9
+ FORMATS.delete('idn-hostname')
10
+ FORMATS.delete('iri')
11
+ FORMATS.delete('iri-reference')
12
+ FORMATS.delete('relative-json-pointer')
13
+ FORMATS.delete('regex')
14
+ CONTENT_ENCODINGS = Draft7::CONTENT_ENCODINGS
15
+ CONTENT_MEDIA_TYPES = Draft7::CONTENT_MEDIA_TYPES
5
16
  SCHEMA = {
6
17
  '$schema' => 'http://json-schema.org/draft-06/schema#',
7
18
  '$id' => 'http://json-schema.org/draft-06/schema#',
@@ -2,6 +2,11 @@
2
2
  module JSONSchemer
3
3
  module Draft7
4
4
  BASE_URI = URI('http://json-schema.org/draft-07/schema#')
5
+ FORMATS = Draft201909::FORMATS.dup
6
+ FORMATS.delete('duration')
7
+ FORMATS.delete('uuid')
8
+ CONTENT_ENCODINGS = Draft201909::CONTENT_ENCODINGS
9
+ CONTENT_MEDIA_TYPES = Draft201909::CONTENT_MEDIA_TYPES
5
10
  SCHEMA = {
6
11
  '$schema' => 'http://json-schema.org/draft-07/schema#',
7
12
  '$id' => 'http://json-schema.org/draft-07/schema#',