json_schemer 2.1.0 → 2.2.0

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: bd2081bcd9677b1615f3076f9c83171bba0f4a9c7030b94f175ea0b350b75b7c
4
- data.tar.gz: 920692383076ade224cb1b1f02f1579a824792c07333ebdee467596a0c3e3b6a
3
+ metadata.gz: 4013d2026a3ac04ca50649b4cf6437ea2ee477ed1e9d350942cfa7e56ead0828
4
+ data.tar.gz: 810f08388b21cc68d849181a87a820982c2fc7a577c402d5e05699b0c8d07507
5
5
  SHA512:
6
- metadata.gz: 84f674bd5560c44cbc752a18365cfbff341bb4e94a7cae43674d5591bb2229e0bd5d096346bca9e8ef54313c500496195ebabcb3d3100a39346e23be4d984f20
7
- data.tar.gz: 77a8391db7a554ec8ff93b155c77f0945984a194a3f64178d3f184fdbfc21806736f18788295579812ddc138c6b5a1dde977a16e8aa3b8c7edf7545c15eae3fa
6
+ metadata.gz: f4c5bb6fe5e6d4e0ccfd8abd28a0c12ae23254e74c807ba94278680bf7b706970276cef14f26eaf488f9c46ebd8d418b5aef90bf4abd886b9fd1d7513bb6d27a
7
+ data.tar.gz: 19a0c839189ba44d6688c389fa38b3f0b1ccc9986643471e2df4d50c347c5ce22ec3fa170002fc000fc877e05080842b57cc01351f46ff1e876ba972880e6584
@@ -6,7 +6,7 @@ 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.5, 2.6, 2.7, 3.0, 3.1, 3.2, 3.3, head, jruby, jruby-head, truffleruby, truffleruby-head]
10
10
  exclude:
11
11
  - os: windows-latest
12
12
  ruby: truffleruby
data/CHANGELOG.md CHANGED
@@ -1,6 +1,35 @@
1
1
  # Changelog
2
2
 
3
- ## [2.1.0] - XXXX-XX-XX
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
4
33
 
5
34
  ### Bug Fixes
6
35
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,9 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- json_schemer (2.1.0)
4
+ json_schemer (2.2.0)
5
+ base64
6
+ bigdecimal
5
7
  hana (~> 1.3)
6
8
  regexp_parser (~> 2.0)
7
9
  simpleidn (~> 0.2)
@@ -9,7 +11,11 @@ PATH
9
11
  GEM
10
12
  remote: https://rubygems.org/
11
13
  specs:
14
+ base64 (0.2.0)
15
+ bigdecimal (3.1.6)
16
+ bigdecimal (3.1.6-java)
12
17
  concurrent-ruby (1.2.2)
18
+ csv (3.2.8)
13
19
  docile (1.4.0)
14
20
  hana (1.3.7)
15
21
  i18n (1.14.1)
@@ -17,8 +23,8 @@ GEM
17
23
  i18n-debug (1.2.0)
18
24
  i18n (< 2)
19
25
  minitest (5.15.0)
20
- rake (13.0.6)
21
- regexp_parser (2.8.2)
26
+ rake (13.1.0)
27
+ regexp_parser (2.9.0)
22
28
  simplecov (0.22.0)
23
29
  docile (~> 1.1)
24
30
  simplecov-html (~> 0.11)
@@ -30,7 +36,7 @@ GEM
30
36
  unf (0.1.4)
31
37
  unf_ext
32
38
  unf (0.1.4-java)
33
- unf_ext (0.0.9)
39
+ unf_ext (0.0.9.1)
34
40
 
35
41
  PLATFORMS
36
42
  java
@@ -38,6 +44,7 @@ PLATFORMS
38
44
 
39
45
  DEPENDENCIES
40
46
  bundler (~> 2.0)
47
+ csv
41
48
  i18n
42
49
  i18n-debug
43
50
  json_schemer!
@@ -46,4 +53,4 @@ DEPENDENCIES
46
53
  simplecov (~> 0.22)
47
54
 
48
55
  BUNDLED WITH
49
- 2.3.25
56
+ 2.3.27
data/README.md CHANGED
@@ -212,7 +212,8 @@ JSONSchemer.schema(
212
212
  },
213
213
 
214
214
  # insert default property values during validation
215
- # true/false
215
+ # string keys by default (use `:symbol` to insert symbol keys)
216
+ # true/false/:symbol
216
217
  # default: false
217
218
  insert_property_defaults: true,
218
219
 
@@ -256,6 +257,20 @@ JSONSchemer.schema(
256
257
  )
257
258
  ```
258
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
+
259
274
  ## Custom Error Messages
260
275
 
261
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.
data/json_schemer.gemspec CHANGED
@@ -26,9 +26,12 @@ 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 "csv"
29
30
  spec.add_development_dependency "i18n"
30
31
  spec.add_development_dependency "i18n-debug"
31
32
 
33
+ spec.add_runtime_dependency "base64"
34
+ spec.add_runtime_dependency "bigdecimal"
32
35
  spec.add_runtime_dependency "hana", "~> 1.3"
33
36
  spec.add_runtime_dependency "regexp_parser", "~> 2.0"
34
37
  spec.add_runtime_dependency "simpleidn", "~> 0.2"
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JSONSchemer
4
+ Configuration = Struct.new(
5
+ :base_uri, :meta_schema, :vocabulary, :format, :formats, :content_encodings, :content_media_types, :keywords,
6
+ :before_property_validation, :after_property_validation, :insert_property_defaults, :property_default_resolver,
7
+ :ref_resolver, :regexp_resolver, :output_format, :resolve_enumerators, :access_mode,
8
+ keyword_init: true
9
+ ) do
10
+ def initialize(
11
+ base_uri: URI('json-schemer://schema'),
12
+ meta_schema: Draft202012::BASE_URI.to_s,
13
+ vocabulary: nil,
14
+ format: true,
15
+ formats: {},
16
+ content_encodings: {},
17
+ content_media_types: {},
18
+ keywords: {},
19
+ before_property_validation: [],
20
+ after_property_validation: [],
21
+ insert_property_defaults: false,
22
+ property_default_resolver: nil,
23
+ ref_resolver: proc { |uri| raise UnknownRef, uri.to_s },
24
+ regexp_resolver: 'ruby',
25
+ output_format: 'classic',
26
+ resolve_enumerators: false,
27
+ access_mode: nil
28
+ )
29
+ super
30
+ end
31
+ end
32
+ end
@@ -300,7 +300,7 @@ module JSONSchemer
300
300
  end
301
301
 
302
302
  def false_schema_error(formatted_instance_location:, **)
303
- "object property at #{formatted_instance_location} is not defined and schema does not allow additional properties"
303
+ "object property at #{formatted_instance_location} is a disallowed additional property"
304
304
  end
305
305
 
306
306
  def parse
@@ -136,7 +136,7 @@ module JSONSchemer
136
136
  end
137
137
  end
138
138
 
139
- def fetch_unknown!(token)
139
+ def fetch(token)
140
140
  if value.is_a?(Hash)
141
141
  parsed[token] ||= JSONSchemer::Schema::UNKNOWN_KEYWORD_CLASS.new(value.fetch(token), self, token, schema)
142
142
  elsif value.is_a?(Array)
@@ -146,8 +146,8 @@ module JSONSchemer
146
146
  end
147
147
  end
148
148
 
149
- def unknown_schema!
150
- @unknown_schema ||= subschema(value)
149
+ def parsed_schema
150
+ @parsed_schema ||= subschema(value)
151
151
  end
152
152
 
153
153
  def validate(instance, instance_location, keyword_location, _context)
@@ -8,6 +8,10 @@ module JSONSchemer
8
8
  "array items at #{formatted_instance_location} do not match `unevaluatedItems` schema"
9
9
  end
10
10
 
11
+ def false_schema_error(formatted_instance_location:, **)
12
+ "array item at #{formatted_instance_location} is a disallowed unevaluated item"
13
+ end
14
+
11
15
  def parse
12
16
  subschema(value)
13
17
  end
@@ -18,7 +22,7 @@ module JSONSchemer
18
22
  unevaluated_items = instance.size.times.to_set
19
23
 
20
24
  context.adjacent_results.each_value do |adjacent_result|
21
- collect_unevaluated_items(adjacent_result, instance_location, unevaluated_items)
25
+ collect_unevaluated_items(adjacent_result, unevaluated_items)
22
26
  end
23
27
 
24
28
  nested = unevaluated_items.map do |index|
@@ -30,8 +34,7 @@ module JSONSchemer
30
34
 
31
35
  private
32
36
 
33
- def collect_unevaluated_items(result, instance_location, unevaluated_items)
34
- return unless result.valid && result.instance_location == instance_location
37
+ def collect_unevaluated_items(result, unevaluated_items)
35
38
  case result.source
36
39
  when Applicator::PrefixItems
37
40
  unevaluated_items.subtract(0..result.annotation)
@@ -41,7 +44,9 @@ module JSONSchemer
41
44
  unevaluated_items.subtract(result.annotation)
42
45
  end
43
46
  result.nested&.each do |subresult|
44
- collect_unevaluated_items(subresult, instance_location, unevaluated_items)
47
+ if subresult.valid && subresult.instance_location == result.instance_location
48
+ collect_unevaluated_items(subresult, unevaluated_items)
49
+ end
45
50
  end
46
51
  end
47
52
  end
@@ -51,6 +56,10 @@ module JSONSchemer
51
56
  "object properties at #{formatted_instance_location} do not match `unevaluatedProperties` schema"
52
57
  end
53
58
 
59
+ def false_schema_error(formatted_instance_location:, **)
60
+ "object property at #{formatted_instance_location} is a disallowed unevaluated property"
61
+ end
62
+
54
63
  def parse
55
64
  subschema(value)
56
65
  end
@@ -61,7 +70,7 @@ module JSONSchemer
61
70
  evaluated_keys = Set[]
62
71
 
63
72
  context.adjacent_results.each_value do |adjacent_result|
64
- collect_evaluated_keys(adjacent_result, instance_location, evaluated_keys)
73
+ collect_evaluated_keys(adjacent_result, evaluated_keys)
65
74
  end
66
75
 
67
76
  evaluated = instance.reject do |key, _value|
@@ -77,14 +86,15 @@ module JSONSchemer
77
86
 
78
87
  private
79
88
 
80
- def collect_evaluated_keys(result, instance_location, evaluated_keys)
81
- return unless result.valid && result.instance_location == instance_location
89
+ def collect_evaluated_keys(result, evaluated_keys)
82
90
  case result.source
83
91
  when Applicator::Properties, Applicator::PatternProperties, Applicator::AdditionalProperties, UnevaluatedProperties
84
92
  evaluated_keys.merge(result.annotation)
85
93
  end
86
94
  result.nested&.each do |subresult|
87
- collect_evaluated_keys(subresult, instance_location, evaluated_keys)
95
+ if subresult.valid && subresult.instance_location == result.instance_location
96
+ collect_evaluated_keys(subresult, evaluated_keys)
97
+ end
88
98
  end
89
99
  end
90
100
  end
@@ -76,8 +76,8 @@ module JSONSchemer
76
76
  IRI_ESCAPE_REGEX = /[^[:ascii:]]/
77
77
  UUID_REGEX = /\A\h{8}-\h{4}-\h{4}-[89AB]\h{3}-\h{12}\z/i
78
78
  NIL_UUID = '00000000-0000-0000-0000-000000000000'
79
- ASCII_8BIT_TO_PERCENT_ENCODED = 256.times.each_with_object({}) do |byte, out|
80
- out[-byte.chr] = -sprintf('%%%02X', byte)
79
+ BINARY_TO_PERCENT_ENCODED = 256.times.each_with_object({}) do |byte, out|
80
+ out[-byte.chr(Encoding::BINARY)] = -sprintf('%%%02X', byte)
81
81
  end.freeze
82
82
 
83
83
  class << self
@@ -88,10 +88,9 @@ module JSONSchemer
88
88
  include URITemplate
89
89
 
90
90
  def percent_encode(data, regexp)
91
- data = data.dup
92
- data.force_encoding(Encoding::ASCII_8BIT)
93
- data.gsub!(regexp, ASCII_8BIT_TO_PERCENT_ENCODED)
94
- data.force_encoding(Encoding::US_ASCII)
91
+ binary = data.b
92
+ binary.gsub!(regexp, BINARY_TO_PERCENT_ENCODED)
93
+ binary.force_encoding(data.encoding)
95
94
  end
96
95
 
97
96
  def valid_date_time?(data)
@@ -30,6 +30,14 @@ module JSONSchemer
30
30
  keyword
31
31
  end
32
32
 
33
+ def fetch(key)
34
+ parsed.fetch(parsed.is_a?(Array) ? key.to_i : key)
35
+ end
36
+
37
+ def parsed_schema
38
+ parsed.is_a?(Schema) ? parsed : nil
39
+ end
40
+
33
41
  private
34
42
 
35
43
  def parse
@@ -37,8 +45,11 @@ module JSONSchemer
37
45
  end
38
46
 
39
47
  def subschema(value, keyword = nil, **options)
48
+ options[:configuration] ||= schema.configuration
40
49
  options[:base_uri] ||= schema.base_uri
41
50
  options[:meta_schema] ||= schema.meta_schema
51
+ options[:ref_resolver] ||= schema.ref_resolver
52
+ options[:regexp_resolver] ||= schema.regexp_resolver
42
53
  Schema.new(value, self, root, keyword, **options)
43
54
  end
44
55
  end
@@ -8,16 +8,14 @@ module JSONSchemer
8
8
  case version
9
9
  when /\A3\.1\.\d+\z/
10
10
  @document_schema = JSONSchemer.openapi31_document
11
- json_schema_dialect = document.fetch('jsonSchemaDialect') { OpenAPI31::BASE_URI.to_s }
11
+ meta_schema = document.fetch('jsonSchemaDialect') { OpenAPI31::BASE_URI.to_s }
12
12
  when /\A3\.0\.\d+\z/
13
13
  @document_schema = JSONSchemer.openapi30_document
14
- json_schema_dialect = OpenAPI30::BASE_URI.to_s
14
+ meta_schema = OpenAPI30::BASE_URI.to_s
15
15
  else
16
16
  raise UnsupportedOpenAPIVersion, version
17
17
  end
18
18
 
19
- meta_schema = META_SCHEMAS_BY_BASE_URI_STR[json_schema_dialect] || raise(UnsupportedMetaSchema, json_schema_dialect)
20
-
21
19
  @schema = JSONSchemer.schema(@document, :meta_schema => meta_schema, **options)
22
20
  end
23
21
 
@@ -1584,8 +1584,7 @@ module JSONSchemer
1584
1584
  'type' => 'string'
1585
1585
  },
1586
1586
  'operationRef' => {
1587
- 'type' => 'string',
1588
- 'format' => 'uri-reference'
1587
+ 'type' => 'string'
1589
1588
  },
1590
1589
  'parameters' => {
1591
1590
  'type' => 'object',
@@ -5,7 +5,7 @@ module JSONSchemer
5
5
  # https://spec.openapis.org/oas/v3.0.3#data-types
6
6
  FORMATS = OpenAPI31::FORMATS.merge(
7
7
  'byte' => proc { |instance, _value| ContentEncoding::BASE64.call(instance).first },
8
- 'binary' => proc { |instance, _value| instance.is_a?(String) && instance.encoding == Encoding::ASCII_8BIT },
8
+ 'binary' => proc { |instance, _value| instance.is_a?(String) && instance.encoding == Encoding::BINARY },
9
9
  'date' => Format::DATE
10
10
  )
11
11
  SCHEMA = {
@@ -270,8 +270,7 @@ module JSONSchemer
270
270
  'type' => 'object',
271
271
  'properties' => {
272
272
  'url' => {
273
- 'type' => 'string',
274
- 'format' => 'uri-reference'
273
+ 'type' => 'string'
275
274
  },
276
275
  'description' => {
277
276
  'type' => 'string'
@@ -1054,8 +1053,7 @@ module JSONSchemer
1054
1053
  'type' => 'object',
1055
1054
  'properties' => {
1056
1055
  'operationRef' => {
1057
- 'type' => 'string',
1058
- 'format' => 'uri-reference'
1056
+ 'type' => 'string'
1059
1057
  },
1060
1058
  'operationId' => {
1061
1059
  'type' => 'string'
@@ -201,8 +201,8 @@ module JSONSchemer
201
201
 
202
202
  if result.source.is_a?(Schema::PROPERTIES_KEYWORD_CLASS) && result.instance.is_a?(Hash)
203
203
  result.source.parsed.each do |property, schema|
204
- next if result.instance.key?(property) || !schema.parsed.key?('default')
205
- default = schema.parsed.fetch('default')
204
+ next if result.instance.key?(property)
205
+ next unless default = default_keyword_instance(schema)
206
206
  instance_location = Location.join(result.instance_location, property)
207
207
  keyword_location = Location.join(Location.join(result.keyword_location, property), default.keyword)
208
208
  default_result = default.validate(nil, instance_location, keyword_location, nil)
@@ -225,5 +225,17 @@ module JSONSchemer
225
225
 
226
226
  inserted
227
227
  end
228
+
229
+ private
230
+
231
+ def default_keyword_instance(schema)
232
+ schema.parsed.fetch('default') do
233
+ schema.parsed.find do |_keyword, keyword_instance|
234
+ next unless keyword_instance.respond_to?(:ref_schema)
235
+ next unless default = default_keyword_instance(keyword_instance.ref_schema)
236
+ break default
237
+ end
238
+ end
239
+ end
228
240
  end
229
241
  end
@@ -4,28 +4,26 @@ module JSONSchemer
4
4
  Context = Struct.new(:instance, :dynamic_scope, :adjacent_results, :short_circuit, :access_mode) do
5
5
  def original_instance(instance_location)
6
6
  Hana::Pointer.parse(Location.resolve(instance_location)).reduce(instance) do |obj, token|
7
- obj.fetch(obj.is_a?(Array) ? token.to_i : token)
7
+ if obj.is_a?(Array)
8
+ obj.fetch(token.to_i)
9
+ elsif !obj.key?(token) && obj.key?(token.to_sym)
10
+ obj.fetch(token.to_sym)
11
+ else
12
+ obj.fetch(token)
13
+ end
8
14
  end
9
15
  end
10
16
  end
11
17
 
12
18
  include Output
13
19
 
14
- DEFAULT_SCHEMA = Draft202012::BASE_URI.to_s.freeze
15
20
  SCHEMA_KEYWORD_CLASS = Draft202012::Vocab::Core::Schema
16
21
  VOCABULARY_KEYWORD_CLASS = Draft202012::Vocab::Core::Vocabulary
17
22
  ID_KEYWORD_CLASS = Draft202012::Vocab::Core::Id
18
23
  UNKNOWN_KEYWORD_CLASS = Draft202012::Vocab::Core::UnknownKeyword
19
24
  NOT_KEYWORD_CLASS = Draft202012::Vocab::Applicator::Not
20
25
  PROPERTIES_KEYWORD_CLASS = Draft202012::Vocab::Applicator::Properties
21
- DEFAULT_BASE_URI = URI('json-schemer://schema').freeze
22
- DEFAULT_FORMATS = {}.freeze
23
- DEFAULT_CONTENT_ENCODINGS = {}.freeze
24
- DEFAULT_CONTENT_MEDIA_TYPES = {}.freeze
25
- DEFAULT_KEYWORDS = {}.freeze
26
- DEFAULT_BEFORE_PROPERTY_VALIDATION = [].freeze
27
- DEFAULT_AFTER_PROPERTY_VALIDATION = [].freeze
28
- DEFAULT_REF_RESOLVER = proc { |uri| raise UnknownRef, uri.to_s }
26
+
29
27
  NET_HTTP_REF_RESOLVER = proc { |uri| JSON.parse(Net::HTTP.get(uri)) }
30
28
  RUBY_REGEXP_RESOLVER = proc { |pattern| Regexp.new(pattern) }
31
29
  ECMA_REGEXP_RESOLVER = proc { |pattern| Regexp.new(EcmaRegexp.ruby_equivalent(pattern)) }
@@ -40,39 +38,44 @@ module JSONSchemer
40
38
  false
41
39
  end
42
40
  end
41
+ SYMBOL_PROPERTY_DEFAULT_RESOLVER = proc do |instance, property, results_with_tree_validity|
42
+ DEFAULT_PROPERTY_DEFAULT_RESOLVER.call(instance, property.to_sym, results_with_tree_validity)
43
+ end
43
44
 
44
45
  attr_accessor :base_uri, :meta_schema, :keywords, :keyword_order
45
- attr_reader :value, :parent, :root, :parsed
46
- attr_reader :vocabulary, :format, :formats, :content_encodings, :content_media_types, :custom_keywords, :before_property_validation, :after_property_validation, :insert_property_defaults, :property_default_resolver
46
+ attr_reader :value, :parent, :root, :configuration, :parsed
47
+ attr_reader :vocabulary, :format, :formats, :content_encodings, :content_media_types, :custom_keywords, :before_property_validation, :after_property_validation, :insert_property_defaults
47
48
 
48
49
  def initialize(
49
50
  value,
50
51
  parent = nil,
51
52
  root = self,
52
53
  keyword = nil,
53
- base_uri: DEFAULT_BASE_URI,
54
- meta_schema: nil,
55
- vocabulary: nil,
56
- format: true,
57
- formats: DEFAULT_FORMATS,
58
- content_encodings: DEFAULT_CONTENT_ENCODINGS,
59
- content_media_types: DEFAULT_CONTENT_MEDIA_TYPES,
60
- keywords: DEFAULT_KEYWORDS,
61
- before_property_validation: DEFAULT_BEFORE_PROPERTY_VALIDATION,
62
- after_property_validation: DEFAULT_AFTER_PROPERTY_VALIDATION,
63
- insert_property_defaults: false,
64
- property_default_resolver: DEFAULT_PROPERTY_DEFAULT_RESOLVER,
65
- ref_resolver: DEFAULT_REF_RESOLVER,
66
- regexp_resolver: 'ruby',
67
- output_format: 'classic',
68
- resolve_enumerators: false,
69
- access_mode: nil
54
+ configuration: JSONSchemer.configuration,
55
+ base_uri: configuration.base_uri,
56
+ meta_schema: configuration.meta_schema,
57
+ vocabulary: configuration.vocabulary,
58
+ format: configuration.format,
59
+ formats: configuration.formats,
60
+ content_encodings: configuration.content_encodings,
61
+ content_media_types: configuration.content_media_types,
62
+ keywords: configuration.keywords,
63
+ before_property_validation: configuration.before_property_validation,
64
+ after_property_validation: configuration.after_property_validation,
65
+ insert_property_defaults: configuration.insert_property_defaults,
66
+ property_default_resolver: configuration.property_default_resolver,
67
+ ref_resolver: configuration.ref_resolver,
68
+ regexp_resolver: configuration.regexp_resolver,
69
+ output_format: configuration.output_format,
70
+ resolve_enumerators: configuration.resolve_enumerators,
71
+ access_mode: configuration.access_mode
70
72
  )
71
73
  @value = deep_stringify_keys(value)
72
74
  @parent = parent
73
75
  @root = root
74
76
  @keyword = keyword
75
77
  @schema = self
78
+ @configuration = configuration
76
79
  @base_uri = base_uri
77
80
  @meta_schema = meta_schema
78
81
  @vocabulary = vocabulary
@@ -109,12 +112,12 @@ module JSONSchemer
109
112
  output
110
113
  end
111
114
 
112
- def valid_schema?
113
- meta_schema.valid?(value)
115
+ def valid_schema?(**options)
116
+ meta_schema.valid?(value, **options)
114
117
  end
115
118
 
116
- def validate_schema
117
- meta_schema.validate(value)
119
+ def validate_schema(**options)
120
+ meta_schema.validate(value, **options)
118
121
  end
119
122
 
120
123
  def ref(value)
@@ -142,10 +145,11 @@ module JSONSchemer
142
145
  adjacent_results[keyword_instance.class] = keyword_result
143
146
  end
144
147
 
145
- if custom_keywords.any?
146
- custom_keywords.each do |custom_keyword, callable|
148
+ if root.custom_keywords.any?
149
+ resolved_instance_location = Location.resolve(instance_location)
150
+ root.custom_keywords.each do |custom_keyword, callable|
147
151
  if value.key?(custom_keyword)
148
- [*callable.call(instance, value, instance_location)].each do |custom_keyword_result|
152
+ [*callable.call(instance, value, resolved_instance_location)].each do |custom_keyword_result|
149
153
  custom_keyword_valid = custom_keyword_result == true
150
154
  valid &&= custom_keyword_valid
151
155
  type = custom_keyword_result.is_a?(String) ? custom_keyword_result : custom_keyword
@@ -184,16 +188,9 @@ module JSONSchemer
184
188
  uri.fragment = nil
185
189
  remote_schema = JSONSchemer.schema(
186
190
  ref_resolver.call(uri) || raise(InvalidRefResolution, uri.to_s),
191
+ :configuration => configuration,
187
192
  :base_uri => uri,
188
193
  :meta_schema => meta_schema,
189
- :format => format,
190
- :formats => formats,
191
- :content_encodings => content_encodings,
192
- :content_media_types => content_media_types,
193
- :keywords => custom_keywords,
194
- :before_property_validation => before_property_validation,
195
- :after_property_validation => after_property_validation,
196
- :property_default_resolver => property_default_resolver,
197
194
  :ref_resolver => ref_resolver,
198
195
  :regexp_resolver => regexp_resolver
199
196
  )
@@ -203,18 +200,13 @@ module JSONSchemer
203
200
  end
204
201
 
205
202
  schema = Hana::Pointer.parse(pointer).reduce(schema) do |obj, token|
206
- if obj.is_a?(UNKNOWN_KEYWORD_CLASS)
207
- obj.fetch_unknown!(token)
208
- elsif obj.parsed.is_a?(Array)
209
- obj.parsed.fetch(token.to_i)
210
- else
211
- obj.parsed.fetch(token)
212
- end
203
+ obj.fetch(token)
213
204
  rescue IndexError
214
205
  raise InvalidRefPointer, pointer
215
206
  end
216
207
 
217
- schema = schema.unknown_schema! unless schema.is_a?(Schema)
208
+ schema = schema.parsed_schema if schema.is_a?(Keyword)
209
+ raise InvalidRefPointer, pointer unless schema.is_a?(Schema)
218
210
 
219
211
  schema
220
212
  end
@@ -299,6 +291,10 @@ module JSONSchemer
299
291
  '^'
300
292
  end
301
293
 
294
+ def fetch(key)
295
+ parsed.fetch(key)
296
+ end
297
+
302
298
  def fetch_format(format, *args, &block)
303
299
  if meta_schema == self
304
300
  formats.fetch(format, *args, &block)
@@ -343,6 +339,21 @@ module JSONSchemer
343
339
  end
344
340
  end
345
341
 
342
+ def ref_resolver
343
+ @ref_resolver ||= @original_ref_resolver == 'net/http' ? CachedResolver.new(&NET_HTTP_REF_RESOLVER) : @original_ref_resolver
344
+ end
345
+
346
+ def regexp_resolver
347
+ @regexp_resolver ||= case @original_regexp_resolver
348
+ when 'ecma'
349
+ CachedResolver.new(&ECMA_REGEXP_RESOLVER)
350
+ when 'ruby'
351
+ CachedResolver.new(&RUBY_REGEXP_RESOLVER)
352
+ else
353
+ @original_regexp_resolver
354
+ end
355
+ end
356
+
346
357
  def inspect
347
358
  "#<#{self.class.name} @value=#{@value.inspect} @parent=#{@parent.inspect} @keyword=#{@keyword.inspect}>"
348
359
  end
@@ -354,8 +365,8 @@ module JSONSchemer
354
365
 
355
366
  if value.is_a?(Hash) && value.key?('$schema')
356
367
  @parsed['$schema'] = SCHEMA_KEYWORD_CLASS.new(value.fetch('$schema'), self, '$schema')
357
- elsif root == self && !meta_schema
358
- SCHEMA_KEYWORD_CLASS.new(DEFAULT_SCHEMA, self, '$schema')
368
+ elsif meta_schema.is_a?(String)
369
+ SCHEMA_KEYWORD_CLASS.new(meta_schema, self, '$schema')
359
370
  end
360
371
 
361
372
  if value.is_a?(Hash) && value.key?('$vocabulary')
@@ -395,19 +406,8 @@ module JSONSchemer
395
406
  @root_keyword_location ||= Location.root
396
407
  end
397
408
 
398
- def ref_resolver
399
- @ref_resolver ||= @original_ref_resolver == 'net/http' ? CachedResolver.new(&NET_HTTP_REF_RESOLVER) : @original_ref_resolver
400
- end
401
-
402
- def regexp_resolver
403
- @regexp_resolver ||= case @original_regexp_resolver
404
- when 'ecma'
405
- CachedResolver.new(&ECMA_REGEXP_RESOLVER)
406
- when 'ruby'
407
- CachedResolver.new(&RUBY_REGEXP_RESOLVER)
408
- else
409
- @original_regexp_resolver
410
- end
409
+ def property_default_resolver
410
+ @property_default_resolver ||= insert_property_defaults == :symbol ? SYMBOL_PROPERTY_DEFAULT_RESOLVER : DEFAULT_PROPERTY_DEFAULT_RESOLVER
411
411
  end
412
412
 
413
413
  def resolve_enumerators!(output)
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module JSONSchemer
3
- VERSION = '2.1.0'
3
+ VERSION = '2.2.0'
4
4
  end
data/lib/json_schemer.rb CHANGED
@@ -59,10 +59,10 @@ require 'json_schemer/openapi30/meta'
59
59
  require 'json_schemer/openapi30/vocab/base'
60
60
  require 'json_schemer/openapi30/vocab'
61
61
  require 'json_schemer/openapi'
62
+ require 'json_schemer/configuration'
62
63
  require 'json_schemer/schema'
63
64
 
64
65
  module JSONSchemer
65
- class UnsupportedMetaSchema < StandardError; end
66
66
  class UnsupportedOpenAPIVersion < StandardError; end
67
67
  class UnknownRef < StandardError; end
68
68
  class UnknownFormat < StandardError; end
@@ -113,33 +113,19 @@ module JSONSchemer
113
113
  end
114
114
 
115
115
  class << self
116
- def schema(schema, meta_schema: draft202012, **options)
117
- case schema
118
- when String
119
- schema = JSON.parse(schema)
120
- when Pathname
121
- base_uri = URI.parse(File.join('file:', URI::DEFAULT_PARSER.escape(schema.realpath.to_s)))
122
- options[:base_uri] = base_uri
123
- schema = if options.key?(:ref_resolver)
124
- FILE_URI_REF_RESOLVER.call(base_uri)
125
- else
126
- ref_resolver = CachedResolver.new(&FILE_URI_REF_RESOLVER)
127
- options[:ref_resolver] = ref_resolver
128
- ref_resolver.call(base_uri)
129
- end
130
- end
131
- unless meta_schema.is_a?(Schema)
132
- meta_schema = META_SCHEMAS_BY_BASE_URI_STR[meta_schema] || raise(UnsupportedMetaSchema, meta_schema)
133
- end
134
- Schema.new(schema, :meta_schema => meta_schema, **options)
116
+ def schema(schema, **options)
117
+ schema = resolve(schema, options)
118
+ Schema.new(schema, **options)
135
119
  end
136
120
 
137
121
  def valid_schema?(schema, **options)
138
- schema(schema, **options).valid_schema?
122
+ schema = resolve(schema, options)
123
+ meta_schema(schema, options).valid?(schema, **options.slice(:output_format, :resolve_enumerators, :access_mode))
139
124
  end
140
125
 
141
126
  def validate_schema(schema, **options)
142
- schema(schema, **options).validate_schema
127
+ schema = resolve(schema, options)
128
+ meta_schema(schema, options).validate(schema, **options.slice(:output_format, :resolve_enumerators, :access_mode))
143
129
  end
144
130
 
145
131
  def draft202012
@@ -245,6 +231,44 @@ module JSONSchemer
245
231
  def openapi(document, **options)
246
232
  OpenAPI.new(document, **options)
247
233
  end
234
+
235
+ def configuration
236
+ @configuration ||= Configuration.new
237
+ end
238
+
239
+ def configure
240
+ yield configuration
241
+ end
242
+
243
+ private
244
+
245
+ def resolve(schema, options)
246
+ case schema
247
+ when String
248
+ JSON.parse(schema)
249
+ when Pathname
250
+ base_uri = URI.parse(File.join('file:', URI::DEFAULT_PARSER.escape(schema.realpath.to_s)))
251
+ options[:base_uri] = base_uri
252
+ if options.key?(:ref_resolver)
253
+ FILE_URI_REF_RESOLVER.call(base_uri)
254
+ else
255
+ ref_resolver = CachedResolver.new(&FILE_URI_REF_RESOLVER)
256
+ options[:ref_resolver] = ref_resolver
257
+ ref_resolver.call(base_uri)
258
+ end
259
+ else
260
+ schema
261
+ end
262
+ end
263
+
264
+ def meta_schema(schema, options)
265
+ parseable_schema = {}
266
+ if schema.is_a?(Hash)
267
+ meta_schema = schema['$schema'] || schema[:'$schema']
268
+ parseable_schema['$schema'] = meta_schema if meta_schema.is_a?(String)
269
+ end
270
+ schema(parseable_schema, **options).meta_schema
271
+ end
248
272
  end
249
273
 
250
274
  META_SCHEMA_CALLABLES_BY_BASE_URI_STR = {
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json_schemer
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Harsha
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-11-17 00:00:00.000000000 Z
11
+ date: 2024-03-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0.22'
69
+ - !ruby/object:Gem::Dependency
70
+ name: csv
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  - !ruby/object:Gem::Dependency
70
84
  name: i18n
71
85
  requirement: !ruby/object:Gem::Requirement
@@ -94,6 +108,34 @@ dependencies:
94
108
  - - ">="
95
109
  - !ruby/object:Gem::Version
96
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: base64
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: bigdecimal
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
97
139
  - !ruby/object:Gem::Dependency
98
140
  name: hana
99
141
  requirement: !ruby/object:Gem::Requirement
@@ -160,6 +202,7 @@ files:
160
202
  - json_schemer.gemspec
161
203
  - lib/json_schemer.rb
162
204
  - lib/json_schemer/cached_resolver.rb
205
+ - lib/json_schemer/configuration.rb
163
206
  - lib/json_schemer/content.rb
164
207
  - lib/json_schemer/draft201909/meta.rb
165
208
  - lib/json_schemer/draft201909/vocab.rb
@@ -225,7 +268,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
225
268
  - !ruby/object:Gem::Version
226
269
  version: '0'
227
270
  requirements: []
228
- rubygems_version: 3.4.10
271
+ rubygems_version: 3.5.3
229
272
  signing_key:
230
273
  specification_version: 4
231
274
  summary: JSON Schema validator. Supports drafts 4, 6, 7, 2019-09, 2020-12, OpenAPI