json_schemer 0.2.16 → 0.2.25
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 +20 -5
- data/Gemfile.lock +26 -10
- data/README.md +32 -1
- data/bin/hostname_character_classes +42 -0
- data/bin/rake +29 -0
- data/exe/json_schemer +62 -0
- data/json_schemer.gemspec +5 -11
- data/lib/json_schemer/cached_resolver.rb +16 -0
- data/lib/json_schemer/format/hostname.rb +58 -0
- data/lib/json_schemer/format.rb +29 -18
- data/lib/json_schemer/schema/base.rb +47 -24
- data/lib/json_schemer/version.rb +1 -1
- data/lib/json_schemer.rb +24 -12
- metadata +51 -18
- 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: cd29a78d2cbd45dc0409c551d0e3c11e39a801204bab75e22ded249c4ef8edf8
|
4
|
+
data.tar.gz: 9d0556d1f4723ae50b9a038595f44ffdc9c0dcc8d05d9b9771f58248fc88a764
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d3bb75eb37c9ad70776fbf8d7fc5c1d7cc4a79bf7c68e2430dbf30f256fb0a3e1cddf718fa1799486814058d98455ef47b62baa514b1b49beafe0c7fc6a489dc
|
7
|
+
data.tar.gz: eea979521c2d65afef0325f94513ecf64bf604c562af41afae2ec258a4449b70a28aee927be925681ceecc434ba716271d7bbc6a713f8413b0f22710b7ed7c9c
|
data/.github/workflows/ci.yml
CHANGED
@@ -1,16 +1,31 @@
|
|
1
1
|
name: ci
|
2
2
|
on: [push, pull_request]
|
3
3
|
jobs:
|
4
|
-
|
4
|
+
test:
|
5
5
|
strategy:
|
6
|
+
fail-fast: false
|
6
7
|
matrix:
|
7
|
-
|
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]
|
10
|
+
exclude:
|
11
|
+
- os: windows-latest
|
12
|
+
ruby: jruby
|
13
|
+
- os: windows-latest
|
14
|
+
ruby: jruby-head
|
15
|
+
- os: windows-latest
|
16
|
+
ruby: truffleruby
|
17
|
+
- os: windows-latest
|
18
|
+
ruby: truffleruby-head
|
19
|
+
runs-on: ${{ matrix.os }}
|
9
20
|
steps:
|
10
21
|
- uses: actions/checkout@v2
|
11
22
|
- uses: ruby/setup-ruby@v1
|
12
23
|
with:
|
13
24
|
ruby-version: ${{ matrix.ruby }}
|
25
|
+
bundler-cache: true
|
14
26
|
- run: |
|
15
|
-
|
16
|
-
|
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,24 +1,39 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
json_schemer (0.2.
|
5
|
-
ecma-re-validator (~> 0.
|
4
|
+
json_schemer (0.2.25)
|
5
|
+
ecma-re-validator (~> 0.3)
|
6
6
|
hana (~> 1.3)
|
7
|
-
regexp_parser (~>
|
7
|
+
regexp_parser (~> 2.0)
|
8
|
+
simpleidn (~> 0.2)
|
8
9
|
uri_template (~> 0.7)
|
9
10
|
|
10
11
|
GEM
|
11
12
|
remote: https://rubygems.org/
|
12
13
|
specs:
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
docile (1.4.0)
|
15
|
+
ecma-re-validator (0.3.0)
|
16
|
+
regexp_parser (~> 2.0)
|
17
|
+
hana (1.3.7)
|
18
|
+
minitest (5.15.0)
|
19
|
+
rake (13.0.6)
|
20
|
+
regexp_parser (2.6.1)
|
21
|
+
simplecov (0.22.0)
|
22
|
+
docile (~> 1.1)
|
23
|
+
simplecov-html (~> 0.11)
|
24
|
+
simplecov_json_formatter (~> 0.1)
|
25
|
+
simplecov-html (0.12.3)
|
26
|
+
simplecov_json_formatter (0.1.4)
|
27
|
+
simpleidn (0.2.1)
|
28
|
+
unf (~> 0.1.4)
|
29
|
+
unf (0.1.4)
|
30
|
+
unf_ext
|
31
|
+
unf (0.1.4-java)
|
32
|
+
unf_ext (0.0.8.2)
|
19
33
|
uri_template (0.7.0)
|
20
34
|
|
21
35
|
PLATFORMS
|
36
|
+
java
|
22
37
|
ruby
|
23
38
|
|
24
39
|
DEPENDENCIES
|
@@ -26,6 +41,7 @@ DEPENDENCIES
|
|
26
41
|
json_schemer!
|
27
42
|
minitest (~> 5.0)
|
28
43
|
rake (~> 13.0)
|
44
|
+
simplecov (~> 0.22)
|
29
45
|
|
30
46
|
BUNDLED WITH
|
31
|
-
2.
|
47
|
+
2.3.25
|
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.
|
@@ -0,0 +1,42 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'open-uri'
|
4
|
+
require 'csv'
|
5
|
+
|
6
|
+
# https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.1
|
7
|
+
# https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.2
|
8
|
+
|
9
|
+
csv_options = { :col_sep => ';', :skip_blanks => true, :skip_lines => /\A#/ }
|
10
|
+
|
11
|
+
unicode_data = URI('https://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt')
|
12
|
+
derived_joining_type = URI('https://www.unicode.org/Public/UCD/latest/ucd/extracted/DerivedJoiningType.txt')
|
13
|
+
|
14
|
+
# https://www.unicode.org/reports/tr44/#Canonical_Combining_Class_Values
|
15
|
+
virama_canonical_combining_class = '9'
|
16
|
+
|
17
|
+
virama_codes = CSV.new(unicode_data.read, **csv_options).select do |code, _name, _category, canonical_combining_class|
|
18
|
+
canonical_combining_class == virama_canonical_combining_class
|
19
|
+
end.map(&:first)
|
20
|
+
|
21
|
+
# https://www.unicode.org/reports/tr44/#Default_Values
|
22
|
+
# https://www.unicode.org/reports/tr44/#Derived_Extracted
|
23
|
+
codes_by_joining_type = CSV.new(derived_joining_type.read, **csv_options).group_by do |_code, joining_type|
|
24
|
+
joining_type.gsub(/#.+/, '').strip
|
25
|
+
end.transform_values do |rows|
|
26
|
+
rows.map do |code, _joining_type|
|
27
|
+
code.strip
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def codes_to_character_class(codes)
|
32
|
+
characters = codes.map do |code|
|
33
|
+
code.gsub(/(\h+)/, '\u{\1}').gsub('..', '-')
|
34
|
+
end
|
35
|
+
"[#{characters.join}]"
|
36
|
+
end
|
37
|
+
|
38
|
+
puts "VIRAMA_CHARACTER_CLASS = '#{codes_to_character_class(virama_codes)}'"
|
39
|
+
|
40
|
+
codes_by_joining_type.slice('L', 'D', 'T', 'R').each do |joining_type, codes|
|
41
|
+
puts "JOINING_TYPE_#{joining_type}_CHARACTER_CLASS = '#{codes_to_character_class(codes)}'"
|
42
|
+
end
|
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?)
|
data/json_schemer.gemspec
CHANGED
@@ -20,22 +20,16 @@ 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 = '
|
23
|
+
spec.required_ruby_version = '>= 2.4'
|
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
|
+
spec.add_development_dependency "simplecov", "~> 0.22"
|
28
29
|
|
29
|
-
|
30
|
-
# spec.add_development_dependency "jschema", "~> 0.2.1"
|
31
|
-
# spec.add_development_dependency "json-schema", "~> 2.8.0"
|
32
|
-
# spec.add_development_dependency "json_schema", "~> 0.17.0"
|
33
|
-
# spec.add_development_dependency "json_validation", "~> 0.1.0"
|
34
|
-
# spec.add_development_dependency "jsonschema", "~> 2.0.2"
|
35
|
-
# spec.add_development_dependency "rj_schema", "~> 0.2.0"
|
36
|
-
|
37
|
-
spec.add_runtime_dependency "ecma-re-validator", "~> 0.2"
|
30
|
+
spec.add_runtime_dependency "ecma-re-validator", "~> 0.3"
|
38
31
|
spec.add_runtime_dependency "hana", "~> 1.3"
|
32
|
+
spec.add_runtime_dependency "regexp_parser", "~> 2.0"
|
33
|
+
spec.add_runtime_dependency "simpleidn", "~> 0.2"
|
39
34
|
spec.add_runtime_dependency "uri_template", "~> 0.7"
|
40
|
-
spec.add_runtime_dependency "regexp_parser", "~> 1.5"
|
41
35
|
end
|
@@ -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
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module JSONSchemer
|
3
|
+
module Format
|
4
|
+
module Hostname
|
5
|
+
# https://datatracker.ietf.org/doc/html/rfc5892#section-2.1
|
6
|
+
MARKS = '\p{Mn}\p{Mc}'
|
7
|
+
LETTER_DIGITS = "\\p{Ll}\\p{Lu}\\p{Lo}\\p{Nd}\\p{Lm}#{MARKS}"
|
8
|
+
# https://datatracker.ietf.org/doc/html/rfc5892#section-2.6
|
9
|
+
EXCEPTIONS_PVALID = '\u{06FD}\u{06FE}\u{0F0B}\u{3007}' # \u{00DF}\u{03C2} covered by \p{Ll}
|
10
|
+
EXCEPTIONS_DISALLOWED = '\u{0640}\u{07FA}\u{302E}\u{302F}\u{3031}\u{3032}\u{3033}\u{3034}\u{3035}\u{303B}'
|
11
|
+
LABEL_CHARACTER_CLASS = "[#{LETTER_DIGITS}#{EXCEPTIONS_PVALID}&&[^#{EXCEPTIONS_DISALLOWED}]]"
|
12
|
+
# https://datatracker.ietf.org/doc/html/rfc5891#section-4.2.3.2
|
13
|
+
LEADING_CHARACTER_CLASS = "[#{LABEL_CHARACTER_CLASS}&&[^#{MARKS}]]"
|
14
|
+
LABEL_REGEX_STRING = "#{LEADING_CHARACTER_CLASS}([#{LABEL_CHARACTER_CLASS}\-]*#{LABEL_CHARACTER_CLASS})?"
|
15
|
+
HOSTNAME_REGEX = /\A(#{LABEL_REGEX_STRING}\.)*#{LABEL_REGEX_STRING}\z/i.freeze
|
16
|
+
# bin/hostname_character_classes
|
17
|
+
VIRAMA_CHARACTER_CLASS = '[\u{094D}\u{09CD}\u{0A4D}\u{0ACD}\u{0B4D}\u{0BCD}\u{0C4D}\u{0CCD}\u{0D3B}\u{0D3C}\u{0D4D}\u{0DCA}\u{0E3A}\u{0EBA}\u{0F84}\u{1039}\u{103A}\u{1714}\u{1715}\u{1734}\u{17D2}\u{1A60}\u{1B44}\u{1BAA}\u{1BAB}\u{1BF2}\u{1BF3}\u{2D7F}\u{A806}\u{A82C}\u{A8C4}\u{A953}\u{A9C0}\u{AAF6}\u{ABED}\u{10A3F}\u{11046}\u{11070}\u{1107F}\u{110B9}\u{11133}\u{11134}\u{111C0}\u{11235}\u{112EA}\u{1134D}\u{11442}\u{114C2}\u{115BF}\u{1163F}\u{116B6}\u{1172B}\u{11839}\u{1193D}\u{1193E}\u{119E0}\u{11A34}\u{11A47}\u{11A99}\u{11C3F}\u{11D44}\u{11D45}\u{11D97}\u{11F41}\u{11F42}]'
|
18
|
+
JOINING_TYPE_L_CHARACTER_CLASS = '[\u{A872}\u{10ACD}\u{10AD7}\u{10D00}\u{10FCB}]'
|
19
|
+
JOINING_TYPE_D_CHARACTER_CLASS = '[\u{0620}\u{0626}\u{0628}\u{062A}-\u{062E}\u{0633}-\u{063F}\u{0641}-\u{0647}\u{0649}-\u{064A}\u{066E}-\u{066F}\u{0678}-\u{0687}\u{069A}-\u{06BF}\u{06C1}-\u{06C2}\u{06CC}\u{06CE}\u{06D0}-\u{06D1}\u{06FA}-\u{06FC}\u{06FF}\u{0712}-\u{0714}\u{071A}-\u{071D}\u{071F}-\u{0727}\u{0729}\u{072B}\u{072D}-\u{072E}\u{074E}-\u{0758}\u{075C}-\u{076A}\u{076D}-\u{0770}\u{0772}\u{0775}-\u{0777}\u{077A}-\u{077F}\u{07CA}-\u{07EA}\u{0841}-\u{0845}\u{0848}\u{084A}-\u{0853}\u{0855}\u{0860}\u{0862}-\u{0865}\u{0868}\u{0886}\u{0889}-\u{088D}\u{08A0}-\u{08A9}\u{08AF}-\u{08B0}\u{08B3}-\u{08B8}\u{08BA}-\u{08C8}\u{1807}\u{1820}-\u{1842}\u{1843}\u{1844}-\u{1878}\u{1887}-\u{18A8}\u{18AA}\u{A840}-\u{A871}\u{10AC0}-\u{10AC4}\u{10AD3}-\u{10AD6}\u{10AD8}-\u{10ADC}\u{10ADE}-\u{10AE0}\u{10AEB}-\u{10AEE}\u{10B80}\u{10B82}\u{10B86}-\u{10B88}\u{10B8A}-\u{10B8B}\u{10B8D}\u{10B90}\u{10BAD}-\u{10BAE}\u{10D01}-\u{10D21}\u{10D23}\u{10F30}-\u{10F32}\u{10F34}-\u{10F44}\u{10F51}-\u{10F53}\u{10F70}-\u{10F73}\u{10F76}-\u{10F81}\u{10FB0}\u{10FB2}-\u{10FB3}\u{10FB8}\u{10FBB}-\u{10FBC}\u{10FBE}-\u{10FBF}\u{10FC1}\u{10FC4}\u{10FCA}\u{1E900}-\u{1E943}]'
|
20
|
+
JOINING_TYPE_T_CHARACTER_CLASS = '[\u{00AD}\u{0300}-\u{036F}\u{0483}-\u{0487}\u{0488}-\u{0489}\u{0591}-\u{05BD}\u{05BF}\u{05C1}-\u{05C2}\u{05C4}-\u{05C5}\u{05C7}\u{0610}-\u{061A}\u{061C}\u{064B}-\u{065F}\u{0670}\u{06D6}-\u{06DC}\u{06DF}-\u{06E4}\u{06E7}-\u{06E8}\u{06EA}-\u{06ED}\u{070F}\u{0711}\u{0730}-\u{074A}\u{07A6}-\u{07B0}\u{07EB}-\u{07F3}\u{07FD}\u{0816}-\u{0819}\u{081B}-\u{0823}\u{0825}-\u{0827}\u{0829}-\u{082D}\u{0859}-\u{085B}\u{0898}-\u{089F}\u{08CA}-\u{08E1}\u{08E3}-\u{0902}\u{093A}\u{093C}\u{0941}-\u{0948}\u{094D}\u{0951}-\u{0957}\u{0962}-\u{0963}\u{0981}\u{09BC}\u{09C1}-\u{09C4}\u{09CD}\u{09E2}-\u{09E3}\u{09FE}\u{0A01}-\u{0A02}\u{0A3C}\u{0A41}-\u{0A42}\u{0A47}-\u{0A48}\u{0A4B}-\u{0A4D}\u{0A51}\u{0A70}-\u{0A71}\u{0A75}\u{0A81}-\u{0A82}\u{0ABC}\u{0AC1}-\u{0AC5}\u{0AC7}-\u{0AC8}\u{0ACD}\u{0AE2}-\u{0AE3}\u{0AFA}-\u{0AFF}\u{0B01}\u{0B3C}\u{0B3F}\u{0B41}-\u{0B44}\u{0B4D}\u{0B55}-\u{0B56}\u{0B62}-\u{0B63}\u{0B82}\u{0BC0}\u{0BCD}\u{0C00}\u{0C04}\u{0C3C}\u{0C3E}-\u{0C40}\u{0C46}-\u{0C48}\u{0C4A}-\u{0C4D}\u{0C55}-\u{0C56}\u{0C62}-\u{0C63}\u{0C81}\u{0CBC}\u{0CBF}\u{0CC6}\u{0CCC}-\u{0CCD}\u{0CE2}-\u{0CE3}\u{0D00}-\u{0D01}\u{0D3B}-\u{0D3C}\u{0D41}-\u{0D44}\u{0D4D}\u{0D62}-\u{0D63}\u{0D81}\u{0DCA}\u{0DD2}-\u{0DD4}\u{0DD6}\u{0E31}\u{0E34}-\u{0E3A}\u{0E47}-\u{0E4E}\u{0EB1}\u{0EB4}-\u{0EBC}\u{0EC8}-\u{0ECE}\u{0F18}-\u{0F19}\u{0F35}\u{0F37}\u{0F39}\u{0F71}-\u{0F7E}\u{0F80}-\u{0F84}\u{0F86}-\u{0F87}\u{0F8D}-\u{0F97}\u{0F99}-\u{0FBC}\u{0FC6}\u{102D}-\u{1030}\u{1032}-\u{1037}\u{1039}-\u{103A}\u{103D}-\u{103E}\u{1058}-\u{1059}\u{105E}-\u{1060}\u{1071}-\u{1074}\u{1082}\u{1085}-\u{1086}\u{108D}\u{109D}\u{135D}-\u{135F}\u{1712}-\u{1714}\u{1732}-\u{1733}\u{1752}-\u{1753}\u{1772}-\u{1773}\u{17B4}-\u{17B5}\u{17B7}-\u{17BD}\u{17C6}\u{17C9}-\u{17D3}\u{17DD}\u{180B}-\u{180D}\u{180F}\u{1885}-\u{1886}\u{18A9}\u{1920}-\u{1922}\u{1927}-\u{1928}\u{1932}\u{1939}-\u{193B}\u{1A17}-\u{1A18}\u{1A1B}\u{1A56}\u{1A58}-\u{1A5E}\u{1A60}\u{1A62}\u{1A65}-\u{1A6C}\u{1A73}-\u{1A7C}\u{1A7F}\u{1AB0}-\u{1ABD}\u{1ABE}\u{1ABF}-\u{1ACE}\u{1B00}-\u{1B03}\u{1B34}\u{1B36}-\u{1B3A}\u{1B3C}\u{1B42}\u{1B6B}-\u{1B73}\u{1B80}-\u{1B81}\u{1BA2}-\u{1BA5}\u{1BA8}-\u{1BA9}\u{1BAB}-\u{1BAD}\u{1BE6}\u{1BE8}-\u{1BE9}\u{1BED}\u{1BEF}-\u{1BF1}\u{1C2C}-\u{1C33}\u{1C36}-\u{1C37}\u{1CD0}-\u{1CD2}\u{1CD4}-\u{1CE0}\u{1CE2}-\u{1CE8}\u{1CED}\u{1CF4}\u{1CF8}-\u{1CF9}\u{1DC0}-\u{1DFF}\u{200B}\u{200E}-\u{200F}\u{202A}-\u{202E}\u{2060}-\u{2064}\u{206A}-\u{206F}\u{20D0}-\u{20DC}\u{20DD}-\u{20E0}\u{20E1}\u{20E2}-\u{20E4}\u{20E5}-\u{20F0}\u{2CEF}-\u{2CF1}\u{2D7F}\u{2DE0}-\u{2DFF}\u{302A}-\u{302D}\u{3099}-\u{309A}\u{A66F}\u{A670}-\u{A672}\u{A674}-\u{A67D}\u{A69E}-\u{A69F}\u{A6F0}-\u{A6F1}\u{A802}\u{A806}\u{A80B}\u{A825}-\u{A826}\u{A82C}\u{A8C4}-\u{A8C5}\u{A8E0}-\u{A8F1}\u{A8FF}\u{A926}-\u{A92D}\u{A947}-\u{A951}\u{A980}-\u{A982}\u{A9B3}\u{A9B6}-\u{A9B9}\u{A9BC}-\u{A9BD}\u{A9E5}\u{AA29}-\u{AA2E}\u{AA31}-\u{AA32}\u{AA35}-\u{AA36}\u{AA43}\u{AA4C}\u{AA7C}\u{AAB0}\u{AAB2}-\u{AAB4}\u{AAB7}-\u{AAB8}\u{AABE}-\u{AABF}\u{AAC1}\u{AAEC}-\u{AAED}\u{AAF6}\u{ABE5}\u{ABE8}\u{ABED}\u{FB1E}\u{FE00}-\u{FE0F}\u{FE20}-\u{FE2F}\u{FEFF}\u{FFF9}-\u{FFFB}\u{101FD}\u{102E0}\u{10376}-\u{1037A}\u{10A01}-\u{10A03}\u{10A05}-\u{10A06}\u{10A0C}-\u{10A0F}\u{10A38}-\u{10A3A}\u{10A3F}\u{10AE5}-\u{10AE6}\u{10D24}-\u{10D27}\u{10EAB}-\u{10EAC}\u{10EFD}-\u{10EFF}\u{10F46}-\u{10F50}\u{10F82}-\u{10F85}\u{11001}\u{11038}-\u{11046}\u{11070}\u{11073}-\u{11074}\u{1107F}-\u{11081}\u{110B3}-\u{110B6}\u{110B9}-\u{110BA}\u{110C2}\u{11100}-\u{11102}\u{11127}-\u{1112B}\u{1112D}-\u{11134}\u{11173}\u{11180}-\u{11181}\u{111B6}-\u{111BE}\u{111C9}-\u{111CC}\u{111CF}\u{1122F}-\u{11231}\u{11234}\u{11236}-\u{11237}\u{1123E}\u{11241}\u{112DF}\u{112E3}-\u{112EA}\u{11300}-\u{11301}\u{1133B}-\u{1133C}\u{11340}\u{11366}-\u{1136C}\u{11370}-\u{11374}\u{11438}-\u{1143F}\u{11442}-\u{11444}\u{11446}\u{1145E}\u{114B3}-\u{114B8}\u{114BA}\u{114BF}-\u{114C0}\u{114C2}-\u{114C3}\u{115B2}-\u{115B5}\u{115BC}-\u{115BD}\u{115BF}-\u{115C0}\u{115DC}-\u{115DD}\u{11633}-\u{1163A}\u{1163D}\u{1163F}-\u{11640}\u{116AB}\u{116AD}\u{116B0}-\u{116B5}\u{116B7}\u{1171D}-\u{1171F}\u{11722}-\u{11725}\u{11727}-\u{1172B}\u{1182F}-\u{11837}\u{11839}-\u{1183A}\u{1193B}-\u{1193C}\u{1193E}\u{11943}\u{119D4}-\u{119D7}\u{119DA}-\u{119DB}\u{119E0}\u{11A01}-\u{11A0A}\u{11A33}-\u{11A38}\u{11A3B}-\u{11A3E}\u{11A47}\u{11A51}-\u{11A56}\u{11A59}-\u{11A5B}\u{11A8A}-\u{11A96}\u{11A98}-\u{11A99}\u{11C30}-\u{11C36}\u{11C38}-\u{11C3D}\u{11C3F}\u{11C92}-\u{11CA7}\u{11CAA}-\u{11CB0}\u{11CB2}-\u{11CB3}\u{11CB5}-\u{11CB6}\u{11D31}-\u{11D36}\u{11D3A}\u{11D3C}-\u{11D3D}\u{11D3F}-\u{11D45}\u{11D47}\u{11D90}-\u{11D91}\u{11D95}\u{11D97}\u{11EF3}-\u{11EF4}\u{11F00}-\u{11F01}\u{11F36}-\u{11F3A}\u{11F40}\u{11F42}\u{13430}-\u{1343F}\u{13440}\u{13447}-\u{13455}\u{16AF0}-\u{16AF4}\u{16B30}-\u{16B36}\u{16F4F}\u{16F8F}-\u{16F92}\u{16FE4}\u{1BC9D}-\u{1BC9E}\u{1BCA0}-\u{1BCA3}\u{1CF00}-\u{1CF2D}\u{1CF30}-\u{1CF46}\u{1D167}-\u{1D169}\u{1D173}-\u{1D17A}\u{1D17B}-\u{1D182}\u{1D185}-\u{1D18B}\u{1D1AA}-\u{1D1AD}\u{1D242}-\u{1D244}\u{1DA00}-\u{1DA36}\u{1DA3B}-\u{1DA6C}\u{1DA75}\u{1DA84}\u{1DA9B}-\u{1DA9F}\u{1DAA1}-\u{1DAAF}\u{1E000}-\u{1E006}\u{1E008}-\u{1E018}\u{1E01B}-\u{1E021}\u{1E023}-\u{1E024}\u{1E026}-\u{1E02A}\u{1E08F}\u{1E130}-\u{1E136}\u{1E2AE}\u{1E2EC}-\u{1E2EF}\u{1E4EC}-\u{1E4EF}\u{1E8D0}-\u{1E8D6}\u{1E944}-\u{1E94A}\u{1E94B}\u{E0001}\u{E0020}-\u{E007F}\u{E0100}-\u{E01EF}]'
|
21
|
+
JOINING_TYPE_R_CHARACTER_CLASS = '[\u{0622}-\u{0625}\u{0627}\u{0629}\u{062F}-\u{0632}\u{0648}\u{0671}-\u{0673}\u{0675}-\u{0677}\u{0688}-\u{0699}\u{06C0}\u{06C3}-\u{06CB}\u{06CD}\u{06CF}\u{06D2}-\u{06D3}\u{06D5}\u{06EE}-\u{06EF}\u{0710}\u{0715}-\u{0719}\u{071E}\u{0728}\u{072A}\u{072C}\u{072F}\u{074D}\u{0759}-\u{075B}\u{076B}-\u{076C}\u{0771}\u{0773}-\u{0774}\u{0778}-\u{0779}\u{0840}\u{0846}-\u{0847}\u{0849}\u{0854}\u{0856}-\u{0858}\u{0867}\u{0869}-\u{086A}\u{0870}-\u{0882}\u{088E}\u{08AA}-\u{08AC}\u{08AE}\u{08B1}-\u{08B2}\u{08B9}\u{10AC5}\u{10AC7}\u{10AC9}-\u{10ACA}\u{10ACE}-\u{10AD2}\u{10ADD}\u{10AE1}\u{10AE4}\u{10AEF}\u{10B81}\u{10B83}-\u{10B85}\u{10B89}\u{10B8C}\u{10B8E}-\u{10B8F}\u{10B91}\u{10BA9}-\u{10BAC}\u{10D22}\u{10F33}\u{10F54}\u{10F74}-\u{10F75}\u{10FB4}-\u{10FB6}\u{10FB9}-\u{10FBA}\u{10FBD}\u{10FC2}-\u{10FC3}\u{10FC9}]'
|
22
|
+
# https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.1
|
23
|
+
# https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.2
|
24
|
+
ZERO_WIDTH_VIRAMA = "#{VIRAMA_CHARACTER_CLASS}[\\u{200C}\\u{200D}]"
|
25
|
+
ZERO_WIDTH_NON_JOINER_JOINING_TYPE = "[#{JOINING_TYPE_L_CHARACTER_CLASS}#{JOINING_TYPE_D_CHARACTER_CLASS}]#{JOINING_TYPE_T_CHARACTER_CLASS}*\\u{200C}#{JOINING_TYPE_T_CHARACTER_CLASS}*[#{JOINING_TYPE_R_CHARACTER_CLASS}#{JOINING_TYPE_D_CHARACTER_CLASS}]"
|
26
|
+
# https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.3
|
27
|
+
MIDDLE_DOT = '\u{006C}\u{00B7}\u{006C}'
|
28
|
+
# https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.4
|
29
|
+
GREEK_LOWER_NUMERAL_SIGN = '\u{0375}\p{Greek}'
|
30
|
+
# https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.5
|
31
|
+
# https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.6
|
32
|
+
HEBREW_PUNCTUATION = '\p{Hebrew}[\u{05F3}\u{05F4}]'
|
33
|
+
CONTEXT_REGEX = /(#{ZERO_WIDTH_VIRAMA}|#{ZERO_WIDTH_NON_JOINER_JOINING_TYPE}|#{MIDDLE_DOT}|#{GREEK_LOWER_NUMERAL_SIGN}|#{HEBREW_PUNCTUATION})/.freeze
|
34
|
+
# https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.7
|
35
|
+
KATAKANA_MIDDLE_DOT_REGEX = /\u{30FB}/.freeze
|
36
|
+
KATAKANA_MIDDLE_DOT_CONTEXT_REGEX = /[\p{Hiragana}\p{Katakana}\p{Han}]/.freeze
|
37
|
+
# https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.8
|
38
|
+
# https://datatracker.ietf.org/doc/html/rfc5892#appendix-A.9
|
39
|
+
ARABIC_INDIC_DIGITS_REGEX = /[\u{0660}-\u{0669}]/.freeze
|
40
|
+
ARABIC_EXTENDED_DIGITS_REGEX = /[\u{06F0}-\u{06F9}]/.freeze
|
41
|
+
|
42
|
+
def valid_hostname?(data)
|
43
|
+
data.split('.').map do |a_label|
|
44
|
+
return false if a_label.size > 63
|
45
|
+
u_label = SimpleIDN.to_unicode(a_label)
|
46
|
+
# https://datatracker.ietf.org/doc/html/rfc5891#section-4.2.3.1
|
47
|
+
return false if u_label.slice(2, 2) == '--'
|
48
|
+
return false if ARABIC_INDIC_DIGITS_REGEX.match?(u_label) && ARABIC_EXTENDED_DIGITS_REGEX.match?(u_label)
|
49
|
+
u_label.gsub!(CONTEXT_REGEX, 'ok')
|
50
|
+
u_label.gsub!(KATAKANA_MIDDLE_DOT_REGEX, 'ok') if KATAKANA_MIDDLE_DOT_CONTEXT_REGEX.match?(u_label)
|
51
|
+
u_label
|
52
|
+
end.join('.').match?(HOSTNAME_REGEX)
|
53
|
+
rescue SimpleIDN::ConversionError
|
54
|
+
false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/json_schemer/format.rb
CHANGED
@@ -1,15 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
module JSONSchemer
|
3
3
|
module Format
|
4
|
+
include Hostname
|
5
|
+
|
4
6
|
# this is no good
|
5
7
|
EMAIL_REGEX = /\A[^@\s]+@([\p{L}\d-]+\.)+[\p{L}\d\-]{2,}\z/i.freeze
|
6
|
-
LABEL_REGEX_STRING = '[\p{L}\p{N}]([\p{L}\p{N}\-]*[\p{L}\p{N}])?'
|
7
|
-
HOSTNAME_REGEX = /\A(#{LABEL_REGEX_STRING}\.)*#{LABEL_REGEX_STRING}\z/i.freeze
|
8
8
|
JSON_POINTER_REGEX_STRING = '(\/([^~\/]|~[01])*)*'
|
9
9
|
JSON_POINTER_REGEX = /\A#{JSON_POINTER_REGEX_STRING}\z/.freeze
|
10
10
|
RELATIVE_JSON_POINTER_REGEX = /\A(0|[1-9]\d*)(#|#{JSON_POINTER_REGEX_STRING})?\z/.freeze
|
11
11
|
DATE_TIME_OFFSET_REGEX = /(Z|[\+\-]([01][0-9]|2[0-3]):[0-5][0-9])\z/i.freeze
|
12
|
-
|
12
|
+
HOUR_24_REGEX = /T24/.freeze
|
13
|
+
LEAP_SECOND_REGEX = /T\d{2}:\d{2}:6/.freeze
|
14
|
+
IP_REGEX = /\A[\h:.]+\z/.freeze
|
15
|
+
INVALID_QUERY_REGEX = /\s/.freeze
|
13
16
|
|
14
17
|
def valid_spec_format?(data, format)
|
15
18
|
case format
|
@@ -28,9 +31,9 @@ module JSONSchemer
|
|
28
31
|
when 'idn-hostname'
|
29
32
|
valid_hostname?(data)
|
30
33
|
when 'ipv4'
|
31
|
-
valid_ip?(data,
|
34
|
+
valid_ip?(data, Socket::AF_INET)
|
32
35
|
when 'ipv6'
|
33
|
-
valid_ip?(data,
|
36
|
+
valid_ip?(data, Socket::AF_INET6)
|
34
37
|
when 'uri'
|
35
38
|
valid_uri?(data)
|
36
39
|
when 'uri-reference'
|
@@ -47,6 +50,8 @@ module JSONSchemer
|
|
47
50
|
valid_relative_json_pointer?(data)
|
48
51
|
when 'regex'
|
49
52
|
EcmaReValidator.valid?(data)
|
53
|
+
else
|
54
|
+
raise UnknownFormat, format
|
50
55
|
end
|
51
56
|
end
|
52
57
|
|
@@ -58,25 +63,24 @@ module JSONSchemer
|
|
58
63
|
end
|
59
64
|
|
60
65
|
def valid_date_time?(data)
|
61
|
-
|
66
|
+
return false if HOUR_24_REGEX.match?(data)
|
67
|
+
datetime = DateTime.rfc3339(data)
|
68
|
+
return false if LEAP_SECOND_REGEX.match?(data) && datetime.to_time.utc.strftime('%H:%M') != '23:59'
|
62
69
|
DATE_TIME_OFFSET_REGEX.match?(data)
|
63
|
-
rescue ArgumentError
|
64
|
-
raise e unless e.message == 'invalid date'
|
70
|
+
rescue ArgumentError
|
65
71
|
false
|
66
72
|
end
|
67
73
|
|
68
74
|
def valid_email?(data)
|
69
|
-
EMAIL_REGEX.match?(data)
|
70
|
-
|
71
|
-
|
72
|
-
def valid_hostname?(data)
|
73
|
-
HOSTNAME_REGEX.match?(data) && data.split('.').all? { |label| label.size <= 63 }
|
75
|
+
return false unless EMAIL_REGEX.match?(data)
|
76
|
+
local, _domain = data.partition('@')
|
77
|
+
!local.start_with?('.') && !local.end_with?('.') && !local.include?('..')
|
74
78
|
end
|
75
79
|
|
76
|
-
def valid_ip?(data,
|
77
|
-
|
78
|
-
|
79
|
-
rescue IPAddr::
|
80
|
+
def valid_ip?(data, family)
|
81
|
+
IPAddr.new(data, family)
|
82
|
+
IP_REGEX.match?(data)
|
83
|
+
rescue IPAddr::Error
|
80
84
|
false
|
81
85
|
end
|
82
86
|
|
@@ -101,7 +105,14 @@ module JSONSchemer
|
|
101
105
|
end
|
102
106
|
|
103
107
|
def iri_escape(data)
|
104
|
-
|
108
|
+
data.gsub(/[^[:ascii:]]/) do |match|
|
109
|
+
us = match
|
110
|
+
tmp = +''
|
111
|
+
us.each_byte do |uc|
|
112
|
+
tmp << sprintf('%%%02X', uc)
|
113
|
+
end
|
114
|
+
tmp
|
115
|
+
end.force_encoding(Encoding::US_ASCII)
|
105
116
|
end
|
106
117
|
|
107
118
|
def valid_uri_template?(data)
|
@@ -30,12 +30,23 @@ 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
|
36
44
|
end
|
37
45
|
end
|
38
46
|
|
47
|
+
JSON_POINTER_TOKEN_ESCAPE_CHARS = { '~' => '~0', '/' => '~1' }
|
48
|
+
JSON_POINTER_TOKEN_ESCAPE_REGEXP = Regexp.union(JSON_POINTER_TOKEN_ESCAPE_CHARS.keys)
|
49
|
+
|
39
50
|
def initialize(
|
40
51
|
schema,
|
41
52
|
format: true,
|
@@ -44,7 +55,8 @@ module JSONSchemer
|
|
44
55
|
after_property_validation: nil,
|
45
56
|
formats: nil,
|
46
57
|
keywords: nil,
|
47
|
-
ref_resolver: DEFAULT_REF_RESOLVER
|
58
|
+
ref_resolver: DEFAULT_REF_RESOLVER,
|
59
|
+
regexp_resolver: 'ecma'
|
48
60
|
)
|
49
61
|
raise InvalidSymbolKey, 'schemas must use string keys' if schema.is_a?(Hash) && !schema.empty? && !schema.first.first.is_a?(String)
|
50
62
|
@root = schema
|
@@ -54,7 +66,15 @@ module JSONSchemer
|
|
54
66
|
@after_property_validation = [*after_property_validation]
|
55
67
|
@formats = formats
|
56
68
|
@keywords = keywords
|
57
|
-
@ref_resolver = ref_resolver == 'net/http' ?
|
69
|
+
@ref_resolver = ref_resolver == 'net/http' ? CachedResolver.new(&NET_HTTP_REF_RESOLVER) : ref_resolver
|
70
|
+
@regexp_resolver = case regexp_resolver
|
71
|
+
when 'ecma'
|
72
|
+
CachedResolver.new(&ECMA_262_REGEXP_RESOLVER)
|
73
|
+
when 'ruby'
|
74
|
+
CachedResolver.new(&Regexp.method(:new))
|
75
|
+
else
|
76
|
+
regexp_resolver
|
77
|
+
end
|
58
78
|
end
|
59
79
|
|
60
80
|
def valid?(data)
|
@@ -180,7 +200,7 @@ module JSONSchemer
|
|
180
200
|
|
181
201
|
if if_schema && valid_instance?(instance.merge(schema: if_schema, before_property_validation: false, after_property_validation: false))
|
182
202
|
validate_instance(instance.merge(schema: then_schema, schema_pointer: "#{instance.schema_pointer}/then"), &block) unless then_schema.nil?
|
183
|
-
elsif
|
203
|
+
elsif schema.key?('if')
|
184
204
|
validate_instance(instance.merge(schema: else_schema, schema_pointer: "#{instance.schema_pointer}/else"), &block) unless else_schema.nil?
|
185
205
|
end
|
186
206
|
|
@@ -204,7 +224,7 @@ module JSONSchemer
|
|
204
224
|
|
205
225
|
private
|
206
226
|
|
207
|
-
attr_reader :root, :formats, :keywords, :ref_resolver
|
227
|
+
attr_reader :root, :formats, :keywords, :ref_resolver, :regexp_resolver
|
208
228
|
|
209
229
|
def id_keyword
|
210
230
|
ID_KEYWORD
|
@@ -225,10 +245,12 @@ module JSONSchemer
|
|
225
245
|
def child(schema)
|
226
246
|
JSONSchemer.schema(
|
227
247
|
schema,
|
248
|
+
default_schema_class: self.class,
|
228
249
|
format: format?,
|
229
250
|
formats: formats,
|
230
251
|
keywords: keywords,
|
231
|
-
ref_resolver: ref_resolver
|
252
|
+
ref_resolver: ref_resolver,
|
253
|
+
regexp_resolver: regexp_resolver
|
232
254
|
)
|
233
255
|
end
|
234
256
|
|
@@ -355,8 +377,7 @@ module JSONSchemer
|
|
355
377
|
validate_exclusive_minimum(instance, exclusive_minimum, minimum, &block) if exclusive_minimum
|
356
378
|
|
357
379
|
if multiple_of
|
358
|
-
|
359
|
-
yield error(instance, 'multipleOf') unless quotient.floor == quotient
|
380
|
+
yield error(instance, 'multipleOf') unless BigDecimal(data.to_s).modulo(multiple_of).zero?
|
360
381
|
end
|
361
382
|
end
|
362
383
|
|
@@ -399,7 +420,7 @@ module JSONSchemer
|
|
399
420
|
|
400
421
|
yield error(instance, 'maxLength') if max_length && data.size > max_length
|
401
422
|
yield error(instance, 'minLength') if min_length && data.size < min_length
|
402
|
-
yield error(instance, 'pattern') if pattern &&
|
423
|
+
yield error(instance, 'pattern') if pattern && !resolve_regexp(pattern).match?(data)
|
403
424
|
yield error(instance, 'format') if format? && spec_format?(format) && !valid_spec_format?(data, format)
|
404
425
|
|
405
426
|
if content_encoding || content_media_type
|
@@ -514,7 +535,8 @@ module JSONSchemer
|
|
514
535
|
dependencies.each do |key, value|
|
515
536
|
next unless data.key?(key)
|
516
537
|
subschema = value.is_a?(Array) ? { 'required' => value } : value
|
517
|
-
|
538
|
+
escaped_key = escape_json_pointer_token(key)
|
539
|
+
subinstance = instance.merge(schema: subschema, schema_pointer: "#{instance.schema_pointer}/dependencies/#{escaped_key}")
|
518
540
|
validate_instance(subinstance, &block)
|
519
541
|
end
|
520
542
|
end
|
@@ -528,6 +550,8 @@ module JSONSchemer
|
|
528
550
|
|
529
551
|
regex_pattern_properties = nil
|
530
552
|
data.each do |key, value|
|
553
|
+
escaped_key = escape_json_pointer_token(key)
|
554
|
+
|
531
555
|
unless property_names.nil?
|
532
556
|
subinstance = instance.merge(
|
533
557
|
data: key,
|
@@ -542,9 +566,9 @@ module JSONSchemer
|
|
542
566
|
if properties && properties.key?(key)
|
543
567
|
subinstance = instance.merge(
|
544
568
|
data: value,
|
545
|
-
data_pointer: "#{instance.data_pointer}/#{
|
569
|
+
data_pointer: "#{instance.data_pointer}/#{escaped_key}",
|
546
570
|
schema: properties[key],
|
547
|
-
schema_pointer: "#{instance.schema_pointer}/properties/#{
|
571
|
+
schema_pointer: "#{instance.schema_pointer}/properties/#{escaped_key}"
|
548
572
|
)
|
549
573
|
validate_instance(subinstance, &block)
|
550
574
|
matched_key = true
|
@@ -552,15 +576,16 @@ module JSONSchemer
|
|
552
576
|
|
553
577
|
if pattern_properties
|
554
578
|
regex_pattern_properties ||= pattern_properties.map do |pattern, property_schema|
|
555
|
-
[pattern,
|
579
|
+
[pattern, resolve_regexp(pattern), property_schema]
|
556
580
|
end
|
557
581
|
regex_pattern_properties.each do |pattern, regex, property_schema|
|
582
|
+
escaped_pattern = escape_json_pointer_token(pattern)
|
558
583
|
if regex.match?(key)
|
559
584
|
subinstance = instance.merge(
|
560
585
|
data: value,
|
561
|
-
data_pointer: "#{instance.data_pointer}/#{
|
586
|
+
data_pointer: "#{instance.data_pointer}/#{escaped_key}",
|
562
587
|
schema: property_schema,
|
563
|
-
schema_pointer: "#{instance.schema_pointer}/patternProperties/#{
|
588
|
+
schema_pointer: "#{instance.schema_pointer}/patternProperties/#{escaped_pattern}"
|
564
589
|
)
|
565
590
|
validate_instance(subinstance, &block)
|
566
591
|
matched_key = true
|
@@ -573,7 +598,7 @@ module JSONSchemer
|
|
573
598
|
unless additional_properties.nil?
|
574
599
|
subinstance = instance.merge(
|
575
600
|
data: value,
|
576
|
-
data_pointer: "#{instance.data_pointer}/#{
|
601
|
+
data_pointer: "#{instance.data_pointer}/#{escaped_key}",
|
577
602
|
schema: additional_properties,
|
578
603
|
schema_pointer: "#{instance.schema_pointer}/additionalProperties"
|
579
604
|
)
|
@@ -592,18 +617,12 @@ module JSONSchemer
|
|
592
617
|
|
593
618
|
def safe_strict_decode64(data)
|
594
619
|
Base64.strict_decode64(data)
|
595
|
-
rescue ArgumentError
|
596
|
-
raise e unless e.message == 'invalid base64'
|
620
|
+
rescue ArgumentError
|
597
621
|
nil
|
598
622
|
end
|
599
623
|
|
600
|
-
def
|
601
|
-
|
602
|
-
@ecma_262_regex[pattern] ||= Regexp.new(
|
603
|
-
Regexp::Scanner.scan(pattern).map do |type, token, text|
|
604
|
-
type == :anchor ? RUBY_REGEX_ANCHORS_TO_ECMA_262.fetch(token, text) : text
|
605
|
-
end.join
|
606
|
-
)
|
624
|
+
def escape_json_pointer_token(token)
|
625
|
+
token.gsub(JSON_POINTER_TOKEN_ESCAPE_REGEXP, JSON_POINTER_TOKEN_ESCAPE_CHARS)
|
607
626
|
end
|
608
627
|
|
609
628
|
def join_uri(a, b)
|
@@ -653,6 +672,10 @@ module JSONSchemer
|
|
653
672
|
def resolve_ref(uri)
|
654
673
|
ref_resolver.call(uri) || raise(InvalidRefResolution, uri.to_s)
|
655
674
|
end
|
675
|
+
|
676
|
+
def resolve_regexp(pattern)
|
677
|
+
regexp_resolver.call(pattern) || raise(InvalidRegexpResolution, pattern)
|
678
|
+
end
|
656
679
|
end
|
657
680
|
end
|
658
681
|
end
|
data/lib/json_schemer/version.rb
CHANGED
data/lib/json_schemer.rb
CHANGED
@@ -1,21 +1,25 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require 'base64'
|
3
|
+
require 'bigdecimal'
|
3
4
|
require 'ipaddr'
|
4
5
|
require 'json'
|
5
6
|
require 'net/http'
|
6
7
|
require 'pathname'
|
8
|
+
require 'set'
|
7
9
|
require 'time'
|
8
10
|
require 'uri'
|
9
11
|
|
10
12
|
require 'ecma-re-validator'
|
11
13
|
require 'hana'
|
12
14
|
require 'regexp_parser'
|
15
|
+
require 'simpleidn'
|
13
16
|
require 'uri_template'
|
14
17
|
|
15
18
|
require 'json_schemer/version'
|
19
|
+
require 'json_schemer/format/hostname'
|
16
20
|
require 'json_schemer/format'
|
17
21
|
require 'json_schemer/errors'
|
18
|
-
require 'json_schemer/
|
22
|
+
require 'json_schemer/cached_resolver'
|
19
23
|
require 'json_schemer/schema/base'
|
20
24
|
require 'json_schemer/schema/draft4'
|
21
25
|
require 'json_schemer/schema/draft6'
|
@@ -24,49 +28,57 @@ require 'json_schemer/schema/draft7'
|
|
24
28
|
module JSONSchemer
|
25
29
|
class UnsupportedMetaSchema < StandardError; end
|
26
30
|
class UnknownRef < StandardError; end
|
31
|
+
class UnknownFormat < StandardError; end
|
27
32
|
class InvalidRefResolution < StandardError; end
|
33
|
+
class InvalidRegexpResolution < StandardError; end
|
28
34
|
class InvalidFileURI < StandardError; end
|
29
35
|
class InvalidSymbolKey < StandardError; end
|
30
36
|
|
31
|
-
|
37
|
+
SCHEMA_CLASS_BY_META_SCHEMA = {
|
32
38
|
'http://json-schema.org/schema#' => Schema::Draft4, # Version-less $schema deprecated after Draft 4
|
33
39
|
'http://json-schema.org/draft-04/schema#' => Schema::Draft4,
|
34
40
|
'http://json-schema.org/draft-06/schema#' => Schema::Draft6,
|
35
41
|
'http://json-schema.org/draft-07/schema#' => Schema::Draft7
|
36
42
|
}.freeze
|
37
43
|
|
38
|
-
|
44
|
+
WINDOWS_URI_PATH_REGEX = /\A\/[a-z]:/i
|
39
45
|
|
40
46
|
FILE_URI_REF_RESOLVER = proc do |uri|
|
41
47
|
raise InvalidFileURI, 'must use `file` scheme' unless uri.scheme == 'file'
|
42
48
|
raise InvalidFileURI, 'cannot have a host (use `file:///`)' if uri.host && !uri.host.empty?
|
43
|
-
|
49
|
+
path = uri.path
|
50
|
+
path = path[1..-1] if path.match?(WINDOWS_URI_PATH_REGEX)
|
51
|
+
JSON.parse(File.read(URI::DEFAULT_PARSER.unescape(path)))
|
44
52
|
end
|
45
53
|
|
46
54
|
class << self
|
47
|
-
def schema(schema, **options)
|
55
|
+
def schema(schema, default_schema_class: Schema::Draft7, **options)
|
48
56
|
case schema
|
49
57
|
when String
|
50
58
|
schema = JSON.parse(schema)
|
51
59
|
when Pathname
|
52
|
-
uri = URI.parse(
|
60
|
+
uri = URI.parse(File.join('file:', URI::DEFAULT_PARSER.escape(schema.realpath.to_s)))
|
53
61
|
if options.key?(:ref_resolver)
|
54
62
|
schema = FILE_URI_REF_RESOLVER.call(uri)
|
55
63
|
else
|
56
|
-
ref_resolver =
|
64
|
+
ref_resolver = CachedResolver.new(&FILE_URI_REF_RESOLVER)
|
57
65
|
schema = ref_resolver.call(uri)
|
58
66
|
options[:ref_resolver] = ref_resolver
|
59
67
|
end
|
60
|
-
schema[draft_class(schema)::ID_KEYWORD] ||= uri.to_s
|
68
|
+
schema[draft_class(schema, default_schema_class)::ID_KEYWORD] ||= uri.to_s
|
61
69
|
end
|
62
|
-
draft_class(schema).new(schema, **options)
|
70
|
+
draft_class(schema, default_schema_class).new(schema, **options)
|
63
71
|
end
|
64
72
|
|
65
73
|
private
|
66
74
|
|
67
|
-
def draft_class(schema)
|
68
|
-
|
69
|
-
|
75
|
+
def draft_class(schema, default_schema_class)
|
76
|
+
if schema.is_a?(Hash) && schema.key?('$schema')
|
77
|
+
meta_schema = schema.fetch('$schema')
|
78
|
+
SCHEMA_CLASS_BY_META_SCHEMA[meta_schema] || raise(UnsupportedMetaSchema, meta_schema)
|
79
|
+
else
|
80
|
+
default_schema_class
|
81
|
+
end
|
70
82
|
end
|
71
83
|
end
|
72
84
|
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.25
|
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:
|
11
|
+
date: 2023-05-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -52,20 +52,34 @@ dependencies:
|
|
52
52
|
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: simplecov
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.22'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.22'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: ecma-re-validator
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
58
72
|
requirements:
|
59
73
|
- - "~>"
|
60
74
|
- !ruby/object:Gem::Version
|
61
|
-
version: '0.
|
75
|
+
version: '0.3'
|
62
76
|
type: :runtime
|
63
77
|
prerelease: false
|
64
78
|
version_requirements: !ruby/object:Gem::Requirement
|
65
79
|
requirements:
|
66
80
|
- - "~>"
|
67
81
|
- !ruby/object:Gem::Version
|
68
|
-
version: '0.
|
82
|
+
version: '0.3'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: hana
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -81,37 +95,52 @@ dependencies:
|
|
81
95
|
- !ruby/object:Gem::Version
|
82
96
|
version: '1.3'
|
83
97
|
- !ruby/object:Gem::Dependency
|
84
|
-
name:
|
98
|
+
name: regexp_parser
|
85
99
|
requirement: !ruby/object:Gem::Requirement
|
86
100
|
requirements:
|
87
101
|
- - "~>"
|
88
102
|
- !ruby/object:Gem::Version
|
89
|
-
version: '0
|
103
|
+
version: '2.0'
|
90
104
|
type: :runtime
|
91
105
|
prerelease: false
|
92
106
|
version_requirements: !ruby/object:Gem::Requirement
|
93
107
|
requirements:
|
94
108
|
- - "~>"
|
95
109
|
- !ruby/object:Gem::Version
|
96
|
-
version: '0
|
110
|
+
version: '2.0'
|
97
111
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
112
|
+
name: simpleidn
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.2'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.2'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: uri_template
|
99
127
|
requirement: !ruby/object:Gem::Requirement
|
100
128
|
requirements:
|
101
129
|
- - "~>"
|
102
130
|
- !ruby/object:Gem::Version
|
103
|
-
version: '
|
131
|
+
version: '0.7'
|
104
132
|
type: :runtime
|
105
133
|
prerelease: false
|
106
134
|
version_requirements: !ruby/object:Gem::Requirement
|
107
135
|
requirements:
|
108
136
|
- - "~>"
|
109
137
|
- !ruby/object:Gem::Version
|
110
|
-
version: '
|
111
|
-
description:
|
138
|
+
version: '0.7'
|
139
|
+
description:
|
112
140
|
email:
|
113
141
|
- davishmcclurg@gmail.com
|
114
|
-
executables:
|
142
|
+
executables:
|
143
|
+
- json_schemer
|
115
144
|
extensions: []
|
116
145
|
extra_rdoc_files: []
|
117
146
|
files:
|
@@ -123,12 +152,16 @@ files:
|
|
123
152
|
- README.md
|
124
153
|
- Rakefile
|
125
154
|
- bin/console
|
155
|
+
- bin/hostname_character_classes
|
156
|
+
- bin/rake
|
126
157
|
- bin/setup
|
158
|
+
- exe/json_schemer
|
127
159
|
- json_schemer.gemspec
|
128
160
|
- lib/json_schemer.rb
|
129
|
-
- lib/json_schemer/
|
161
|
+
- lib/json_schemer/cached_resolver.rb
|
130
162
|
- lib/json_schemer/errors.rb
|
131
163
|
- lib/json_schemer/format.rb
|
164
|
+
- lib/json_schemer/format/hostname.rb
|
132
165
|
- lib/json_schemer/schema/base.rb
|
133
166
|
- lib/json_schemer/schema/draft4.rb
|
134
167
|
- lib/json_schemer/schema/draft6.rb
|
@@ -138,13 +171,13 @@ homepage: https://github.com/davishmcclurg/json_schemer
|
|
138
171
|
licenses:
|
139
172
|
- MIT
|
140
173
|
metadata: {}
|
141
|
-
post_install_message:
|
174
|
+
post_install_message:
|
142
175
|
rdoc_options: []
|
143
176
|
require_paths:
|
144
177
|
- lib
|
145
178
|
required_ruby_version: !ruby/object:Gem::Requirement
|
146
179
|
requirements:
|
147
|
-
- - "
|
180
|
+
- - ">="
|
148
181
|
- !ruby/object:Gem::Version
|
149
182
|
version: '2.4'
|
150
183
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
@@ -153,8 +186,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
153
186
|
- !ruby/object:Gem::Version
|
154
187
|
version: '0'
|
155
188
|
requirements: []
|
156
|
-
rubygems_version: 3.
|
157
|
-
signing_key:
|
189
|
+
rubygems_version: 3.4.10
|
190
|
+
signing_key:
|
158
191
|
specification_version: 4
|
159
192
|
summary: JSON Schema validator. Supports drafts 4, 6, and 7.
|
160
193
|
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
|