json_schemer 0.2.20 → 0.2.22
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +6 -1
- data/Gemfile.lock +5 -5
- data/README.md +32 -1
- data/bin/rake +29 -0
- data/exe/json_schemer +62 -0
- data/lib/json_schemer/cached_resolver.rb +16 -0
- data/lib/json_schemer/format.rb +1 -1
- data/lib/json_schemer/schema/base.rb +28 -15
- data/lib/json_schemer/version.rb +1 -1
- data/lib/json_schemer.rb +5 -4
- metadata +12 -9
- data/lib/json_schemer/cached_ref_resolver.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bc9ff5876e71054ee601dc4464c99560e86557796ef792e36512e1f08725a065
|
4
|
+
data.tar.gz: 5ee26787348f3aecb1a591c38c7bccc7aaf4166fd14d1299999d426ecaa15893
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9b5dc77e4a136d9dd7ab774e530880188c3125647535707d67b9b09e615e538bfe826249fd53644d3dc452e8387667bc74a3e24ec21f847145bc4b92de15371d
|
7
|
+
data.tar.gz: 160ead197e1f0a61090e936a526e4753156b185e4049db6d5ca7efb9c8bbf4430ebd9a0eff952299fc5bdb0b93a063745d9be3e919bee80ce93c05864a9655b7
|
data/.github/workflows/ci.yml
CHANGED
@@ -23,4 +23,9 @@ jobs:
|
|
23
23
|
with:
|
24
24
|
ruby-version: ${{ matrix.ruby }}
|
25
25
|
bundler-cache: true
|
26
|
-
- run:
|
26
|
+
- run: |
|
27
|
+
mkdir -p tmp/gems
|
28
|
+
gem build json_schemer.gemspec
|
29
|
+
gem install --local --ignore-dependencies --no-document --install-dir tmp/gems json_schemer-*.gem
|
30
|
+
rm json_schemer-*.gem
|
31
|
+
bin/rake test
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
json_schemer (0.2.
|
4
|
+
json_schemer (0.2.22)
|
5
5
|
ecma-re-validator (~> 0.3)
|
6
6
|
hana (~> 1.3)
|
7
7
|
regexp_parser (~> 2.0)
|
@@ -13,9 +13,9 @@ GEM
|
|
13
13
|
ecma-re-validator (0.3.0)
|
14
14
|
regexp_parser (~> 2.0)
|
15
15
|
hana (1.3.7)
|
16
|
-
minitest (5.
|
17
|
-
rake (13.0.
|
18
|
-
regexp_parser (2.
|
16
|
+
minitest (5.15.0)
|
17
|
+
rake (13.0.6)
|
18
|
+
regexp_parser (2.3.1)
|
19
19
|
uri_template (0.7.0)
|
20
20
|
|
21
21
|
PLATFORMS
|
@@ -28,4 +28,4 @@ DEPENDENCIES
|
|
28
28
|
rake (~> 13.0)
|
29
29
|
|
30
30
|
BUNDLED WITH
|
31
|
-
2.
|
31
|
+
2.3.11
|
data/README.md
CHANGED
@@ -110,10 +110,41 @@ JSONSchemer.schema(
|
|
110
110
|
# 'net/http'/proc/lambda/respond_to?(:call)
|
111
111
|
# 'net/http': proc { |uri| JSON.parse(Net::HTTP.get(uri)) }
|
112
112
|
# default: proc { |uri| raise UnknownRef, uri.to_s }
|
113
|
-
ref_resolver: 'net/http'
|
113
|
+
ref_resolver: 'net/http',
|
114
|
+
|
115
|
+
# use different method to match regexes
|
116
|
+
# 'ecma'/'ruby'/proc/lambda/respond_to?(:call)
|
117
|
+
# default: 'ecma'
|
118
|
+
regexp_resolver: proc do |pattern|
|
119
|
+
RE2::Regexp.new(pattern)
|
120
|
+
end
|
114
121
|
)
|
115
122
|
```
|
116
123
|
|
124
|
+
## CLI
|
125
|
+
|
126
|
+
The `json_schemer` executable takes a JSON schema file as the first argument followed by one or more JSON data files to validate. If there are any validation errors, it outputs them and returns an error code.
|
127
|
+
|
128
|
+
Validation errors are output as single-line JSON objects. The `--errors` option can be used to limit the number of errors returned or prevent output entirely (and fail fast).
|
129
|
+
|
130
|
+
The schema or data can also be read from stdin using `-`.
|
131
|
+
|
132
|
+
```
|
133
|
+
% json_schemer --help
|
134
|
+
Usage:
|
135
|
+
json_schemer [options] <schema> <data>...
|
136
|
+
json_schemer [options] <schema> -
|
137
|
+
json_schemer [options] - <data>...
|
138
|
+
json_schemer -h | --help
|
139
|
+
json_schemer --version
|
140
|
+
|
141
|
+
Options:
|
142
|
+
-e, --errors MAX Maximum number of errors to output
|
143
|
+
Use "0" to validate with no output
|
144
|
+
-h, --help Show help
|
145
|
+
-v, --version Show version
|
146
|
+
```
|
147
|
+
|
117
148
|
## Development
|
118
149
|
|
119
150
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/bin/rake
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rake' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("rake", "rake")
|
data/exe/json_schemer
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
require 'optparse'
|
5
|
+
require 'pathname'
|
6
|
+
require 'json_schemer'
|
7
|
+
|
8
|
+
parser = OptionParser.new('Usage:', 32, ' ')
|
9
|
+
parser.separator(" #{parser.program_name} [options] <schema> <data>...")
|
10
|
+
parser.separator(" #{parser.program_name} [options] <schema> -")
|
11
|
+
parser.separator(" #{parser.program_name} [options] - <data>...")
|
12
|
+
parser.separator(" #{parser.program_name} -h | --help")
|
13
|
+
parser.separator(" #{parser.program_name} --version")
|
14
|
+
parser.separator('')
|
15
|
+
parser.separator('Options:')
|
16
|
+
parser.on('-e', '--errors MAX', Integer, 'Maximum number of errors to output', 'Use "0" to validate with no output')
|
17
|
+
parser.on_tail('-h', '--help', 'Show help')
|
18
|
+
parser.on_tail('-v', '--version', 'Show version')
|
19
|
+
|
20
|
+
options = {}
|
21
|
+
parser.parse!(:into => options)
|
22
|
+
|
23
|
+
if options[:help]
|
24
|
+
$stdout.puts(parser)
|
25
|
+
exit
|
26
|
+
end
|
27
|
+
|
28
|
+
if options[:version]
|
29
|
+
$stdout.puts("#{parser.program_name} #{JSONSchemer::VERSION}")
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
|
33
|
+
if ARGV.size == 0
|
34
|
+
$stderr.puts("#{parser.program_name}: no schema or data")
|
35
|
+
exit(false)
|
36
|
+
end
|
37
|
+
|
38
|
+
if ARGV.size == 1
|
39
|
+
$stderr.puts("#{parser.program_name}: no data")
|
40
|
+
exit(false)
|
41
|
+
end
|
42
|
+
|
43
|
+
if ARGV.count('-') > 1
|
44
|
+
$stderr.puts("#{parser.program_name}: multiple stdin")
|
45
|
+
exit(false)
|
46
|
+
end
|
47
|
+
|
48
|
+
errors = 0
|
49
|
+
schema = ARGF.file.is_a?(File) ? Pathname.new(ARGF.file.path) : ARGF.file.read
|
50
|
+
schemer = JSONSchemer.schema(schema)
|
51
|
+
|
52
|
+
while ARGV.any?
|
53
|
+
data = JSON.parse(ARGF.skip.file.read)
|
54
|
+
schemer.validate(data).each do |error|
|
55
|
+
exit(false) if options[:errors] == 0
|
56
|
+
errors += 1
|
57
|
+
$stdout.puts(JSON.generate(error))
|
58
|
+
exit(false) if options[:errors] == errors
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
exit(errors.zero?)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module JSONSchemer
|
3
|
+
class CachedResolver
|
4
|
+
def initialize(&resolver)
|
5
|
+
@resolver = resolver
|
6
|
+
@cache = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(*args)
|
10
|
+
@cache[args] = @resolver.call(*args) unless @cache.key?(args)
|
11
|
+
@cache[args]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class CachedRefResolver < CachedResolver; end
|
16
|
+
end
|
data/lib/json_schemer/format.rb
CHANGED
@@ -30,6 +30,14 @@ module JSONSchemer
|
|
30
30
|
:eol => '\z'
|
31
31
|
}.freeze
|
32
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
|
+
|
33
41
|
INSERT_DEFAULT_PROPERTY = proc do |data, property, property_schema, _parent|
|
34
42
|
if !data.key?(property) && property_schema.is_a?(Hash) && property_schema.key?('default')
|
35
43
|
data[property] = property_schema.fetch('default').clone
|
@@ -44,7 +52,8 @@ module JSONSchemer
|
|
44
52
|
after_property_validation: nil,
|
45
53
|
formats: nil,
|
46
54
|
keywords: nil,
|
47
|
-
ref_resolver: DEFAULT_REF_RESOLVER
|
55
|
+
ref_resolver: DEFAULT_REF_RESOLVER,
|
56
|
+
regexp_resolver: 'ecma'
|
48
57
|
)
|
49
58
|
raise InvalidSymbolKey, 'schemas must use string keys' if schema.is_a?(Hash) && !schema.empty? && !schema.first.first.is_a?(String)
|
50
59
|
@root = schema
|
@@ -54,7 +63,15 @@ module JSONSchemer
|
|
54
63
|
@after_property_validation = [*after_property_validation]
|
55
64
|
@formats = formats
|
56
65
|
@keywords = keywords
|
57
|
-
@ref_resolver = ref_resolver == 'net/http' ?
|
66
|
+
@ref_resolver = ref_resolver == 'net/http' ? CachedResolver.new(&NET_HTTP_REF_RESOLVER) : ref_resolver
|
67
|
+
@regexp_resolver = case regexp_resolver
|
68
|
+
when 'ecma'
|
69
|
+
CachedResolver.new(&ECMA_262_REGEXP_RESOLVER)
|
70
|
+
when 'ruby'
|
71
|
+
CachedResolver.new(&Regexp.method(:new))
|
72
|
+
else
|
73
|
+
regexp_resolver
|
74
|
+
end
|
58
75
|
end
|
59
76
|
|
60
77
|
def valid?(data)
|
@@ -204,7 +221,7 @@ module JSONSchemer
|
|
204
221
|
|
205
222
|
private
|
206
223
|
|
207
|
-
attr_reader :root, :formats, :keywords, :ref_resolver
|
224
|
+
attr_reader :root, :formats, :keywords, :ref_resolver, :regexp_resolver
|
208
225
|
|
209
226
|
def id_keyword
|
210
227
|
ID_KEYWORD
|
@@ -228,7 +245,8 @@ module JSONSchemer
|
|
228
245
|
format: format?,
|
229
246
|
formats: formats,
|
230
247
|
keywords: keywords,
|
231
|
-
ref_resolver: ref_resolver
|
248
|
+
ref_resolver: ref_resolver,
|
249
|
+
regexp_resolver: regexp_resolver
|
232
250
|
)
|
233
251
|
end
|
234
252
|
|
@@ -398,7 +416,7 @@ module JSONSchemer
|
|
398
416
|
|
399
417
|
yield error(instance, 'maxLength') if max_length && data.size > max_length
|
400
418
|
yield error(instance, 'minLength') if min_length && data.size < min_length
|
401
|
-
yield error(instance, 'pattern') if pattern &&
|
419
|
+
yield error(instance, 'pattern') if pattern && resolve_regexp(pattern) !~ data
|
402
420
|
yield error(instance, 'format') if format? && spec_format?(format) && !valid_spec_format?(data, format)
|
403
421
|
|
404
422
|
if content_encoding || content_media_type
|
@@ -551,7 +569,7 @@ module JSONSchemer
|
|
551
569
|
|
552
570
|
if pattern_properties
|
553
571
|
regex_pattern_properties ||= pattern_properties.map do |pattern, property_schema|
|
554
|
-
[pattern,
|
572
|
+
[pattern, resolve_regexp(pattern), property_schema]
|
555
573
|
end
|
556
574
|
regex_pattern_properties.each do |pattern, regex, property_schema|
|
557
575
|
if regex.match?(key)
|
@@ -596,15 +614,6 @@ module JSONSchemer
|
|
596
614
|
nil
|
597
615
|
end
|
598
616
|
|
599
|
-
def ecma_262_regex(pattern)
|
600
|
-
@ecma_262_regex ||= {}
|
601
|
-
@ecma_262_regex[pattern] ||= Regexp.new(
|
602
|
-
Regexp::Scanner.scan(pattern).map do |type, token, text|
|
603
|
-
type == :anchor ? RUBY_REGEX_ANCHORS_TO_ECMA_262.fetch(token, text) : text
|
604
|
-
end.join
|
605
|
-
)
|
606
|
-
end
|
607
|
-
|
608
617
|
def join_uri(a, b)
|
609
618
|
b = URI.parse(b) if b
|
610
619
|
if a && b && a.relative? && b.relative?
|
@@ -652,6 +661,10 @@ module JSONSchemer
|
|
652
661
|
def resolve_ref(uri)
|
653
662
|
ref_resolver.call(uri) || raise(InvalidRefResolution, uri.to_s)
|
654
663
|
end
|
664
|
+
|
665
|
+
def resolve_regexp(pattern)
|
666
|
+
regexp_resolver.call(pattern) || raise(InvalidRegexpResolution, pattern)
|
667
|
+
end
|
655
668
|
end
|
656
669
|
end
|
657
670
|
end
|
data/lib/json_schemer/version.rb
CHANGED
data/lib/json_schemer.rb
CHANGED
@@ -17,7 +17,7 @@ require 'uri_template'
|
|
17
17
|
require 'json_schemer/version'
|
18
18
|
require 'json_schemer/format'
|
19
19
|
require 'json_schemer/errors'
|
20
|
-
require 'json_schemer/
|
20
|
+
require 'json_schemer/cached_resolver'
|
21
21
|
require 'json_schemer/schema/base'
|
22
22
|
require 'json_schemer/schema/draft4'
|
23
23
|
require 'json_schemer/schema/draft6'
|
@@ -27,6 +27,7 @@ module JSONSchemer
|
|
27
27
|
class UnsupportedMetaSchema < StandardError; end
|
28
28
|
class UnknownRef < StandardError; end
|
29
29
|
class InvalidRefResolution < StandardError; end
|
30
|
+
class InvalidRegexpResolution < StandardError; end
|
30
31
|
class InvalidFileURI < StandardError; end
|
31
32
|
class InvalidSymbolKey < StandardError; end
|
32
33
|
|
@@ -46,7 +47,7 @@ module JSONSchemer
|
|
46
47
|
raise InvalidFileURI, 'cannot have a host (use `file:///`)' if uri.host && !uri.host.empty?
|
47
48
|
path = uri.path
|
48
49
|
path = path[1..-1] if path.match?(WINDOWS_URI_PATH_REGEX)
|
49
|
-
JSON.parse(File.read(path))
|
50
|
+
JSON.parse(File.read(URI::DEFAULT_PARSER.unescape(path)))
|
50
51
|
end
|
51
52
|
|
52
53
|
class << self
|
@@ -55,11 +56,11 @@ module JSONSchemer
|
|
55
56
|
when String
|
56
57
|
schema = JSON.parse(schema)
|
57
58
|
when Pathname
|
58
|
-
uri = URI.parse(File.join('file:', schema.realpath))
|
59
|
+
uri = URI.parse(File.join('file:', URI::DEFAULT_PARSER.escape(schema.realpath.to_s)))
|
59
60
|
if options.key?(:ref_resolver)
|
60
61
|
schema = FILE_URI_REF_RESOLVER.call(uri)
|
61
62
|
else
|
62
|
-
ref_resolver =
|
63
|
+
ref_resolver = CachedResolver.new(&FILE_URI_REF_RESOLVER)
|
63
64
|
schema = ref_resolver.call(uri)
|
64
65
|
options[:ref_resolver] = ref_resolver
|
65
66
|
end
|
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: 0.2.
|
4
|
+
version: 0.2.22
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Harsha
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-10-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -108,10 +108,11 @@ dependencies:
|
|
108
108
|
- - "~>"
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: '2.0'
|
111
|
-
description:
|
111
|
+
description:
|
112
112
|
email:
|
113
113
|
- davishmcclurg@gmail.com
|
114
|
-
executables:
|
114
|
+
executables:
|
115
|
+
- json_schemer
|
115
116
|
extensions: []
|
116
117
|
extra_rdoc_files: []
|
117
118
|
files:
|
@@ -123,10 +124,12 @@ files:
|
|
123
124
|
- README.md
|
124
125
|
- Rakefile
|
125
126
|
- bin/console
|
127
|
+
- bin/rake
|
126
128
|
- bin/setup
|
129
|
+
- exe/json_schemer
|
127
130
|
- json_schemer.gemspec
|
128
131
|
- lib/json_schemer.rb
|
129
|
-
- lib/json_schemer/
|
132
|
+
- lib/json_schemer/cached_resolver.rb
|
130
133
|
- lib/json_schemer/errors.rb
|
131
134
|
- lib/json_schemer/format.rb
|
132
135
|
- lib/json_schemer/schema/base.rb
|
@@ -138,7 +141,7 @@ homepage: https://github.com/davishmcclurg/json_schemer
|
|
138
141
|
licenses:
|
139
142
|
- MIT
|
140
143
|
metadata: {}
|
141
|
-
post_install_message:
|
144
|
+
post_install_message:
|
142
145
|
rdoc_options: []
|
143
146
|
require_paths:
|
144
147
|
- lib
|
@@ -153,8 +156,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
153
156
|
- !ruby/object:Gem::Version
|
154
157
|
version: '0'
|
155
158
|
requirements: []
|
156
|
-
rubygems_version: 3.
|
157
|
-
signing_key:
|
159
|
+
rubygems_version: 3.3.7
|
160
|
+
signing_key:
|
158
161
|
specification_version: 4
|
159
162
|
summary: JSON Schema validator. Supports drafts 4, 6, and 7.
|
160
163
|
test_files: []
|
@@ -1,14 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
module JSONSchemer
|
3
|
-
class CachedRefResolver
|
4
|
-
def initialize(&ref_resolver)
|
5
|
-
@ref_resolver = ref_resolver
|
6
|
-
@cache = {}
|
7
|
-
end
|
8
|
-
|
9
|
-
def call(uri)
|
10
|
-
@cache[uri] = @ref_resolver.call(uri) unless @cache.key?(uri)
|
11
|
-
@cache[uri]
|
12
|
-
end
|
13
|
-
end
|
14
|
-
end
|