json_schemer 0.2.18 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +3 -7
  3. data/CHANGELOG.md +89 -0
  4. data/Gemfile.lock +35 -10
  5. data/README.md +395 -5
  6. data/bin/hostname_character_classes +42 -0
  7. data/bin/rake +29 -0
  8. data/exe/json_schemer +62 -0
  9. data/json_schemer.gemspec +9 -12
  10. data/lib/json_schemer/cached_resolver.rb +16 -0
  11. data/lib/json_schemer/configuration.rb +32 -0
  12. data/lib/json_schemer/content.rb +18 -0
  13. data/lib/json_schemer/draft201909/meta.rb +320 -0
  14. data/lib/json_schemer/draft201909/vocab/applicator.rb +104 -0
  15. data/lib/json_schemer/draft201909/vocab/core.rb +45 -0
  16. data/lib/json_schemer/draft201909/vocab.rb +31 -0
  17. data/lib/json_schemer/draft202012/meta.rb +364 -0
  18. data/lib/json_schemer/draft202012/vocab/applicator.rb +382 -0
  19. data/lib/json_schemer/draft202012/vocab/content.rb +52 -0
  20. data/lib/json_schemer/draft202012/vocab/core.rb +160 -0
  21. data/lib/json_schemer/draft202012/vocab/format_annotation.rb +23 -0
  22. data/lib/json_schemer/draft202012/vocab/format_assertion.rb +23 -0
  23. data/lib/json_schemer/draft202012/vocab/meta_data.rb +30 -0
  24. data/lib/json_schemer/draft202012/vocab/unevaluated.rb +104 -0
  25. data/lib/json_schemer/draft202012/vocab/validation.rb +286 -0
  26. data/lib/json_schemer/draft202012/vocab.rb +105 -0
  27. data/lib/json_schemer/draft4/meta.rb +161 -0
  28. data/lib/json_schemer/draft4/vocab/validation.rb +39 -0
  29. data/lib/json_schemer/draft4/vocab.rb +18 -0
  30. data/lib/json_schemer/draft6/meta.rb +172 -0
  31. data/lib/json_schemer/draft6/vocab.rb +16 -0
  32. data/lib/json_schemer/draft7/meta.rb +183 -0
  33. data/lib/json_schemer/draft7/vocab/validation.rb +69 -0
  34. data/lib/json_schemer/draft7/vocab.rb +30 -0
  35. data/lib/json_schemer/ecma_regexp.rb +51 -0
  36. data/lib/json_schemer/errors.rb +1 -0
  37. data/lib/json_schemer/format/duration.rb +23 -0
  38. data/lib/json_schemer/format/email.rb +56 -0
  39. data/lib/json_schemer/format/hostname.rb +58 -0
  40. data/lib/json_schemer/format/json_pointer.rb +18 -0
  41. data/lib/json_schemer/format/uri_template.rb +34 -0
  42. data/lib/json_schemer/format.rb +128 -109
  43. data/lib/json_schemer/keyword.rb +56 -0
  44. data/lib/json_schemer/location.rb +25 -0
  45. data/lib/json_schemer/openapi.rb +38 -0
  46. data/lib/json_schemer/openapi30/document.rb +1672 -0
  47. data/lib/json_schemer/openapi30/meta.rb +32 -0
  48. data/lib/json_schemer/openapi30/vocab/base.rb +18 -0
  49. data/lib/json_schemer/openapi30/vocab.rb +12 -0
  50. data/lib/json_schemer/openapi31/document.rb +1557 -0
  51. data/lib/json_schemer/openapi31/meta.rb +136 -0
  52. data/lib/json_schemer/openapi31/vocab/base.rb +127 -0
  53. data/lib/json_schemer/openapi31/vocab.rb +18 -0
  54. data/lib/json_schemer/output.rb +56 -0
  55. data/lib/json_schemer/result.rb +241 -0
  56. data/lib/json_schemer/schema.rb +424 -0
  57. data/lib/json_schemer/version.rb +1 -1
  58. data/lib/json_schemer.rb +243 -29
  59. metadata +141 -25
  60. data/lib/json_schemer/cached_ref_resolver.rb +0 -14
  61. data/lib/json_schemer/schema/base.rb +0 -658
  62. data/lib/json_schemer/schema/draft4.rb +0 -44
  63. data/lib/json_schemer/schema/draft6.rb +0 -25
  64. data/lib/json_schemer/schema/draft7.rb +0 -32
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 426a95173deee91594b5ad1df05dac15ea3c90bac6e17b3136a7170744555c50
4
- data.tar.gz: 25268e7f5cb108245aad3d112624eee96774fe8820eda5e24a4f91d97d91e676
3
+ metadata.gz: 4013d2026a3ac04ca50649b4cf6437ea2ee477ed1e9d350942cfa7e56ead0828
4
+ data.tar.gz: 810f08388b21cc68d849181a87a820982c2fc7a577c402d5e05699b0c8d07507
5
5
  SHA512:
6
- metadata.gz: 5534a623dfece170bd27bbb2cbc34978e9e81c075ddad60213d8ce4538d50a6ee119b7efcb2b750068cee0cf565ee582a35eac990c14c2ca647d9d8cc314d4f5
7
- data.tar.gz: b813c1b1acd0b1cf0680bf0ecae7eef33b6eb576ff5735d76f072e82fc5cfb36e80ec5b0a3fde6829ec3de856e903b42164a917b833019c33a11d79d6db6080b
6
+ metadata.gz: f4c5bb6fe5e6d4e0ccfd8abd28a0c12ae23254e74c807ba94278680bf7b706970276cef14f26eaf488f9c46ebd8d418b5aef90bf4abd886b9fd1d7513bb6d27a
7
+ data.tar.gz: 19a0c839189ba44d6688c389fa38b3f0b1ccc9986643471e2df4d50c347c5ce22ec3fa170002fc000fc877e05080842b57cc01351f46ff1e876ba972880e6584
@@ -6,21 +6,17 @@ jobs:
6
6
  fail-fast: false
7
7
  matrix:
8
8
  os: [ubuntu-latest, windows-latest, macos-latest]
9
- ruby: [2.4, 2.5, 2.6, 2.7, 3.0, head, jruby, jruby-head, truffleruby, truffleruby-head]
9
+ ruby: [2.5, 2.6, 2.7, 3.0, 3.1, 3.2, 3.3, head, jruby, jruby-head, truffleruby, truffleruby-head]
10
10
  exclude:
11
- - os: windows-latest
12
- ruby: jruby
13
- - os: windows-latest
14
- ruby: jruby-head
15
11
  - os: windows-latest
16
12
  ruby: truffleruby
17
13
  - os: windows-latest
18
14
  ruby: truffleruby-head
19
15
  runs-on: ${{ matrix.os }}
20
16
  steps:
21
- - uses: actions/checkout@v2
17
+ - uses: actions/checkout@v4
22
18
  - uses: ruby/setup-ruby@v1
23
19
  with:
24
20
  ruby-version: ${{ matrix.ruby }}
25
21
  bundler-cache: true
26
- - run: bundle exec rake test
22
+ - run: bin/rake test
data/CHANGELOG.md ADDED
@@ -0,0 +1,89 @@
1
+ # Changelog
2
+
3
+ ## [2.2.0] - XXXX-XX-XX
4
+
5
+ ## Bug Fixes
6
+
7
+ - Support symbol keys when accessing original instance: https://github.com/davishmcclurg/json_schemer/commit/d52c130e9967919c6cf1c9dbc3f0babfb8b01cf8
8
+ - Support custom keywords in nested schemas: https://github.com/davishmcclurg/json_schemer/commit/93c85a5006981347c7e9a4c11b73c6bdb65d8ba2
9
+ - Stringify instance location for custom keywords: https://github.com/davishmcclurg/json_schemer/commit/513c99130b9e7986b09881e7efd3fb7143744754
10
+ - Reduce unhelpful error output in `unevaluated` keywords: https://github.com/davishmcclurg/json_schemer/pull/164
11
+ - Handle parse errors during schema validation: https://github.com/davishmcclurg/json_schemer/pull/171
12
+ - Follow refs when finding default property values: https://github.com/davishmcclurg/json_schemer/pull/175
13
+
14
+ ## Features
15
+
16
+ - Global configuration with `Configuration` object: https://github.com/davishmcclurg/json_schemer/pull/170
17
+ - Symbol key property defaults with `insert_property_defaults: :symbol`: https://github.com/davishmcclurg/json_schemer/commit/a72473dc84199107ddedc8998950e5b82273232a
18
+ - Consistent schema type support for schema validation methods: https://github.com/davishmcclurg/json_schemer/commit/bbcd0cea20cbaa61cf2bdae5f53840861cae54b8
19
+ - Validation option support for schema validation methods: https://github.com/davishmcclurg/json_schemer/commit/2eeef77de522f127619b7d0faa51e0d7e40977ad
20
+
21
+ [2.2.0]: https://github.com/davishmcclurg/json_schemer/releases/tag/v2.2.0
22
+
23
+ ## [2.1.1] - 2023-11-28
24
+
25
+ ### Bug Fixes
26
+
27
+ - Fix refs to/through keyword objects: https://github.com/davishmcclurg/json_schemer/pull/160
28
+ - Temporary fix for incorrect `uri-reference` format in OpenAPI 3.x: https://github.com/davishmcclurg/json_schemer/pull/161
29
+
30
+ [2.1.1]: https://github.com/davishmcclurg/json_schemer/releases/tag/v2.1.1
31
+
32
+ ## [2.1.0] - 2023-11-17
33
+
34
+ ### Bug Fixes
35
+
36
+ - Limit anyOf/oneOf discriminator to listed refs: https://github.com/davishmcclurg/json_schemer/pull/145
37
+ - Require discriminator `propertyName` property: https://github.com/davishmcclurg/json_schemer/pull/145
38
+ - Support `Schema#ref` in subschemas: https://github.com/davishmcclurg/json_schemer/pull/145
39
+ - Resolve JSON pointer refs using correct base URI: https://github.com/davishmcclurg/json_schemer/pull/147
40
+ - `date` format in OpenAPI 3.0: https://github.com/davishmcclurg/json_schemer/commit/69fe7a815ecf0cfb1c40ac402bf46a789c05e972
41
+
42
+ ### Features
43
+
44
+ - Custom error messages with `x-error` keyword and I18n: https://github.com/davishmcclurg/json_schemer/pull/149
45
+ - Custom content encodings and media types: https://github.com/davishmcclurg/json_schemer/pull/148
46
+
47
+ [2.1.0]: https://github.com/davishmcclurg/json_schemer/releases/tag/v2.1.0
48
+
49
+ ## [2.0.0] - 2023-08-20
50
+
51
+ 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.
52
+
53
+ ### Breaking Changes
54
+
55
+ - The default meta schema is now Draft 2020-12. Other meta schemas can be specified using `meta_schema`.
56
+ - 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"`.
57
+ - 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.
58
+ - `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).
59
+ - Error output
60
+ - Special characters in `schema_pointer` are no longer percent encoded (eg, `definitions/foo\"bar` instead of `/definitions/foo%22bar`)
61
+ - Keyword validation order changed so errors may be returned in a different order (eg, `items` errors before `contains`).
62
+ - Array `dependencies` return `"type": "dependencies"` errors instead of `"required"` and point to the schema that contains the `dependencies` keyword.
63
+ - `not` errors point to the schema that contains the `not` keyword (instead of the schema defined by the `not` keyword).
64
+ - Custom keyword errors are now always wrapped in regular error hashes. Returned strings are used to set `type`:
65
+ ```
66
+ >> JSONSchemer.schema({ 'x' => 'y' }, :keywords => { 'x' => proc { false } }).validate({}).to_a
67
+ => [{"data"=>{}, "data_pointer"=>"", "schema"=>{"x"=>"y"}, "schema_pointer"=>"", "root_schema"=>{"x"=>"y"}, "type"=>"x"}]
68
+ >> JSONSchemer.schema({ 'x' => 'y' }, :keywords => { 'x' => proc { 'wrong!' } }).validate({}).to_a
69
+ => [{"data"=>{}, "data_pointer"=>"", "schema"=>{"x"=>"y"}, "schema_pointer"=>"", "root_schema"=>{"x"=>"y"}, "type"=>"wrong!"}]
70
+ ```
71
+
72
+ [2.0.0]: https://github.com/davishmcclurg/json_schemer/releases/tag/v2.0.0
73
+
74
+ ## [1.0.0] - 2023-05-26
75
+
76
+ ### Breaking Changes
77
+
78
+ - Ruby 2.4 is no longer supported.
79
+ - The default `regexp_resolver` is now `ruby`, which passes patterns directly to `Regexp`. The previous default, `ecma`, rewrites patterns to behave more like Javascript (ECMA-262) regular expressions:
80
+ - Beginning of string: `^` -> `\A`
81
+ - End of string: `$` -> `\z`
82
+ - Space: `\s` -> `[\t\r\n\f\v\uFEFF\u2029\p{Zs}]`
83
+ - Non-space: `\S` -> `[^\t\r\n\f\v\uFEFF\u2029\p{Zs}]`
84
+ - Invalid ECMA-262 regular expressions raise `JSONSchemer::InvalidEcmaRegexp` when `regexp_resolver` is set to `ecma`.
85
+ - Embedded subschemas (ie, subschemas referenced by `$id`) can only be found under "known" keywords (eg, `definitions`). Previously, the entire schema object was scanned for `$id`.
86
+ - Empty fragments are now removed from `$ref` URIs before calling `ref_resolver`.
87
+ - Refs that are fragment-only JSON pointers with special characters must use the proper encoding (eg, `"$ref": "#/definitions/some-%7Bid%7D"`).
88
+
89
+ [1.0.0]: https://github.com/davishmcclurg/json_schemer/releases/tag/v1.0.0
data/Gemfile.lock CHANGED
@@ -1,31 +1,56 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- json_schemer (0.2.18)
5
- ecma-re-validator (~> 0.3)
4
+ json_schemer (2.2.0)
5
+ base64
6
+ bigdecimal
6
7
  hana (~> 1.3)
7
8
  regexp_parser (~> 2.0)
8
- uri_template (~> 0.7)
9
+ simpleidn (~> 0.2)
9
10
 
10
11
  GEM
11
12
  remote: https://rubygems.org/
12
13
  specs:
13
- ecma-re-validator (0.3.0)
14
- regexp_parser (~> 2.0)
14
+ base64 (0.2.0)
15
+ bigdecimal (3.1.6)
16
+ bigdecimal (3.1.6-java)
17
+ concurrent-ruby (1.2.2)
18
+ csv (3.2.8)
19
+ docile (1.4.0)
15
20
  hana (1.3.7)
16
- minitest (5.14.3)
17
- rake (13.0.1)
18
- regexp_parser (2.1.1)
19
- uri_template (0.7.0)
21
+ i18n (1.14.1)
22
+ concurrent-ruby (~> 1.0)
23
+ i18n-debug (1.2.0)
24
+ i18n (< 2)
25
+ minitest (5.15.0)
26
+ rake (13.1.0)
27
+ regexp_parser (2.9.0)
28
+ simplecov (0.22.0)
29
+ docile (~> 1.1)
30
+ simplecov-html (~> 0.11)
31
+ simplecov_json_formatter (~> 0.1)
32
+ simplecov-html (0.12.3)
33
+ simplecov_json_formatter (0.1.4)
34
+ simpleidn (0.2.1)
35
+ unf (~> 0.1.4)
36
+ unf (0.1.4)
37
+ unf_ext
38
+ unf (0.1.4-java)
39
+ unf_ext (0.0.9.1)
20
40
 
21
41
  PLATFORMS
42
+ java
22
43
  ruby
23
44
 
24
45
  DEPENDENCIES
25
46
  bundler (~> 2.0)
47
+ csv
48
+ i18n
49
+ i18n-debug
26
50
  json_schemer!
27
51
  minitest (~> 5.0)
28
52
  rake (~> 13.0)
53
+ simplecov (~> 0.22)
29
54
 
30
55
  BUNDLED WITH
31
- 2.2.11
56
+ 2.3.27
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
 
@@ -45,7 +45,13 @@ schemer.valid?({ 'abc' => 10 })
45
45
  # error validation (`validate` returns an enumerator)
46
46
 
47
47
  schemer.validate({ 'abc' => 10 }).to_a
48
- # => [{"data"=>10, "schema"=>{"type"=>"integer", "minimum"=>11}, "pointer"=>"#/abc", "type"=>"minimum"}]
48
+ # => [{"data"=>10,
49
+ # "data_pointer"=>"/abc",
50
+ # "schema"=>{"type"=>"integer", "minimum"=>11},
51
+ # "schema_pointer"=>"/properties/abc",
52
+ # "root_schema"=>{"type"=>"object", "properties"=>{"abc"=>{"type"=>"integer", "minimum"=>11}}},
53
+ # "type"=>"minimum",
54
+ # "error"=>"number at `/abc` is less than: 11"}]
49
55
 
50
56
  # default property values
51
57
 
@@ -74,6 +80,80 @@ schemer = JSONSchemer.schema(schema)
74
80
 
75
81
  schema = '{ "type": "integer" }'
76
82
  schemer = JSONSchemer.schema(schema)
83
+
84
+ # schema validation
85
+
86
+ JSONSchemer.valid_schema?({ '$id' => 'valid' })
87
+ # => true
88
+
89
+ JSONSchemer.validate_schema({ '$id' => '#invalid' }).to_a
90
+ # => [{"data"=>"#invalid",
91
+ # "data_pointer"=>"/$id",
92
+ # "schema"=>{"$ref"=>"#/$defs/uriReferenceString", "$comment"=>"Non-empty fragments not allowed.", "pattern"=>"^[^#]*#?$"},
93
+ # "schema_pointer"=>"/properties/$id",
94
+ # "root_schema"=>{...meta schema},
95
+ # "type"=>"pattern",
96
+ # "error"=>"string at `/$id` does not match pattern: ^[^#]*#?$"}]
97
+
98
+ JSONSchemer.schema({ '$id' => 'valid' }).valid_schema?
99
+ # => true
100
+
101
+ JSONSchemer.schema({ '$id' => '#invalid' }).validate_schema.to_a
102
+ # => [{"data"=>"#invalid",
103
+ # "data_pointer"=>"/$id",
104
+ # "schema"=>{"$ref"=>"#/$defs/uriReferenceString", "$comment"=>"Non-empty fragments not allowed.", "pattern"=>"^[^#]*#?$"},
105
+ # "schema_pointer"=>"/properties/$id",
106
+ # "root_schema"=>{...meta schema},
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"}}}
77
157
  ```
78
158
 
79
159
  ## Options
@@ -82,13 +162,58 @@ schemer = JSONSchemer.schema(schema)
82
162
  JSONSchemer.schema(
83
163
  schema,
84
164
 
85
- # 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)
86
179
  # true/false
87
180
  # default: true
88
181
  format: true,
89
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
+
90
214
  # insert default property values during validation
91
- # true/false
215
+ # string keys by default (use `:symbol` to insert symbol keys)
216
+ # true/false/:symbol
92
217
  # default: false
93
218
  insert_property_defaults: true,
94
219
 
@@ -110,10 +235,275 @@ JSONSchemer.schema(
110
235
  # 'net/http'/proc/lambda/respond_to?(:call)
111
236
  # 'net/http': proc { |uri| JSON.parse(Net::HTTP.get(uri)) }
112
237
  # default: proc { |uri| raise UnknownRef, uri.to_s }
113
- ref_resolver: 'net/http'
238
+ ref_resolver: 'net/http',
239
+
240
+ # use different method to match regexes
241
+ # 'ruby'/'ecma'/proc/lambda/respond_to?(:call)
242
+ # 'ruby': proc { |pattern| Regexp.new(pattern) }
243
+ # default: 'ruby'
244
+ regexp_resolver: proc do |pattern|
245
+ RE2::Regexp.new(pattern)
246
+ end,
247
+
248
+ # output formatting (https://json-schema.org/draft/2020-12/json-schema-core.html#section-12)
249
+ # 'classic'/'flag'/'basic'/'detailed'/'verbose'
250
+ # default: 'classic'
251
+ output_format: 'basic',
252
+
253
+ # validate `readOnly`/`writeOnly` keywords (https://spec.openapis.org/oas/v3.0.3#fixed-fields-19)
254
+ # 'read'/'write'/nil
255
+ # default: nil
256
+ access_mode: 'read'
114
257
  )
115
258
  ```
116
259
 
260
+ ## Global Configuration
261
+
262
+ Configuration options can be set globally by modifying `JSONSchemer.configuration`. Global options are applied to any new schemas at creation time (global configuration changes are not reflected in existing schemas). They can be overridden with the regular keyword arguments described [above](#options).
263
+
264
+ ```ruby
265
+ # configuration block
266
+ JSONSchemer.configure do |config|
267
+ config.regexp_resolver = 'ecma'
268
+ end
269
+
270
+ # configuration accessors
271
+ JSONSchemer.configuration.insert_property_defaults = true
272
+ ```
273
+
274
+ ## Custom Error Messages
275
+
276
+ 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.
277
+
278
+ ### `x-error` Keyword
279
+
280
+ ```ruby
281
+ # override all errors for a schema
282
+ schemer = JSONSchemer.schema({
283
+ 'type' => 'string',
284
+ 'x-error' => 'custom error for schema and all keywords'
285
+ })
286
+
287
+ schemer.validate(1).first
288
+ # => {"data"=>1,
289
+ # "data_pointer"=>"",
290
+ # "schema"=>{"type"=>"string", "x-error"=>"custom error for schema and all keywords"},
291
+ # "schema_pointer"=>"",
292
+ # "root_schema"=>{"type"=>"string", "x-error"=>"custom error for schema and all keywords"},
293
+ # "type"=>"string",
294
+ # "error"=>"custom error for schema and all keywords",
295
+ # "x-error"=>true}
296
+
297
+ schemer.validate(1, :output_format => 'basic')
298
+ # => {"valid"=>false,
299
+ # "keywordLocation"=>"",
300
+ # "absoluteKeywordLocation"=>"json-schemer://schema#",
301
+ # "instanceLocation"=>"",
302
+ # "error"=>"custom error for schema and all keywords",
303
+ # "x-error"=>true,
304
+ # "errors"=>#<Enumerator: ...>}
305
+
306
+ # keyword-specific errors
307
+ schemer = JSONSchemer.schema({
308
+ 'type' => 'string',
309
+ 'minLength' => 10,
310
+ 'x-error' => {
311
+ 'type' => 'custom error for `type` keyword',
312
+ # special `^` keyword for schema-level error
313
+ '^' => 'custom error for schema',
314
+ # same behavior as when `x-error` is a string
315
+ '*' => 'fallback error for schema and all keywords'
316
+ }
317
+ })
318
+
319
+ schemer.validate(1).map { _1.fetch('error') }
320
+ # => ["custom error for `type` keyword"]
321
+
322
+ schemer.validate('1').map { _1.fetch('error') }
323
+ # => ["custom error for schema and all keywords"]
324
+
325
+ schemer.validate(1, :output_format => 'basic').fetch('error')
326
+ # => "custom error for schema"
327
+
328
+ # variable interpolation (instance/instanceLocation/keywordLocation/absoluteKeywordLocation)
329
+ schemer = JSONSchemer.schema({
330
+ '$id' => 'https://example.com/schema',
331
+ 'properties' => {
332
+ 'abc' => {
333
+ 'type' => 'string',
334
+ 'x-error' => <<~ERROR
335
+ instance: %{instance}
336
+ instance location: %{instanceLocation}
337
+ keyword location: %{keywordLocation}
338
+ absolute keyword location: %{absoluteKeywordLocation}
339
+ ERROR
340
+ }
341
+ }
342
+ })
343
+
344
+ puts schemer.validate({ 'abc' => 1 }).first.fetch('error')
345
+ # instance: 1
346
+ # instance location: /abc
347
+ # keyword location: /properties/abc/type
348
+ # absolute keyword location: https://example.com/schema#/properties/abc/type
349
+ ```
350
+
351
+ ### I18n
352
+
353
+ 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.
354
+
355
+ Translation keys are looked up in this order:
356
+
357
+ 1. `$LOCALE.json_schemer.errors.$ABSOLUTE_KEYWORD_LOCATION`
358
+ 2. `$LOCALE.json_schemer.errors.$SCHEMA_ID.$KEYWORD_LOCATION`
359
+ 3. `$LOCALE.json_schemer.errors.$KEYWORD_LOCATION`
360
+ 4. `$LOCALE.json_schemer.errors.$SCHEMA_ID.$KEYWORD`
361
+ 5. `$LOCALE.json_schemer.errors.$SCHEMA_ID.*`
362
+ 6. `$LOCALE.json_schemer.errors.$META_SCHEMA_ID.$KEYWORD`
363
+ 7. `$LOCALE.json_schemer.errors.$META_SCHEMA_ID.*`
364
+ 8. `$LOCALE.json_schemer.errors.$KEYWORD`
365
+ 9. `$LOCALE.json_schemer.errors.*`
366
+
367
+ Example translations file:
368
+
369
+ ```yaml
370
+ en:
371
+ json_schemer:
372
+ errors:
373
+ 'https://example.com/schema#/properties/abc/type': custom error for absolute keyword location
374
+ 'https://example.com/schema':
375
+ '#/properties/abc/type': custom error for keyword location, nested under schema $id
376
+ 'type': custom error for `type` keyword, nested under schema $id
377
+ '^': custom error for schema, nested under schema $id
378
+ '*': fallback error for schema and all keywords, nested under schema $id
379
+ '#/properties/abc/type': custom error for keyword location
380
+ 'http://json-schema.org/draft-07/schema#':
381
+ 'type': custom error for `type` keyword, nested under meta-schema $id ($schema)
382
+ '^': custom error for schema, nested under meta-schema $id
383
+ '*': fallback error for schema and all keywords, nested under meta-schema $id ($schema)
384
+ 'type': custom error for `type` keyword
385
+ '^': custom error for schema
386
+ # variable interpolation (instance/instanceLocation/keywordLocation/absoluteKeywordLocation)
387
+ '*': |
388
+ fallback error for schema and all keywords
389
+ instance: %{instance}
390
+ instance location: %{instanceLocation}
391
+ keyword location: %{keywordLocation}
392
+ absolute keyword location: %{absoluteKeywordLocation}
393
+ ```
394
+
395
+ And output:
396
+
397
+ ```ruby
398
+ require 'i18n'
399
+ I18n.locale = :en # $LOCALE=en
400
+
401
+ schemer = JSONSchemer.schema({
402
+ '$id' => 'https://example.com/schema', # $SCHEMA_ID=https://example.com/schema
403
+ '$schema' => 'http://json-schema.org/draft-07/schema#', # $META_SCHEMA_ID=http://json-schema.org/draft-07/schema#
404
+ 'properties' => {
405
+ 'abc' => {
406
+ 'type' => 'integer' # $KEYWORD=type
407
+ } # $KEYWORD_LOCATION=#/properties/abc/type
408
+ } # $ABSOLUTE_KEYWORD_LOCATION=https://example.com/schema#/properties/abc/type
409
+ })
410
+
411
+ schemer.validate({ 'abc' => 'not-an-integer' }).first
412
+ # => {"data"=>"not-an-integer",
413
+ # "data_pointer"=>"/abc",
414
+ # "schema"=>{"type"=>"integer"},
415
+ # "schema_pointer"=>"/properties/abc",
416
+ # "root_schema"=>{"$id"=>"https://example.com/schema", "$schema"=>"http://json-schema.org/draft-07/schema#", "properties"=>{"abc"=>{"type"=>"integer"}}},
417
+ # "type"=>"integer",
418
+ # "error"=>"custom error for absolute keyword location",
419
+ # "i18n"=>true
420
+ ```
421
+
422
+ In the example above, custom error messsages are looked up using the following keys (in order until one is found):
423
+
424
+ 1. `en.json_schemer.errors.'https://example.com/schema#/properties/abc/type'`
425
+ 2. `en.json_schemer.errors.'https://example.com/schema'.'#/properties/abc/type'`
426
+ 3. `en.json_schemer.errors.'#/properties/abc/type'`
427
+ 4. `en.json_schemer.errors.'https://example.com/schema'.type`
428
+ 5. `en.json_schemer.errors.'https://example.com/schema'.*`
429
+ 6. `en.json_schemer.errors.'http://json-schema.org/draft-07/schema#'.type`
430
+ 7. `en.json_schemer.errors.'http://json-schema.org/draft-07/schema#'.*`
431
+ 8. `en.json_schemer.errors.type`
432
+ 9. `en.json_schemer.errors.*`
433
+
434
+ ## OpenAPI
435
+
436
+ ```ruby
437
+ document = JSONSchemer.openapi({
438
+ 'openapi' => '3.1.0',
439
+ 'info' => {
440
+ 'title' => 'example'
441
+ },
442
+ 'components' => {
443
+ 'schemas' => {
444
+ 'example' => {
445
+ 'type' => 'integer'
446
+ }
447
+ }
448
+ }
449
+ })
450
+
451
+ # document validation using meta schema
452
+
453
+ document.valid?
454
+ # => false
455
+
456
+ document.validate.to_a
457
+ # => [{"data"=>{"title"=>"example"},
458
+ # "data_pointer"=>"/info",
459
+ # "schema"=>{...info schema},
460
+ # "schema_pointer"=>"/$defs/info",
461
+ # "root_schema"=>{...meta schema},
462
+ # "type"=>"required",
463
+ # "details"=>{"missing_keys"=>["version"]}},
464
+ # ...]
465
+
466
+ # data validation using schema by name (in `components/schemas`)
467
+
468
+ document.schema('example').valid?(1)
469
+ # => true
470
+
471
+ document.schema('example').valid?('one')
472
+ # => false
473
+
474
+ # data validation using schema by ref
475
+
476
+ document.ref('#/components/schemas/example').valid?(1)
477
+ # => true
478
+
479
+ document.ref('#/components/schemas/example').valid?('one')
480
+ # => false
481
+ ```
482
+
483
+ ## CLI
484
+
485
+ 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.
486
+
487
+ Validation errors are output as single-line JSON objects. The `--errors` option can be used to limit the number of errors returned or prevent output entirely (and fail fast).
488
+
489
+ The schema or data can also be read from stdin using `-`.
490
+
491
+ ```
492
+ % json_schemer --help
493
+ Usage:
494
+ json_schemer [options] <schema> <data>...
495
+ json_schemer [options] <schema> -
496
+ json_schemer [options] - <data>...
497
+ json_schemer -h | --help
498
+ json_schemer --version
499
+
500
+ Options:
501
+ -e, --errors MAX Maximum number of errors to output
502
+ Use "0" to validate with no output
503
+ -h, --help Show help
504
+ -v, --version Show version
505
+ ```
506
+
117
507
  ## Development
118
508
 
119
509
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.