json_schemer 1.0.3 → 2.4.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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +7 -8
  3. data/CHANGELOG.md +96 -0
  4. data/Gemfile.lock +23 -14
  5. data/README.md +343 -20
  6. data/json_schemer.gemspec +8 -3
  7. data/lib/json_schemer/configuration.rb +31 -0
  8. data/lib/json_schemer/content.rb +18 -0
  9. data/lib/json_schemer/draft201909/meta.rb +320 -0
  10. data/lib/json_schemer/draft201909/vocab/applicator.rb +104 -0
  11. data/lib/json_schemer/draft201909/vocab/core.rb +45 -0
  12. data/lib/json_schemer/draft201909/vocab.rb +31 -0
  13. data/lib/json_schemer/draft202012/meta.rb +364 -0
  14. data/lib/json_schemer/draft202012/vocab/applicator.rb +382 -0
  15. data/lib/json_schemer/draft202012/vocab/content.rb +52 -0
  16. data/lib/json_schemer/draft202012/vocab/core.rb +160 -0
  17. data/lib/json_schemer/draft202012/vocab/format_annotation.rb +23 -0
  18. data/lib/json_schemer/draft202012/vocab/format_assertion.rb +23 -0
  19. data/lib/json_schemer/draft202012/vocab/meta_data.rb +30 -0
  20. data/lib/json_schemer/draft202012/vocab/unevaluated.rb +104 -0
  21. data/lib/json_schemer/draft202012/vocab/validation.rb +290 -0
  22. data/lib/json_schemer/draft202012/vocab.rb +105 -0
  23. data/lib/json_schemer/draft4/meta.rb +161 -0
  24. data/lib/json_schemer/draft4/vocab/validation.rb +38 -0
  25. data/lib/json_schemer/draft4/vocab.rb +18 -0
  26. data/lib/json_schemer/draft6/meta.rb +172 -0
  27. data/lib/json_schemer/draft6/vocab.rb +16 -0
  28. data/lib/json_schemer/draft7/meta.rb +183 -0
  29. data/lib/json_schemer/draft7/vocab/validation.rb +69 -0
  30. data/lib/json_schemer/draft7/vocab.rb +30 -0
  31. data/lib/json_schemer/errors.rb +1 -0
  32. data/lib/json_schemer/format/duration.rb +23 -0
  33. data/lib/json_schemer/format/json_pointer.rb +18 -0
  34. data/lib/json_schemer/format.rb +127 -106
  35. data/lib/json_schemer/keyword.rb +56 -0
  36. data/lib/json_schemer/location.rb +25 -0
  37. data/lib/json_schemer/openapi.rb +38 -0
  38. data/lib/json_schemer/openapi30/document.rb +1672 -0
  39. data/lib/json_schemer/openapi30/meta.rb +34 -0
  40. data/lib/json_schemer/openapi30/vocab/base.rb +18 -0
  41. data/lib/json_schemer/openapi30/vocab.rb +12 -0
  42. data/lib/json_schemer/openapi31/document.rb +1557 -0
  43. data/lib/json_schemer/openapi31/meta.rb +136 -0
  44. data/lib/json_schemer/openapi31/vocab/base.rb +127 -0
  45. data/lib/json_schemer/openapi31/vocab.rb +18 -0
  46. data/lib/json_schemer/output.rb +56 -0
  47. data/lib/json_schemer/resources.rb +24 -0
  48. data/lib/json_schemer/result.rb +242 -0
  49. data/lib/json_schemer/schema.rb +433 -0
  50. data/lib/json_schemer/version.rb +1 -1
  51. data/lib/json_schemer.rb +235 -32
  52. metadata +119 -18
  53. data/lib/json_schemer/schema/base.rb +0 -677
  54. data/lib/json_schemer/schema/draft4.json +0 -149
  55. data/lib/json_schemer/schema/draft4.rb +0 -44
  56. data/lib/json_schemer/schema/draft6.json +0 -155
  57. data/lib/json_schemer/schema/draft6.rb +0 -25
  58. data/lib/json_schemer/schema/draft7.json +0 -172
  59. 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: 04dbbf738ea9253437566fb50075ee7603fd0b536324181aebb0573c32c7f7bc
4
+ data.tar.gz: 29337d5892b33a2f74e435370986f059d7dd9762352bea610e7668dca3171e09
5
5
  SHA512:
6
- metadata.gz: 87f3d572f430bc5b64f25ffc96bf5fcf979fa53eea9e0d325e6d0b853b4f653d2264e151ab312da4e472a02342eb162e9ffdfa3b06c3ec29fa09e39123412f3a
7
- data.tar.gz: fbbed8f04fea519b2264075051e64a7f6af2ade9b92600a490a2291065ed9a13eb15fb73cf356a0caff5ea3f63065d4c7a50ff93b293441273ed98708429cfe8
6
+ metadata.gz: 90f1510bcc40ad9500904453dee34434b91ef45b8ab7d8be84dc728aae4d4c1a4556037f1ae8b48595198529973fe2d1e5c20a64e5aeb9871cd6fddd23636268
7
+ data.tar.gz: c7e8a861d8f763d5248d79d486d8b253b9f81e40952e896d53b7dad84bdd5197a70224008e79de5cf5b6e7a3e9a8e646bd8af4188ae6a87c4a7ac9011998f36f
@@ -6,22 +6,21 @@ jobs:
6
6
  fail-fast: false
7
7
  matrix:
8
8
  os: [ubuntu-latest, windows-latest, macos-latest]
9
- ruby: [2.5, 2.6, 2.7, 3.0, 3.1, 3.2, head, jruby, jruby-head, truffleruby, truffleruby-head]
9
+ ruby: [2.7, 3.0, 3.1, 3.2, 3.3, 3.4, head, jruby, jruby-head, truffleruby, truffleruby-head]
10
10
  exclude:
11
+ - os: ubuntu-latest
12
+ ruby: head
13
+ - os: macos-latest
14
+ ruby: head
11
15
  - os: windows-latest
12
16
  ruby: truffleruby
13
17
  - os: windows-latest
14
18
  ruby: truffleruby-head
15
19
  runs-on: ${{ matrix.os }}
16
20
  steps:
17
- - uses: actions/checkout@v2
21
+ - uses: actions/checkout@v4
18
22
  - uses: ruby/setup-ruby@v1
19
23
  with:
20
24
  ruby-version: ${{ matrix.ruby }}
21
25
  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
26
+ - run: bin/rake test
data/CHANGELOG.md CHANGED
@@ -1,5 +1,101 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.4.0] - 2025-02-01
4
+
5
+ ### Bug Fixes
6
+
7
+ - Store schema resource file URIs as strings to prevent conflicts: https://github.com/davishmcclurg/json_schemer/pull/189
8
+ - Require OpenAPI `discriminator` instances to be objects: https://github.com/davishmcclurg/json_schemer/pull/206
9
+ - Pass configuration options to subschemas: https://github.com/davishmcclurg/json_schemer/pull/208
10
+ - Check applicable instance types in OpenAPI `format` extensions: https://github.com/davishmcclurg/json_schemer/pull/209
11
+ - Use correct max values for OpenAPI `int32`/`int64` formats: https://github.com/davishmcclurg/json_schemer/commit/386c2a6fe089350c61775716643ef0600898060e
12
+
13
+ [2.4.0]: https://github.com/davishmcclurg/json_schemer/releases/tag/v2.4.0
14
+
15
+ ## [2.3.0] - 2024-05-30
16
+
17
+ ### Ruby Versions
18
+
19
+ - Ruby 2.5 and 2.6 are no longer supported.
20
+
21
+ ### Bug Fixes
22
+
23
+ - Remove `base64` runtime dependency: https://github.com/davishmcclurg/json_schemer/pull/182
24
+ - Relax `uuid` format validation: https://github.com/davishmcclurg/json_schemer/pull/183
25
+
26
+ [2.3.0]: https://github.com/davishmcclurg/json_schemer/releases/tag/v2.3.0
27
+
28
+ ## [2.2.0] - 2024-03-02
29
+
30
+ ### Bug Fixes
31
+
32
+ - Support symbol keys when accessing original instance: https://github.com/davishmcclurg/json_schemer/commit/d52c130e9967919c6cf1c9dbc3f0babfb8b01cf8
33
+ - Support custom keywords in nested schemas: https://github.com/davishmcclurg/json_schemer/commit/93c85a5006981347c7e9a4c11b73c6bdb65d8ba2
34
+ - Stringify instance location for custom keywords: https://github.com/davishmcclurg/json_schemer/commit/513c99130b9e7986b09881e7efd3fb7143744754
35
+ - Reduce unhelpful error output in `unevaluated` keywords: https://github.com/davishmcclurg/json_schemer/pull/164
36
+ - Handle parse errors during schema validation: https://github.com/davishmcclurg/json_schemer/pull/171
37
+ - Follow refs when finding default property values: https://github.com/davishmcclurg/json_schemer/pull/175
38
+
39
+ ### Features
40
+
41
+ - Global configuration with `Configuration` object: https://github.com/davishmcclurg/json_schemer/pull/170
42
+ - Symbol key property defaults with `insert_property_defaults: :symbol`: https://github.com/davishmcclurg/json_schemer/commit/a72473dc84199107ddedc8998950e5b82273232a
43
+ - Consistent schema type support for schema validation methods: https://github.com/davishmcclurg/json_schemer/commit/bbcd0cea20cbaa61cf2bdae5f53840861cae54b8
44
+ - Validation option support for schema validation methods: https://github.com/davishmcclurg/json_schemer/commit/2eeef77de522f127619b7d0faa51e0d7e40977ad
45
+
46
+ [2.2.0]: https://github.com/davishmcclurg/json_schemer/releases/tag/v2.2.0
47
+
48
+ ## [2.1.1] - 2023-11-28
49
+
50
+ ### Bug Fixes
51
+
52
+ - Fix refs to/through keyword objects: https://github.com/davishmcclurg/json_schemer/pull/160
53
+ - Temporary fix for incorrect `uri-reference` format in OpenAPI 3.x: https://github.com/davishmcclurg/json_schemer/pull/161
54
+
55
+ [2.1.1]: https://github.com/davishmcclurg/json_schemer/releases/tag/v2.1.1
56
+
57
+ ## [2.1.0] - 2023-11-17
58
+
59
+ ### Bug Fixes
60
+
61
+ - Limit anyOf/oneOf discriminator to listed refs: https://github.com/davishmcclurg/json_schemer/pull/145
62
+ - Require discriminator `propertyName` property: https://github.com/davishmcclurg/json_schemer/pull/145
63
+ - Support `Schema#ref` in subschemas: https://github.com/davishmcclurg/json_schemer/pull/145
64
+ - Resolve JSON pointer refs using correct base URI: https://github.com/davishmcclurg/json_schemer/pull/147
65
+ - `date` format in OpenAPI 3.0: https://github.com/davishmcclurg/json_schemer/commit/69fe7a815ecf0cfb1c40ac402bf46a789c05e972
66
+
67
+ ### Features
68
+
69
+ - Custom error messages with `x-error` keyword and I18n: https://github.com/davishmcclurg/json_schemer/pull/149
70
+ - Custom content encodings and media types: https://github.com/davishmcclurg/json_schemer/pull/148
71
+
72
+ [2.1.0]: https://github.com/davishmcclurg/json_schemer/releases/tag/v2.1.0
73
+
74
+ ## [2.0.0] - 2023-08-20
75
+
76
+ 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.
77
+
78
+ ### Breaking Changes
79
+
80
+ - The default meta schema is now Draft 2020-12. Other meta schemas can be specified using `meta_schema`.
81
+ - 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"`.
82
+ - 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.
83
+ - `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).
84
+ - Error output
85
+ - Special characters in `schema_pointer` are no longer percent encoded (eg, `definitions/foo\"bar` instead of `/definitions/foo%22bar`)
86
+ - Keyword validation order changed so errors may be returned in a different order (eg, `items` errors before `contains`).
87
+ - Array `dependencies` return `"type": "dependencies"` errors instead of `"required"` and point to the schema that contains the `dependencies` keyword.
88
+ - `not` errors point to the schema that contains the `not` keyword (instead of the schema defined by the `not` keyword).
89
+ - Custom keyword errors are now always wrapped in regular error hashes. Returned strings are used to set `type`:
90
+ ```
91
+ >> JSONSchemer.schema({ 'x' => 'y' }, :keywords => { 'x' => proc { false } }).validate({}).to_a
92
+ => [{"data"=>{}, "data_pointer"=>"", "schema"=>{"x"=>"y"}, "schema_pointer"=>"", "root_schema"=>{"x"=>"y"}, "type"=>"x"}]
93
+ >> JSONSchemer.schema({ 'x' => 'y' }, :keywords => { 'x' => proc { 'wrong!' } }).validate({}).to_a
94
+ => [{"data"=>{}, "data_pointer"=>"", "schema"=>{"x"=>"y"}, "schema_pointer"=>"", "root_schema"=>{"x"=>"y"}, "type"=>"wrong!"}]
95
+ ```
96
+
97
+ [2.0.0]: https://github.com/davishmcclurg/json_schemer/releases/tag/v2.0.0
98
+
3
99
  ## [1.0.0] - 2023-05-26
4
100
 
5
101
  ### Breaking Changes
data/Gemfile.lock CHANGED
@@ -1,7 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- json_schemer (1.0.3)
4
+ json_schemer (2.4.0)
5
+ bigdecimal
5
6
  hana (~> 1.3)
6
7
  regexp_parser (~> 2.0)
7
8
  simpleidn (~> 0.2)
@@ -9,34 +10,42 @@ PATH
9
10
  GEM
10
11
  remote: https://rubygems.org/
11
12
  specs:
12
- docile (1.4.0)
13
+ base64 (0.2.0)
14
+ bigdecimal (3.1.9)
15
+ bigdecimal (3.1.9-java)
16
+ concurrent-ruby (1.3.5)
17
+ csv (3.3.2)
18
+ docile (1.4.1)
13
19
  hana (1.3.7)
14
- minitest (5.15.0)
15
- rake (13.0.6)
16
- regexp_parser (2.8.1)
20
+ i18n (1.14.6)
21
+ concurrent-ruby (~> 1.0)
22
+ i18n-debug (1.2.0)
23
+ i18n (< 2)
24
+ minitest (5.25.4)
25
+ rake (13.2.1)
26
+ regexp_parser (2.10.0)
17
27
  simplecov (0.22.0)
18
28
  docile (~> 1.1)
19
29
  simplecov-html (~> 0.11)
20
30
  simplecov_json_formatter (~> 0.1)
21
- simplecov-html (0.12.3)
31
+ simplecov-html (0.13.1)
22
32
  simplecov_json_formatter (0.1.4)
23
- simpleidn (0.2.1)
24
- unf (~> 0.1.4)
25
- unf (0.1.4)
26
- unf_ext
27
- unf (0.1.4-java)
28
- unf_ext (0.0.8.2)
33
+ simpleidn (0.2.3)
29
34
 
30
35
  PLATFORMS
31
36
  java
32
37
  ruby
33
38
 
34
39
  DEPENDENCIES
35
- bundler (~> 2.0)
40
+ base64
41
+ bundler (~> 2.4.0)
42
+ csv
43
+ i18n
44
+ i18n-debug
36
45
  json_schemer!
37
46
  minitest (~> 5.0)
38
47
  rake (~> 13.0)
39
48
  simplecov (~> 0.22)
40
49
 
41
50
  BUNDLED WITH
42
- 2.3.25
51
+ 2.4.22
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,65 @@ 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?
97
- # => true
98
+ # subschemas
98
99
 
99
- JSONSchemer.schema({ '$id' => nil }).validate_schema.to_a
100
- # => [{"data"=>nil,
101
- # "data_pointer"=>"/$id",
102
- # "schema"=>{"type"=>"string", "format"=>"uri-reference"},
103
- # "schema_pointer"=>"/properties/$id",
104
- # "root_schema"=>{...meta schema},
105
- # "type"=>"string"}]
100
+ schema = {
101
+ 'type' => 'integer',
102
+ '$defs' => {
103
+ 'foo' => {
104
+ 'type' => 'string'
105
+ }
106
+ }
107
+ }
108
+ schemer = JSONSchemer.schema(schema)
109
+
110
+ schemer.ref('#/$defs/foo').validate(1).to_a
111
+ # => [{"data"=>1,
112
+ # "data_pointer"=>"",
113
+ # "schema"=>{"type"=>"string"},
114
+ # "schema_pointer"=>"/$defs/foo",
115
+ # "root_schema"=>{"type"=>"integer", "$defs"=>{"foo"=>{"type"=>"string"}}},
116
+ # "type"=>"string",
117
+ # "error"=>"value at root is not a string"}]
118
+
119
+ # schema bundling (https://json-schema.org/draft/2020-12/json-schema-core.html#section-9.3)
120
+
121
+ schema = {
122
+ '$id' => 'http://example.com/schema',
123
+ 'allOf' => [
124
+ { '$ref' => 'schema/one' },
125
+ { '$ref' => 'schema/two' }
126
+ ]
127
+ }
128
+ refs = {
129
+ URI('http://example.com/schema/one') => {
130
+ 'type' => 'integer'
131
+ },
132
+ URI('http://example.com/schema/two') => {
133
+ 'minimum' => 11
134
+ }
135
+ }
136
+ schemer = JSONSchemer.schema(schema, :ref_resolver => refs.to_proc)
137
+
138
+ schemer.bundle
139
+ # => {"$id"=>"http://example.com/schema",
140
+ # "allOf"=>[{"$ref"=>"schema/one"}, {"$ref"=>"schema/two"}],
141
+ # "$schema"=>"https://json-schema.org/draft/2020-12/schema",
142
+ # "$defs"=>
143
+ # {"http://example.com/schema/one"=>{"type"=>"integer", "$id"=>"http://example.com/schema/one", "$schema"=>"https://json-schema.org/draft/2020-12/schema"},
144
+ # "http://example.com/schema/two"=>{"minimum"=>11, "$id"=>"http://example.com/schema/two", "$schema"=>"https://json-schema.org/draft/2020-12/schema"}}}
106
145
  ```
107
146
 
108
147
  ## Options
@@ -111,13 +150,58 @@ JSONSchemer.schema({ '$id' => nil }).validate_schema.to_a
111
150
  JSONSchemer.schema(
112
151
  schema,
113
152
 
114
- # validate `format` (https://tools.ietf.org/html/draft-handrews-json-schema-validation-00#section-7)
153
+ # meta schema to use for vocabularies (keyword behavior) and schema validation
154
+ # String/JSONSchemer::Schema
155
+ # 'https://json-schema.org/draft/2020-12/schema': JSONSchemer.draft202012
156
+ # 'https://json-schema.org/draft/2019-09/schema': JSONSchemer.draft201909
157
+ # 'http://json-schema.org/draft-07/schema#': JSONSchemer.draft7
158
+ # 'http://json-schema.org/draft-06/schema#': JSONSchemer.draft6
159
+ # 'http://json-schema.org/draft-04/schema#': JSONSchemer.draft4
160
+ # 'http://json-schema.org/schema#': JSONSchemer.draft4
161
+ # 'https://spec.openapis.org/oas/3.1/dialect/base': JSONSchemer.openapi31
162
+ # 'json-schemer://openapi30/schema': JSONSchemer.openapi30
163
+ # default: JSONSchemer.draft202012
164
+ meta_schema: 'https://json-schema.org/draft/2020-12/schema',
165
+
166
+ # validate `format` (https://json-schema.org/draft/2020-12/json-schema-validation.html#section-7)
115
167
  # true/false
116
168
  # default: true
117
169
  format: true,
118
170
 
171
+ # custom formats
172
+ formats: {
173
+ 'int32' => proc do |instance, _format|
174
+ instance.is_a?(Integer) && instance.bit_length <= 32
175
+ end,
176
+ # disable specific format
177
+ 'email' => false
178
+ },
179
+
180
+ # custom content encodings
181
+ # only `base64` is available by default
182
+ content_encodings: {
183
+ # return [success, annotation] tuple
184
+ 'urlsafe_base64' => proc do |instance|
185
+ [true, Base64.urlsafe_decode64(instance)]
186
+ rescue
187
+ [false, nil]
188
+ end
189
+ },
190
+
191
+ # custom content media types
192
+ # only `application/json` is available by default
193
+ content_media_types: {
194
+ # return [success, annotation] tuple
195
+ 'text/csv' => proc do |instance|
196
+ [true, CSV.parse(instance)]
197
+ rescue
198
+ [false, nil]
199
+ end
200
+ },
201
+
119
202
  # insert default property values during validation
120
- # true/false
203
+ # string keys by default (use `:symbol` to insert symbol keys)
204
+ # true/false/:symbol
121
205
  # default: false
122
206
  insert_property_defaults: true,
123
207
 
@@ -147,10 +231,243 @@ JSONSchemer.schema(
147
231
  # default: 'ruby'
148
232
  regexp_resolver: proc do |pattern|
149
233
  RE2::Regexp.new(pattern)
150
- end
234
+ end,
235
+
236
+ # output formatting (https://json-schema.org/draft/2020-12/json-schema-core.html#section-12)
237
+ # 'classic'/'flag'/'basic'/'detailed'/'verbose'
238
+ # default: 'classic'
239
+ output_format: 'basic',
240
+
241
+ # validate `readOnly`/`writeOnly` keywords (https://spec.openapis.org/oas/v3.0.3#fixed-fields-19)
242
+ # 'read'/'write'/nil
243
+ # default: nil
244
+ access_mode: 'read'
151
245
  )
152
246
  ```
153
247
 
248
+ ## Global Configuration
249
+
250
+ 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).
251
+
252
+ ```ruby
253
+ # configuration block
254
+ JSONSchemer.configure do |config|
255
+ config.regexp_resolver = 'ecma'
256
+ end
257
+
258
+ # configuration accessors
259
+ JSONSchemer.configuration.insert_property_defaults = true
260
+ ```
261
+
262
+ ## Custom Error Messages
263
+
264
+ 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.
265
+
266
+ ### `x-error` Keyword
267
+
268
+ ```ruby
269
+ # override all errors for a schema
270
+ schemer = JSONSchemer.schema({
271
+ 'type' => 'string',
272
+ 'x-error' => 'custom error for schema and all keywords'
273
+ })
274
+
275
+ schemer.validate(1).first
276
+ # => {"data"=>1,
277
+ # "data_pointer"=>"",
278
+ # "schema"=>{"type"=>"string", "x-error"=>"custom error for schema and all keywords"},
279
+ # "schema_pointer"=>"",
280
+ # "root_schema"=>{"type"=>"string", "x-error"=>"custom error for schema and all keywords"},
281
+ # "type"=>"string",
282
+ # "error"=>"custom error for schema and all keywords",
283
+ # "x-error"=>true}
284
+
285
+ schemer.validate(1, :output_format => 'basic')
286
+ # => {"valid"=>false,
287
+ # "keywordLocation"=>"",
288
+ # "absoluteKeywordLocation"=>"json-schemer://schema#",
289
+ # "instanceLocation"=>"",
290
+ # "error"=>"custom error for schema and all keywords",
291
+ # "x-error"=>true,
292
+ # "errors"=>#<Enumerator: ...>}
293
+
294
+ # keyword-specific errors
295
+ schemer = JSONSchemer.schema({
296
+ 'type' => 'string',
297
+ 'minLength' => 10,
298
+ 'x-error' => {
299
+ 'type' => 'custom error for `type` keyword',
300
+ # special `^` keyword for schema-level error
301
+ '^' => 'custom error for schema',
302
+ # same behavior as when `x-error` is a string
303
+ '*' => 'fallback error for schema and all keywords'
304
+ }
305
+ })
306
+
307
+ schemer.validate(1).map { _1.fetch('error') }
308
+ # => ["custom error for `type` keyword"]
309
+
310
+ schemer.validate('1').map { _1.fetch('error') }
311
+ # => ["custom error for schema and all keywords"]
312
+
313
+ schemer.validate(1, :output_format => 'basic').fetch('error')
314
+ # => "custom error for schema"
315
+
316
+ # variable interpolation (instance/instanceLocation/keywordLocation/absoluteKeywordLocation)
317
+ schemer = JSONSchemer.schema({
318
+ '$id' => 'https://example.com/schema',
319
+ 'properties' => {
320
+ 'abc' => {
321
+ 'type' => 'string',
322
+ 'x-error' => <<~ERROR
323
+ instance: %{instance}
324
+ instance location: %{instanceLocation}
325
+ keyword location: %{keywordLocation}
326
+ absolute keyword location: %{absoluteKeywordLocation}
327
+ ERROR
328
+ }
329
+ }
330
+ })
331
+
332
+ puts schemer.validate({ 'abc' => 1 }).first.fetch('error')
333
+ # instance: 1
334
+ # instance location: /abc
335
+ # keyword location: /properties/abc/type
336
+ # absolute keyword location: https://example.com/schema#/properties/abc/type
337
+ ```
338
+
339
+ ### I18n
340
+
341
+ 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.
342
+
343
+ Translation keys are looked up in this order:
344
+
345
+ 1. `$LOCALE.json_schemer.errors.$ABSOLUTE_KEYWORD_LOCATION`
346
+ 2. `$LOCALE.json_schemer.errors.$SCHEMA_ID.$KEYWORD_LOCATION`
347
+ 3. `$LOCALE.json_schemer.errors.$KEYWORD_LOCATION`
348
+ 4. `$LOCALE.json_schemer.errors.$SCHEMA_ID.$KEYWORD`
349
+ 5. `$LOCALE.json_schemer.errors.$SCHEMA_ID.*`
350
+ 6. `$LOCALE.json_schemer.errors.$META_SCHEMA_ID.$KEYWORD`
351
+ 7. `$LOCALE.json_schemer.errors.$META_SCHEMA_ID.*`
352
+ 8. `$LOCALE.json_schemer.errors.$KEYWORD`
353
+ 9. `$LOCALE.json_schemer.errors.*`
354
+
355
+ Example translations file:
356
+
357
+ ```yaml
358
+ en:
359
+ json_schemer:
360
+ errors:
361
+ 'https://example.com/schema#/properties/abc/type': custom error for absolute keyword location
362
+ 'https://example.com/schema':
363
+ '#/properties/abc/type': custom error for keyword location, nested under schema $id
364
+ 'type': custom error for `type` keyword, nested under schema $id
365
+ '^': custom error for schema, nested under schema $id
366
+ '*': fallback error for schema and all keywords, nested under schema $id
367
+ '#/properties/abc/type': custom error for keyword location
368
+ 'http://json-schema.org/draft-07/schema#':
369
+ 'type': custom error for `type` keyword, nested under meta-schema $id ($schema)
370
+ '^': custom error for schema, nested under meta-schema $id
371
+ '*': fallback error for schema and all keywords, nested under meta-schema $id ($schema)
372
+ 'type': custom error for `type` keyword
373
+ '^': custom error for schema
374
+ # variable interpolation (instance/instanceLocation/keywordLocation/absoluteKeywordLocation)
375
+ '*': |
376
+ fallback error for schema and all keywords
377
+ instance: %{instance}
378
+ instance location: %{instanceLocation}
379
+ keyword location: %{keywordLocation}
380
+ absolute keyword location: %{absoluteKeywordLocation}
381
+ ```
382
+
383
+ And output:
384
+
385
+ ```ruby
386
+ require 'i18n'
387
+ I18n.locale = :en # $LOCALE=en
388
+
389
+ schemer = JSONSchemer.schema({
390
+ '$id' => 'https://example.com/schema', # $SCHEMA_ID=https://example.com/schema
391
+ '$schema' => 'http://json-schema.org/draft-07/schema#', # $META_SCHEMA_ID=http://json-schema.org/draft-07/schema#
392
+ 'properties' => {
393
+ 'abc' => {
394
+ 'type' => 'integer' # $KEYWORD=type
395
+ } # $KEYWORD_LOCATION=#/properties/abc/type
396
+ } # $ABSOLUTE_KEYWORD_LOCATION=https://example.com/schema#/properties/abc/type
397
+ })
398
+
399
+ schemer.validate({ 'abc' => 'not-an-integer' }).first
400
+ # => {"data"=>"not-an-integer",
401
+ # "data_pointer"=>"/abc",
402
+ # "schema"=>{"type"=>"integer"},
403
+ # "schema_pointer"=>"/properties/abc",
404
+ # "root_schema"=>{"$id"=>"https://example.com/schema", "$schema"=>"http://json-schema.org/draft-07/schema#", "properties"=>{"abc"=>{"type"=>"integer"}}},
405
+ # "type"=>"integer",
406
+ # "error"=>"custom error for absolute keyword location",
407
+ # "i18n"=>true
408
+ ```
409
+
410
+ In the example above, custom error messsages are looked up using the following keys (in order until one is found):
411
+
412
+ 1. `en.json_schemer.errors.'https://example.com/schema#/properties/abc/type'`
413
+ 2. `en.json_schemer.errors.'https://example.com/schema'.'#/properties/abc/type'`
414
+ 3. `en.json_schemer.errors.'#/properties/abc/type'`
415
+ 4. `en.json_schemer.errors.'https://example.com/schema'.type`
416
+ 5. `en.json_schemer.errors.'https://example.com/schema'.*`
417
+ 6. `en.json_schemer.errors.'http://json-schema.org/draft-07/schema#'.type`
418
+ 7. `en.json_schemer.errors.'http://json-schema.org/draft-07/schema#'.*`
419
+ 8. `en.json_schemer.errors.type`
420
+ 9. `en.json_schemer.errors.*`
421
+
422
+ ## OpenAPI
423
+
424
+ ```ruby
425
+ document = JSONSchemer.openapi({
426
+ 'openapi' => '3.1.0',
427
+ 'info' => {
428
+ 'title' => 'example'
429
+ },
430
+ 'components' => {
431
+ 'schemas' => {
432
+ 'example' => {
433
+ 'type' => 'integer'
434
+ }
435
+ }
436
+ }
437
+ })
438
+
439
+ # document validation using meta schema
440
+
441
+ document.valid?
442
+ # => false
443
+
444
+ document.validate.to_a
445
+ # => [{"data"=>{"title"=>"example"},
446
+ # "data_pointer"=>"/info",
447
+ # "schema"=>{...info schema},
448
+ # "schema_pointer"=>"/$defs/info",
449
+ # "root_schema"=>{...meta schema},
450
+ # "type"=>"required",
451
+ # "details"=>{"missing_keys"=>["version"]}},
452
+ # ...]
453
+
454
+ # data validation using schema by name (in `components/schemas`)
455
+
456
+ document.schema('example').valid?(1)
457
+ # => true
458
+
459
+ document.schema('example').valid?('one')
460
+ # => false
461
+
462
+ # data validation using schema by ref
463
+
464
+ document.ref('#/components/schemas/example').valid?(1)
465
+ # => true
466
+
467
+ document.ref('#/components/schemas/example').valid?('one')
468
+ # => false
469
+ ```
470
+
154
471
  ## CLI
155
472
 
156
473
  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.
@@ -181,7 +498,13 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
181
498
 
182
499
  ## Build Status
183
500
 
184
- ![Build Status](https://github.com/davishmcclurg/json_schemer/workflows/ci/badge.svg)
501
+ ![CI](https://github.com/davishmcclurg/json_schemer/actions/workflows/ci.yml/badge.svg)
502
+ ![JSON Schema Versions](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fruby-json_schemer%2Fsupported_versions.json)<br>
503
+ ![Draft 2020-12](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fruby-json_schemer%2Fcompliance%2Fdraft2020-12.json)
504
+ ![Draft 2019-09](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fruby-json_schemer%2Fcompliance%2Fdraft2019-09.json)
505
+ ![Draft 7](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fruby-json_schemer%2Fcompliance%2Fdraft7.json)
506
+ ![Draft 6](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fruby-json_schemer%2Fcompliance%2Fdraft6.json)
507
+ ![Draft 4](https://img.shields.io/endpoint?url=https%3A%2F%2Fbowtie.report%2Fbadges%2Fruby-json_schemer%2Fcompliance%2Fdraft4.json)
185
508
 
186
509
  ## Contributing
187
510
 
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
 
@@ -20,13 +20,18 @@ Gem::Specification.new do |spec|
20
20
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
21
  spec.require_paths = ["lib"]
22
22
 
23
- spec.required_ruby_version = '>= 2.5'
23
+ spec.required_ruby_version = '>= 2.7'
24
24
 
25
- spec.add_development_dependency "bundler", "~> 2.0"
25
+ spec.add_development_dependency "base64"
26
+ spec.add_development_dependency "bundler", "~> 2.4.0"
26
27
  spec.add_development_dependency "rake", "~> 13.0"
27
28
  spec.add_development_dependency "minitest", "~> 5.0"
28
29
  spec.add_development_dependency "simplecov", "~> 0.22"
30
+ spec.add_development_dependency "csv"
31
+ spec.add_development_dependency "i18n"
32
+ spec.add_development_dependency "i18n-debug"
29
33
 
34
+ spec.add_runtime_dependency "bigdecimal"
30
35
  spec.add_runtime_dependency "hana", "~> 1.3"
31
36
  spec.add_runtime_dependency "regexp_parser", "~> 2.0"
32
37
  spec.add_runtime_dependency "simpleidn", "~> 0.2"