json_schemer 0.2.25 → 1.0.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: 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
  - - ">="