json_schemer 2.0.0 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ed0173ca9146797b3576cf21870b4d7163ea05354912d0d0be21d6e89d54d16c
4
- data.tar.gz: 1913f580bebb4d874be1a8d196a7b197156613b4240407855b9145fe03acf38b
3
+ metadata.gz: 1981782f2342c7123beb0374680037bc11f3be72a8e4be0a213cfb7691643415
4
+ data.tar.gz: e8b6c605ba1ce2a2751ac90167f53c27fad975140ab9a0e8328c2b04ce8fe732
5
5
  SHA512:
6
- metadata.gz: 8ae6cbd519a54752d739192b3438c61abd81db1f11af409a48fe77b37205c395ca577f7fadbf2819134e942d5708d4a80a59f9d39c5e0e3d42a0144b78b70426
7
- data.tar.gz: 216ac23667163998671a455298cc699a170452095acd0ef661b69a63a879f54d6c12f2676e5ab919472bd66794c6b7ab68687153f2c45ee7898e12be4ff7368c
6
+ metadata.gz: 4d4c6e6c3660560a602b5610559efe37b5839f1142af6486e350bd207cad49e37a00f397d555878957b14951da95745092240bd14417e2ffd02826f053099952
7
+ data.tar.gz: 8dc561414463ceeacf7bbd0be95be9c441a50919cdb839ff8366fa0268d96d91e4947985bd7bab406f3c163e913bf7ca923a38e22d777d2549af3329f91c14f2
@@ -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,32 @@
1
1
  # Changelog
2
2
 
3
- ## [2.0.0] - XXXX-XX-XX
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
4
30
 
5
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.
6
32
 
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.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.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.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
@@ -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)
@@ -130,7 +136,7 @@ module JSONSchemer
130
136
  end
131
137
  end
132
138
 
133
- def fetch_unknown!(token)
139
+ def fetch(token)
134
140
  if value.is_a?(Hash)
135
141
  parsed[token] ||= JSONSchemer::Schema::UNKNOWN_KEYWORD_CLASS.new(value.fetch(token), self, token, schema)
136
142
  elsif value.is_a?(Array)
@@ -140,8 +146,8 @@ module JSONSchemer
140
146
  end
141
147
  end
142
148
 
143
- def unknown_schema!
144
- @unknown_schema ||= subschema(value)
149
+ def parsed_schema
150
+ @parsed_schema ||= subschema(value)
145
151
  end
146
152
 
147
153
  def validate(instance, instance_location, keyword_location, _context)
@@ -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#',