json_schemer 0.2.25 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd29a78d2cbd45dc0409c551d0e3c11e39a801204bab75e22ded249c4ef8edf8
4
- data.tar.gz: 9d0556d1f4723ae50b9a038595f44ffdc9c0dcc8d05d9b9771f58248fc88a764
3
+ metadata.gz: 93264835a6cd46b1c657876bda6a68c53a130c36390e84c316f6ffad3d00194f
4
+ data.tar.gz: 6bc567c682cb103cc673dade1f0a46a0b7500973acf6915acd7d7093de7a74e3
5
5
  SHA512:
6
- metadata.gz: d3bb75eb37c9ad70776fbf8d7fc5c1d7cc4a79bf7c68e2430dbf30f256fb0a3e1cddf718fa1799486814058d98455ef47b62baa514b1b49beafe0c7fc6a489dc
7
- data.tar.gz: eea979521c2d65afef0325f94513ecf64bf604c562af41afae2ec258a4449b70a28aee927be925681ceecc434ba716271d7bbc6a713f8413b0f22710b7ed7c9c
6
+ metadata.gz: 5e0fe51563031e2bcce3331cd13b251a0e8007740c9d5b70e59a03b5151114e221ad50b24358094ccc33b31fcabb0b62e76674a966d5c8bf3ed872accd839f4f
7
+ data.tar.gz: 2835c05bbb368718f20e8ec435def32619b3876d7c1e78259f92ac8b57d0f45b27cc6a9481fc101efe8e0456054ddfffc8d05ad60f37b63f8e2475e95b81e368
@@ -6,12 +6,8 @@ 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, 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, 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
data/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ ## [1.0.0] - 2023-05-26
4
+
5
+ ### Breaking Changes
6
+
7
+ - Ruby 2.4 is no longer supported.
8
+ - 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:
9
+ - Beginning of string: `^` -> `\A`
10
+ - End of string: `$` -> `\z`
11
+ - Space: `\s` -> `[\t\r\n\f\v\uFEFF\u2029\p{Zs}]`
12
+ - Non-space: `\S` -> `[^\t\r\n\f\v\uFEFF\u2029\p{Zs}]`
13
+ - Invalid ECMA-262 regular expressions raise `JSONSchemer::InvalidEcmaRegexp` when `regexp_resolver` is set to `ecma`.
14
+ - 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`.
15
+ - Empty fragments are now removed from `$ref` URIs before calling `ref_resolver`.
16
+ - Refs that are fragment-only JSON pointers with special characters must use the proper encoding (eg, `"$ref": "#/definitions/some-%7Bid%7D"`).
17
+
18
+ [1.0.0]: https://github.com/davishmcclurg/json_schemer/releases/tag/v1.0.0
data/Gemfile.lock CHANGED
@@ -1,8 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- json_schemer (0.2.25)
5
- ecma-re-validator (~> 0.3)
4
+ json_schemer (1.0.0)
6
5
  hana (~> 1.3)
7
6
  regexp_parser (~> 2.0)
8
7
  simpleidn (~> 0.2)
@@ -12,8 +11,6 @@ GEM
12
11
  remote: https://rubygems.org/
13
12
  specs:
14
13
  docile (1.4.0)
15
- ecma-re-validator (0.3.0)
16
- regexp_parser (~> 2.0)
17
14
  hana (1.3.7)
18
15
  minitest (5.15.0)
19
16
  rake (13.0.6)
data/README.md CHANGED
@@ -45,7 +45,12 @@ 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"}]
49
54
 
50
55
  # default property values
51
56
 
@@ -74,6 +79,30 @@ schemer = JSONSchemer.schema(schema)
74
79
 
75
80
  schema = '{ "type": "integer" }'
76
81
  schemer = JSONSchemer.schema(schema)
82
+
83
+ # schema validation
84
+
85
+ JSONSchemer.valid_schema?({ '$id' => '#valid' })
86
+ # => true
87
+
88
+ JSONSchemer.validate_schema({ '$id' => nil }).to_a
89
+ # => [{"data"=>nil,
90
+ # "data_pointer"=>"/$id",
91
+ # "schema"=>{"type"=>"string", "format"=>"uri-reference"},
92
+ # "schema_pointer"=>"/properties/$id",
93
+ # "root_schema"=>{...meta schema},
94
+ # "type"=>"string"}]
95
+
96
+ JSONSchemer.schema({ '$id' => '#valid' }).valid_schema?
97
+ # => true
98
+
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"}]
77
106
  ```
78
107
 
79
108
  ## Options
@@ -113,8 +142,9 @@ JSONSchemer.schema(
113
142
  ref_resolver: 'net/http',
114
143
 
115
144
  # use different method to match regexes
116
- # 'ecma'/'ruby'/proc/lambda/respond_to?(:call)
117
- # default: 'ecma'
145
+ # 'ruby'/'ecma'/proc/lambda/respond_to?(:call)
146
+ # 'ruby': proc { |pattern| Regexp.new(pattern) }
147
+ # default: 'ruby'
118
148
  regexp_resolver: proc do |pattern|
119
149
  RE2::Regexp.new(pattern)
120
150
  end
data/json_schemer.gemspec CHANGED
@@ -20,14 +20,13 @@ 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.4'
23
+ spec.required_ruby_version = '>= 2.5'
24
24
 
25
25
  spec.add_development_dependency "bundler", "~> 2.0"
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
29
 
30
- spec.add_runtime_dependency "ecma-re-validator", "~> 0.3"
31
30
  spec.add_runtime_dependency "hana", "~> 1.3"
32
31
  spec.add_runtime_dependency "regexp_parser", "~> 2.0"
33
32
  spec.add_runtime_dependency "simpleidn", "~> 0.2"
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+ module JSONSchemer
3
+ class EcmaRegexp
4
+ class Syntax < Regexp::Syntax::Base
5
+ implements :anchor, Anchor::Extended
6
+ implements :assertion, Assertion::All
7
+ implements :backref, Backreference::Plain + Backreference::Name
8
+ implements :escape, Escape::Basic + %i[control backspace form_feed newline carriage tab vertical_tab] + Escape::Unicode + Escape::Meta + Escape::Hex + Escape::Octal
9
+ implements :property, UnicodeProperty::All
10
+ implements :nonproperty, UnicodeProperty::All
11
+ implements :free_space, %i[whitespace]
12
+ implements :group, Group::Basic + Group::Named + Group::Passive
13
+ implements :literal, Literal::All
14
+ implements :meta, Meta::Extended
15
+ implements :quantifier, Quantifier::Greedy + Quantifier::Reluctant + Quantifier::Interval + Quantifier::IntervalReluctant
16
+ implements :set, CharacterSet::Basic
17
+ implements :type, CharacterType::Extended
18
+ end
19
+
20
+ RUBY_EQUIVALENTS = {
21
+ :anchor => {
22
+ :bol => '\A',
23
+ :eol => '\z'
24
+ },
25
+ :type => {
26
+ :space => '[\t\r\n\f\v\uFEFF\u2029\p{Zs}]',
27
+ :nonspace => '[^\t\r\n\f\v\uFEFF\u2029\p{Zs}]'
28
+ }
29
+ }.freeze
30
+
31
+ class << self
32
+ def ruby_equivalent(pattern)
33
+ Regexp::Scanner.scan(pattern).map do |type, token, text|
34
+ Syntax.check!(*Syntax.normalize(type, token))
35
+ RUBY_EQUIVALENTS.dig(type, token) || text
36
+ rescue Regexp::Syntax::NotImplementedError
37
+ raise InvalidEcmaRegexp, "invalid token #{text.inspect} (#{type}:#{token}) in #{pattern.inspect}"
38
+ end.join
39
+ rescue Regexp::Scanner::ScannerError
40
+ raise InvalidEcmaRegexp, "invalid pattern #{pattern.inspect}"
41
+ end
42
+ end
43
+ end
44
+ end
@@ -49,7 +49,7 @@ module JSONSchemer
49
49
  when 'relative-json-pointer'
50
50
  valid_relative_json_pointer?(data)
51
51
  when 'regex'
52
- EcmaReValidator.valid?(data)
52
+ valid_regex?(data)
53
53
  else
54
54
  raise UnknownFormat, format
55
55
  end
@@ -129,5 +129,11 @@ module JSONSchemer
129
129
  def valid_relative_json_pointer?(data)
130
130
  RELATIVE_JSON_POINTER_REGEX.match?(data)
131
131
  end
132
+
133
+ def valid_regex?(data)
134
+ !!EcmaRegexp.ruby_equivalent(data)
135
+ rescue InvalidEcmaRegexp
136
+ false
137
+ end
132
138
  end
133
139
  end
@@ -4,40 +4,27 @@ module JSONSchemer
4
4
  class Base
5
5
  include Format
6
6
 
7
- Instance = Struct.new(:data, :data_pointer, :schema, :schema_pointer, :parent_uri, :before_property_validation, :after_property_validation) do
7
+ Instance = Struct.new(:data, :data_pointer, :schema, :schema_pointer, :base_uri, :before_property_validation, :after_property_validation) do
8
8
  def merge(
9
9
  data: self.data,
10
10
  data_pointer: self.data_pointer,
11
11
  schema: self.schema,
12
12
  schema_pointer: self.schema_pointer,
13
- parent_uri: self.parent_uri,
13
+ base_uri: self.base_uri,
14
14
  before_property_validation: self.before_property_validation,
15
15
  after_property_validation: self.after_property_validation
16
16
  )
17
- self.class.new(data, data_pointer, schema, schema_pointer, parent_uri, before_property_validation, after_property_validation)
17
+ self.class.new(data, data_pointer, schema, schema_pointer, base_uri, before_property_validation, after_property_validation)
18
18
  end
19
19
  end
20
20
 
21
21
  ID_KEYWORD = '$id'
22
22
  DEFAULT_REF_RESOLVER = proc { |uri| raise UnknownRef, uri.to_s }
23
23
  NET_HTTP_REF_RESOLVER = proc { |uri| JSON.parse(Net::HTTP.get(uri)) }
24
+ RUBY_REGEXP_RESOLVER = proc { |pattern| Regexp.new(pattern) }
25
+ ECMA_REGEXP_RESOLVER = proc { |pattern| Regexp.new(EcmaRegexp.ruby_equivalent(pattern)) }
24
26
  BOOLEANS = Set[true, false].freeze
25
27
 
26
- RUBY_REGEX_ANCHORS_TO_ECMA_262 = {
27
- :bos => 'A',
28
- :eos => 'z',
29
- :bol => '\A',
30
- :eol => '\z'
31
- }.freeze
32
-
33
- ECMA_262_REGEXP_RESOLVER = proc do |pattern|
34
- Regexp.new(
35
- Regexp::Scanner.scan(pattern).map do |type, token, text|
36
- type == :anchor ? RUBY_REGEX_ANCHORS_TO_ECMA_262.fetch(token, text) : text
37
- end.join
38
- )
39
- end
40
-
41
28
  INSERT_DEFAULT_PROPERTY = proc do |data, property, property_schema, _parent|
42
29
  if !data.key?(property) && property_schema.is_a?(Hash) && property_schema.key?('default')
43
30
  data[property] = property_schema.fetch('default').clone
@@ -47,8 +34,23 @@ module JSONSchemer
47
34
  JSON_POINTER_TOKEN_ESCAPE_CHARS = { '~' => '~0', '/' => '~1' }
48
35
  JSON_POINTER_TOKEN_ESCAPE_REGEXP = Regexp.union(JSON_POINTER_TOKEN_ESCAPE_CHARS.keys)
49
36
 
37
+ class << self
38
+ def draft_name
39
+ name.split('::').last.downcase
40
+ end
41
+
42
+ def meta_schema
43
+ @meta_schema ||= JSON.parse(Pathname.new(__dir__).join("#{draft_name}.json").read).freeze
44
+ end
45
+
46
+ def meta_schemer
47
+ @meta_schemer ||= JSONSchemer.schema(meta_schema)
48
+ end
49
+ end
50
+
50
51
  def initialize(
51
52
  schema,
53
+ base_uri: nil,
52
54
  format: true,
53
55
  insert_property_defaults: false,
54
56
  before_property_validation: nil,
@@ -56,10 +58,11 @@ module JSONSchemer
56
58
  formats: nil,
57
59
  keywords: nil,
58
60
  ref_resolver: DEFAULT_REF_RESOLVER,
59
- regexp_resolver: 'ecma'
61
+ regexp_resolver: 'ruby'
60
62
  )
61
63
  raise InvalidSymbolKey, 'schemas must use string keys' if schema.is_a?(Hash) && !schema.empty? && !schema.first.first.is_a?(String)
62
64
  @root = schema
65
+ @base_uri = base_uri
63
66
  @format = format
64
67
  @before_property_validation = [*before_property_validation]
65
68
  @before_property_validation.unshift(INSERT_DEFAULT_PROPERTY) if insert_property_defaults
@@ -69,24 +72,34 @@ module JSONSchemer
69
72
  @ref_resolver = ref_resolver == 'net/http' ? CachedResolver.new(&NET_HTTP_REF_RESOLVER) : ref_resolver
70
73
  @regexp_resolver = case regexp_resolver
71
74
  when 'ecma'
72
- CachedResolver.new(&ECMA_262_REGEXP_RESOLVER)
75
+ CachedResolver.new(&ECMA_REGEXP_RESOLVER)
73
76
  when 'ruby'
74
- CachedResolver.new(&Regexp.method(:new))
77
+ CachedResolver.new(&RUBY_REGEXP_RESOLVER)
75
78
  else
76
79
  regexp_resolver
77
80
  end
78
81
  end
79
82
 
80
83
  def valid?(data)
81
- valid_instance?(Instance.new(data, '', root, '', nil, @before_property_validation, @after_property_validation))
84
+ valid_instance?(Instance.new(data, '', root, '', @base_uri, @before_property_validation, @after_property_validation))
82
85
  end
83
86
 
84
87
  def validate(data)
85
- validate_instance(Instance.new(data, '', root, '', nil, @before_property_validation, @after_property_validation))
88
+ validate_instance(Instance.new(data, '', root, '', @base_uri, @before_property_validation, @after_property_validation))
89
+ end
90
+
91
+ def valid_schema?
92
+ self.class.meta_schemer.valid?(root)
93
+ end
94
+
95
+ def validate_schema
96
+ self.class.meta_schemer.validate(root)
86
97
  end
87
98
 
88
99
  protected
89
100
 
101
+ attr_reader :root
102
+
90
103
  def valid_instance?(instance)
91
104
  validate_instance(instance).none?
92
105
  end
@@ -116,13 +129,13 @@ module JSONSchemer
116
129
  ref = schema['$ref']
117
130
  id = schema[id_keyword]
118
131
 
119
- instance.parent_uri = join_uri(instance.parent_uri, id)
120
-
121
132
  if ref
122
133
  validate_ref(instance, ref, &block)
123
134
  return
124
135
  end
125
136
 
137
+ instance.base_uri = join_uri(instance.base_uri, id)
138
+
126
139
  if format? && custom_format?(format)
127
140
  validate_custom_format(instance, formats.fetch(format), &block)
128
141
  end
@@ -224,7 +237,7 @@ module JSONSchemer
224
237
 
225
238
  private
226
239
 
227
- attr_reader :root, :formats, :keywords, :ref_resolver, :regexp_resolver
240
+ attr_reader :formats, :keywords, :ref_resolver, :regexp_resolver
228
241
 
229
242
  def id_keyword
230
243
  ID_KEYWORD
@@ -242,10 +255,11 @@ module JSONSchemer
242
255
  !custom_format?(format) && supported_format?(format)
243
256
  end
244
257
 
245
- def child(schema)
258
+ def child(schema, base_uri:)
246
259
  JSONSchemer.schema(
247
260
  schema,
248
261
  default_schema_class: self.class,
262
+ base_uri: base_uri,
249
263
  format: format?,
250
264
  formats: formats,
251
265
  keywords: keywords,
@@ -302,50 +316,38 @@ module JSONSchemer
302
316
  end
303
317
 
304
318
  def validate_ref(instance, ref, &block)
305
- if ref.start_with?('#')
306
- schema_pointer = ref.slice(1..-1)
307
- if valid_json_pointer?(schema_pointer)
308
- ref_pointer = Hana::Pointer.new(URI.decode_www_form_component(schema_pointer))
309
- subinstance = instance.merge(
310
- schema: ref_pointer.eval(root),
311
- schema_pointer: schema_pointer,
312
- parent_uri: (pointer_uri(root, ref_pointer) || instance.parent_uri)
313
- )
314
- validate_instance(subinstance, &block)
315
- return
316
- end
317
- end
318
-
319
- ref_uri = join_uri(instance.parent_uri, ref)
319
+ ref_uri = join_uri(instance.base_uri, ref)
320
320
 
321
+ ref_uri_pointer = ''
321
322
  if valid_json_pointer?(ref_uri.fragment)
322
- ref_pointer = Hana::Pointer.new(URI.decode_www_form_component(ref_uri.fragment))
323
- ref_root = resolve_ref(ref_uri)
324
- ref_object = child(ref_root)
325
- subinstance = instance.merge(
326
- schema: ref_pointer.eval(ref_root),
327
- schema_pointer: ref_uri.fragment,
328
- parent_uri: (pointer_uri(ref_root, ref_pointer) || ref_uri)
329
- )
330
- ref_object.validate_instance(subinstance, &block)
331
- elsif id = ids[ref_uri.to_s]
332
- subinstance = instance.merge(
333
- schema: id.fetch(:schema),
334
- schema_pointer: id.fetch(:pointer),
335
- parent_uri: ref_uri
336
- )
337
- validate_instance(subinstance, &block)
323
+ ref_uri_pointer = ref_uri.fragment
324
+ ref_uri.fragment = nil
325
+ end
326
+
327
+ ref_object = if ids.key?(ref_uri) || ref_uri.to_s == @base_uri.to_s
328
+ self
338
329
  else
339
- ref_root = resolve_ref(ref_uri)
340
- ref_object = child(ref_root)
341
- id = ref_object.ids[ref_uri.to_s] || { schema: ref_root, pointer: '' }
342
- subinstance = instance.merge(
343
- schema: id.fetch(:schema),
344
- schema_pointer: id.fetch(:pointer),
345
- parent_uri: ref_uri
346
- )
347
- ref_object.validate_instance(subinstance, &block)
330
+ child(resolve_ref(ref_uri), base_uri: ref_uri)
331
+ end
332
+
333
+ ref_schema, ref_schema_pointer = ref_object.ids[ref_uri] || [ref_object.root, '']
334
+
335
+ ref_uri_pointer_parts = Hana::Pointer.parse(URI.decode_www_form_component(ref_uri_pointer))
336
+ schema, base_uri = ref_uri_pointer_parts.reduce([ref_schema, ref_uri]) do |(obj, uri), token|
337
+ if obj.is_a?(Array)
338
+ [obj.fetch(token.to_i), uri]
339
+ else
340
+ [obj.fetch(token), join_uri(uri, obj[id_keyword])]
341
+ end
348
342
  end
343
+
344
+ subinstance = instance.merge(
345
+ schema: schema,
346
+ schema_pointer: "#{ref_schema_pointer}#{ref_uri_pointer}",
347
+ base_uri: base_uri
348
+ )
349
+
350
+ ref_object.validate_instance(subinstance, &block)
349
351
  end
350
352
 
351
353
  def validate_custom_format(instance, custom_format)
@@ -627,7 +629,7 @@ module JSONSchemer
627
629
 
628
630
  def join_uri(a, b)
629
631
  b = URI.parse(b) if b
630
- if a && b && a.relative? && b.relative?
632
+ uri = if a && b && a.relative? && b.relative?
631
633
  b
632
634
  elsif a && b
633
635
  URI.join(a, b)
@@ -636,34 +638,26 @@ module JSONSchemer
636
638
  else
637
639
  a
638
640
  end
641
+ uri.fragment = nil if uri.is_a?(URI) && uri.fragment == ''
642
+ uri
639
643
  end
640
644
 
641
- def pointer_uri(schema, pointer)
642
- uri_parts = nil
643
- pointer.reduce(schema) do |obj, token|
644
- next obj.fetch(token.to_i) if obj.is_a?(Array)
645
- if obj_id = obj[id_keyword]
646
- uri_parts ||= []
647
- uri_parts << obj_id
648
- end
649
- obj.fetch(token)
650
- end
651
- uri_parts ? URI.join(*uri_parts) : nil
652
- end
653
-
654
- def resolve_ids(schema, ids = {}, parent_uri = nil, pointer = '')
645
+ def resolve_ids(schema, ids = {}, base_uri = @base_uri, pointer = '')
655
646
  if schema.is_a?(Array)
656
- schema.each_with_index { |subschema, index| resolve_ids(subschema, ids, parent_uri, "#{pointer}/#{index}") }
647
+ schema.each_with_index { |subschema, index| resolve_ids(subschema, ids, base_uri, "#{pointer}/#{index}") }
657
648
  elsif schema.is_a?(Hash)
658
- uri = join_uri(parent_uri, schema[id_keyword])
649
+ uri = join_uri(base_uri, schema[id_keyword])
659
650
  schema.each do |key, value|
660
- if key == id_keyword && uri != parent_uri
661
- ids[uri.to_s] = {
662
- schema: schema,
663
- pointer: pointer
664
- }
651
+ case key
652
+ when id_keyword
653
+ ids[uri] ||= [schema, pointer]
654
+ when 'items', 'allOf', 'anyOf', 'oneOf', 'additionalItems', 'contains', 'additionalProperties', 'propertyNames', 'if', 'then', 'else', 'not'
655
+ resolve_ids(value, ids, uri, "#{pointer}/#{key}")
656
+ when 'properties', 'patternProperties', 'definitions', 'dependencies'
657
+ value.each do |subkey, subvalue|
658
+ resolve_ids(subvalue, ids, uri, "#{pointer}/#{key}/#{subkey}")
659
+ end
665
660
  end
666
- resolve_ids(value, ids, uri, "#{pointer}/#{key}")
667
661
  end
668
662
  end
669
663
  ids
@@ -0,0 +1,149 @@
1
+ {
2
+ "id": "http://json-schema.org/draft-04/schema#",
3
+ "$schema": "http://json-schema.org/draft-04/schema#",
4
+ "description": "Core schema meta-schema",
5
+ "definitions": {
6
+ "schemaArray": {
7
+ "type": "array",
8
+ "minItems": 1,
9
+ "items": { "$ref": "#" }
10
+ },
11
+ "positiveInteger": {
12
+ "type": "integer",
13
+ "minimum": 0
14
+ },
15
+ "positiveIntegerDefault0": {
16
+ "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
17
+ },
18
+ "simpleTypes": {
19
+ "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
20
+ },
21
+ "stringArray": {
22
+ "type": "array",
23
+ "items": { "type": "string" },
24
+ "minItems": 1,
25
+ "uniqueItems": true
26
+ }
27
+ },
28
+ "type": "object",
29
+ "properties": {
30
+ "id": {
31
+ "type": "string"
32
+ },
33
+ "$schema": {
34
+ "type": "string"
35
+ },
36
+ "title": {
37
+ "type": "string"
38
+ },
39
+ "description": {
40
+ "type": "string"
41
+ },
42
+ "default": {},
43
+ "multipleOf": {
44
+ "type": "number",
45
+ "minimum": 0,
46
+ "exclusiveMinimum": true
47
+ },
48
+ "maximum": {
49
+ "type": "number"
50
+ },
51
+ "exclusiveMaximum": {
52
+ "type": "boolean",
53
+ "default": false
54
+ },
55
+ "minimum": {
56
+ "type": "number"
57
+ },
58
+ "exclusiveMinimum": {
59
+ "type": "boolean",
60
+ "default": false
61
+ },
62
+ "maxLength": { "$ref": "#/definitions/positiveInteger" },
63
+ "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
64
+ "pattern": {
65
+ "type": "string",
66
+ "format": "regex"
67
+ },
68
+ "additionalItems": {
69
+ "anyOf": [
70
+ { "type": "boolean" },
71
+ { "$ref": "#" }
72
+ ],
73
+ "default": {}
74
+ },
75
+ "items": {
76
+ "anyOf": [
77
+ { "$ref": "#" },
78
+ { "$ref": "#/definitions/schemaArray" }
79
+ ],
80
+ "default": {}
81
+ },
82
+ "maxItems": { "$ref": "#/definitions/positiveInteger" },
83
+ "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
84
+ "uniqueItems": {
85
+ "type": "boolean",
86
+ "default": false
87
+ },
88
+ "maxProperties": { "$ref": "#/definitions/positiveInteger" },
89
+ "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
90
+ "required": { "$ref": "#/definitions/stringArray" },
91
+ "additionalProperties": {
92
+ "anyOf": [
93
+ { "type": "boolean" },
94
+ { "$ref": "#" }
95
+ ],
96
+ "default": {}
97
+ },
98
+ "definitions": {
99
+ "type": "object",
100
+ "additionalProperties": { "$ref": "#" },
101
+ "default": {}
102
+ },
103
+ "properties": {
104
+ "type": "object",
105
+ "additionalProperties": { "$ref": "#" },
106
+ "default": {}
107
+ },
108
+ "patternProperties": {
109
+ "type": "object",
110
+ "additionalProperties": { "$ref": "#" },
111
+ "default": {}
112
+ },
113
+ "dependencies": {
114
+ "type": "object",
115
+ "additionalProperties": {
116
+ "anyOf": [
117
+ { "$ref": "#" },
118
+ { "$ref": "#/definitions/stringArray" }
119
+ ]
120
+ }
121
+ },
122
+ "enum": {
123
+ "type": "array",
124
+ "minItems": 1,
125
+ "uniqueItems": true
126
+ },
127
+ "type": {
128
+ "anyOf": [
129
+ { "$ref": "#/definitions/simpleTypes" },
130
+ {
131
+ "type": "array",
132
+ "items": { "$ref": "#/definitions/simpleTypes" },
133
+ "minItems": 1,
134
+ "uniqueItems": true
135
+ }
136
+ ]
137
+ },
138
+ "format": { "type": "string" },
139
+ "allOf": { "$ref": "#/definitions/schemaArray" },
140
+ "anyOf": { "$ref": "#/definitions/schemaArray" },
141
+ "oneOf": { "$ref": "#/definitions/schemaArray" },
142
+ "not": { "$ref": "#" }
143
+ },
144
+ "dependencies": {
145
+ "exclusiveMaximum": [ "maximum" ],
146
+ "exclusiveMinimum": [ "minimum" ]
147
+ },
148
+ "default": {}
149
+ }
@@ -0,0 +1,155 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-06/schema#",
3
+ "$id": "http://json-schema.org/draft-06/schema#",
4
+ "title": "Core schema meta-schema",
5
+ "definitions": {
6
+ "schemaArray": {
7
+ "type": "array",
8
+ "minItems": 1,
9
+ "items": { "$ref": "#" }
10
+ },
11
+ "nonNegativeInteger": {
12
+ "type": "integer",
13
+ "minimum": 0
14
+ },
15
+ "nonNegativeIntegerDefault0": {
16
+ "allOf": [
17
+ { "$ref": "#/definitions/nonNegativeInteger" },
18
+ { "default": 0 }
19
+ ]
20
+ },
21
+ "simpleTypes": {
22
+ "enum": [
23
+ "array",
24
+ "boolean",
25
+ "integer",
26
+ "null",
27
+ "number",
28
+ "object",
29
+ "string"
30
+ ]
31
+ },
32
+ "stringArray": {
33
+ "type": "array",
34
+ "items": { "type": "string" },
35
+ "uniqueItems": true,
36
+ "default": []
37
+ }
38
+ },
39
+ "type": ["object", "boolean"],
40
+ "properties": {
41
+ "$id": {
42
+ "type": "string",
43
+ "format": "uri-reference"
44
+ },
45
+ "$schema": {
46
+ "type": "string",
47
+ "format": "uri"
48
+ },
49
+ "$ref": {
50
+ "type": "string",
51
+ "format": "uri-reference"
52
+ },
53
+ "title": {
54
+ "type": "string"
55
+ },
56
+ "description": {
57
+ "type": "string"
58
+ },
59
+ "default": {},
60
+ "examples": {
61
+ "type": "array",
62
+ "items": {}
63
+ },
64
+ "multipleOf": {
65
+ "type": "number",
66
+ "exclusiveMinimum": 0
67
+ },
68
+ "maximum": {
69
+ "type": "number"
70
+ },
71
+ "exclusiveMaximum": {
72
+ "type": "number"
73
+ },
74
+ "minimum": {
75
+ "type": "number"
76
+ },
77
+ "exclusiveMinimum": {
78
+ "type": "number"
79
+ },
80
+ "maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
81
+ "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
82
+ "pattern": {
83
+ "type": "string",
84
+ "format": "regex"
85
+ },
86
+ "additionalItems": { "$ref": "#" },
87
+ "items": {
88
+ "anyOf": [
89
+ { "$ref": "#" },
90
+ { "$ref": "#/definitions/schemaArray" }
91
+ ],
92
+ "default": {}
93
+ },
94
+ "maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
95
+ "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
96
+ "uniqueItems": {
97
+ "type": "boolean",
98
+ "default": false
99
+ },
100
+ "contains": { "$ref": "#" },
101
+ "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
102
+ "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
103
+ "required": { "$ref": "#/definitions/stringArray" },
104
+ "additionalProperties": { "$ref": "#" },
105
+ "definitions": {
106
+ "type": "object",
107
+ "additionalProperties": { "$ref": "#" },
108
+ "default": {}
109
+ },
110
+ "properties": {
111
+ "type": "object",
112
+ "additionalProperties": { "$ref": "#" },
113
+ "default": {}
114
+ },
115
+ "patternProperties": {
116
+ "type": "object",
117
+ "additionalProperties": { "$ref": "#" },
118
+ "propertyNames": { "format": "regex" },
119
+ "default": {}
120
+ },
121
+ "dependencies": {
122
+ "type": "object",
123
+ "additionalProperties": {
124
+ "anyOf": [
125
+ { "$ref": "#" },
126
+ { "$ref": "#/definitions/stringArray" }
127
+ ]
128
+ }
129
+ },
130
+ "propertyNames": { "$ref": "#" },
131
+ "const": {},
132
+ "enum": {
133
+ "type": "array",
134
+ "minItems": 1,
135
+ "uniqueItems": true
136
+ },
137
+ "type": {
138
+ "anyOf": [
139
+ { "$ref": "#/definitions/simpleTypes" },
140
+ {
141
+ "type": "array",
142
+ "items": { "$ref": "#/definitions/simpleTypes" },
143
+ "minItems": 1,
144
+ "uniqueItems": true
145
+ }
146
+ ]
147
+ },
148
+ "format": { "type": "string" },
149
+ "allOf": { "$ref": "#/definitions/schemaArray" },
150
+ "anyOf": { "$ref": "#/definitions/schemaArray" },
151
+ "oneOf": { "$ref": "#/definitions/schemaArray" },
152
+ "not": { "$ref": "#" }
153
+ },
154
+ "default": {}
155
+ }
@@ -0,0 +1,172 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "http://json-schema.org/draft-07/schema#",
4
+ "title": "Core schema meta-schema",
5
+ "definitions": {
6
+ "schemaArray": {
7
+ "type": "array",
8
+ "minItems": 1,
9
+ "items": { "$ref": "#" }
10
+ },
11
+ "nonNegativeInteger": {
12
+ "type": "integer",
13
+ "minimum": 0
14
+ },
15
+ "nonNegativeIntegerDefault0": {
16
+ "allOf": [
17
+ { "$ref": "#/definitions/nonNegativeInteger" },
18
+ { "default": 0 }
19
+ ]
20
+ },
21
+ "simpleTypes": {
22
+ "enum": [
23
+ "array",
24
+ "boolean",
25
+ "integer",
26
+ "null",
27
+ "number",
28
+ "object",
29
+ "string"
30
+ ]
31
+ },
32
+ "stringArray": {
33
+ "type": "array",
34
+ "items": { "type": "string" },
35
+ "uniqueItems": true,
36
+ "default": []
37
+ }
38
+ },
39
+ "type": ["object", "boolean"],
40
+ "properties": {
41
+ "$id": {
42
+ "type": "string",
43
+ "format": "uri-reference"
44
+ },
45
+ "$schema": {
46
+ "type": "string",
47
+ "format": "uri"
48
+ },
49
+ "$ref": {
50
+ "type": "string",
51
+ "format": "uri-reference"
52
+ },
53
+ "$comment": {
54
+ "type": "string"
55
+ },
56
+ "title": {
57
+ "type": "string"
58
+ },
59
+ "description": {
60
+ "type": "string"
61
+ },
62
+ "default": true,
63
+ "readOnly": {
64
+ "type": "boolean",
65
+ "default": false
66
+ },
67
+ "writeOnly": {
68
+ "type": "boolean",
69
+ "default": false
70
+ },
71
+ "examples": {
72
+ "type": "array",
73
+ "items": true
74
+ },
75
+ "multipleOf": {
76
+ "type": "number",
77
+ "exclusiveMinimum": 0
78
+ },
79
+ "maximum": {
80
+ "type": "number"
81
+ },
82
+ "exclusiveMaximum": {
83
+ "type": "number"
84
+ },
85
+ "minimum": {
86
+ "type": "number"
87
+ },
88
+ "exclusiveMinimum": {
89
+ "type": "number"
90
+ },
91
+ "maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
92
+ "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
93
+ "pattern": {
94
+ "type": "string",
95
+ "format": "regex"
96
+ },
97
+ "additionalItems": { "$ref": "#" },
98
+ "items": {
99
+ "anyOf": [
100
+ { "$ref": "#" },
101
+ { "$ref": "#/definitions/schemaArray" }
102
+ ],
103
+ "default": true
104
+ },
105
+ "maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
106
+ "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
107
+ "uniqueItems": {
108
+ "type": "boolean",
109
+ "default": false
110
+ },
111
+ "contains": { "$ref": "#" },
112
+ "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
113
+ "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
114
+ "required": { "$ref": "#/definitions/stringArray" },
115
+ "additionalProperties": { "$ref": "#" },
116
+ "definitions": {
117
+ "type": "object",
118
+ "additionalProperties": { "$ref": "#" },
119
+ "default": {}
120
+ },
121
+ "properties": {
122
+ "type": "object",
123
+ "additionalProperties": { "$ref": "#" },
124
+ "default": {}
125
+ },
126
+ "patternProperties": {
127
+ "type": "object",
128
+ "additionalProperties": { "$ref": "#" },
129
+ "propertyNames": { "format": "regex" },
130
+ "default": {}
131
+ },
132
+ "dependencies": {
133
+ "type": "object",
134
+ "additionalProperties": {
135
+ "anyOf": [
136
+ { "$ref": "#" },
137
+ { "$ref": "#/definitions/stringArray" }
138
+ ]
139
+ }
140
+ },
141
+ "propertyNames": { "$ref": "#" },
142
+ "const": true,
143
+ "enum": {
144
+ "type": "array",
145
+ "items": true,
146
+ "minItems": 1,
147
+ "uniqueItems": true
148
+ },
149
+ "type": {
150
+ "anyOf": [
151
+ { "$ref": "#/definitions/simpleTypes" },
152
+ {
153
+ "type": "array",
154
+ "items": { "$ref": "#/definitions/simpleTypes" },
155
+ "minItems": 1,
156
+ "uniqueItems": true
157
+ }
158
+ ]
159
+ },
160
+ "format": { "type": "string" },
161
+ "contentMediaType": { "type": "string" },
162
+ "contentEncoding": { "type": "string" },
163
+ "if": { "$ref": "#" },
164
+ "then": { "$ref": "#" },
165
+ "else": { "$ref": "#" },
166
+ "allOf": { "$ref": "#/definitions/schemaArray" },
167
+ "anyOf": { "$ref": "#/definitions/schemaArray" },
168
+ "oneOf": { "$ref": "#/definitions/schemaArray" },
169
+ "not": { "$ref": "#" }
170
+ },
171
+ "default": true
172
+ }
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module JSONSchemer
3
- VERSION = '0.2.25'
3
+ VERSION = '1.0.0'
4
4
  end
data/lib/json_schemer.rb CHANGED
@@ -9,7 +9,6 @@ require 'set'
9
9
  require 'time'
10
10
  require 'uri'
11
11
 
12
- require 'ecma-re-validator'
13
12
  require 'hana'
14
13
  require 'regexp_parser'
15
14
  require 'simpleidn'
@@ -20,6 +19,7 @@ require 'json_schemer/format/hostname'
20
19
  require 'json_schemer/format'
21
20
  require 'json_schemer/errors'
22
21
  require 'json_schemer/cached_resolver'
22
+ require 'json_schemer/ecma_regexp'
23
23
  require 'json_schemer/schema/base'
24
24
  require 'json_schemer/schema/draft4'
25
25
  require 'json_schemer/schema/draft6'
@@ -33,7 +33,9 @@ module JSONSchemer
33
33
  class InvalidRegexpResolution < StandardError; end
34
34
  class InvalidFileURI < StandardError; end
35
35
  class InvalidSymbolKey < StandardError; end
36
+ class InvalidEcmaRegexp < StandardError; end
36
37
 
38
+ DEFAULT_SCHEMA_CLASS = Schema::Draft7
37
39
  SCHEMA_CLASS_BY_META_SCHEMA = {
38
40
  'http://json-schema.org/schema#' => Schema::Draft4, # Version-less $schema deprecated after Draft 4
39
41
  'http://json-schema.org/draft-04/schema#' => Schema::Draft4,
@@ -52,33 +54,38 @@ module JSONSchemer
52
54
  end
53
55
 
54
56
  class << self
55
- def schema(schema, default_schema_class: Schema::Draft7, **options)
57
+ def schema(schema, default_schema_class: DEFAULT_SCHEMA_CLASS, **options)
56
58
  case schema
57
59
  when String
58
60
  schema = JSON.parse(schema)
59
61
  when Pathname
60
- uri = URI.parse(File.join('file:', URI::DEFAULT_PARSER.escape(schema.realpath.to_s)))
61
- if options.key?(:ref_resolver)
62
- schema = FILE_URI_REF_RESOLVER.call(uri)
62
+ base_uri = URI.parse(File.join('file:', URI::DEFAULT_PARSER.escape(schema.realpath.to_s)))
63
+ options[:base_uri] = base_uri
64
+ schema = if options.key?(:ref_resolver)
65
+ FILE_URI_REF_RESOLVER.call(base_uri)
63
66
  else
64
67
  ref_resolver = CachedResolver.new(&FILE_URI_REF_RESOLVER)
65
- schema = ref_resolver.call(uri)
66
68
  options[:ref_resolver] = ref_resolver
69
+ ref_resolver.call(base_uri)
67
70
  end
68
- schema[draft_class(schema, default_schema_class)::ID_KEYWORD] ||= uri.to_s
69
71
  end
70
- draft_class(schema, default_schema_class).new(schema, **options)
71
- end
72
-
73
- private
74
72
 
75
- def draft_class(schema, default_schema_class)
76
- if schema.is_a?(Hash) && schema.key?('$schema')
73
+ schema_class = if schema.is_a?(Hash) && schema.key?('$schema')
77
74
  meta_schema = schema.fetch('$schema')
78
75
  SCHEMA_CLASS_BY_META_SCHEMA[meta_schema] || raise(UnsupportedMetaSchema, meta_schema)
79
76
  else
80
77
  default_schema_class
81
78
  end
79
+
80
+ schema_class.new(schema, **options)
81
+ end
82
+
83
+ def valid_schema?(schema, default_schema_class: DEFAULT_SCHEMA_CLASS)
84
+ schema(schema, default_schema_class: default_schema_class).valid_schema?
85
+ end
86
+
87
+ def validate_schema(schema, default_schema_class: DEFAULT_SCHEMA_CLASS)
88
+ schema(schema, default_schema_class: default_schema_class).validate_schema
82
89
  end
83
90
  end
84
91
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json_schemer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.25
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Harsha
@@ -66,20 +66,6 @@ dependencies:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0.22'
69
- - !ruby/object:Gem::Dependency
70
- name: ecma-re-validator
71
- requirement: !ruby/object:Gem::Requirement
72
- requirements:
73
- - - "~>"
74
- - !ruby/object:Gem::Version
75
- version: '0.3'
76
- type: :runtime
77
- prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- requirements:
80
- - - "~>"
81
- - !ruby/object:Gem::Version
82
- version: '0.3'
83
69
  - !ruby/object:Gem::Dependency
84
70
  name: hana
85
71
  requirement: !ruby/object:Gem::Requirement
@@ -146,6 +132,7 @@ extra_rdoc_files: []
146
132
  files:
147
133
  - ".github/workflows/ci.yml"
148
134
  - ".gitignore"
135
+ - CHANGELOG.md
149
136
  - Gemfile
150
137
  - Gemfile.lock
151
138
  - LICENSE.txt
@@ -159,12 +146,16 @@ files:
159
146
  - json_schemer.gemspec
160
147
  - lib/json_schemer.rb
161
148
  - lib/json_schemer/cached_resolver.rb
149
+ - lib/json_schemer/ecma_regexp.rb
162
150
  - lib/json_schemer/errors.rb
163
151
  - lib/json_schemer/format.rb
164
152
  - lib/json_schemer/format/hostname.rb
165
153
  - lib/json_schemer/schema/base.rb
154
+ - lib/json_schemer/schema/draft4.json
166
155
  - lib/json_schemer/schema/draft4.rb
156
+ - lib/json_schemer/schema/draft6.json
167
157
  - lib/json_schemer/schema/draft6.rb
158
+ - lib/json_schemer/schema/draft7.json
168
159
  - lib/json_schemer/schema/draft7.rb
169
160
  - lib/json_schemer/version.rb
170
161
  homepage: https://github.com/davishmcclurg/json_schemer
@@ -179,7 +170,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
179
170
  requirements:
180
171
  - - ">="
181
172
  - !ruby/object:Gem::Version
182
- version: '2.4'
173
+ version: '2.5'
183
174
  required_rubygems_version: !ruby/object:Gem::Requirement
184
175
  requirements:
185
176
  - - ">="