jschema 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/jschema.rb +41 -0
- data/lib/jschema/json_reference.rb +65 -0
- data/lib/jschema/schema.rb +84 -0
- data/lib/jschema/schema_ref.rb +13 -0
- data/lib/jschema/simple_validator.rb +86 -0
- data/lib/jschema/string_length_validator.rb +17 -0
- data/lib/jschema/validator.rb +14 -0
- data/lib/jschema/validator/all_of.rb +29 -0
- data/lib/jschema/validator/any_of.rb +29 -0
- data/lib/jschema/validator/dependencies.rb +63 -0
- data/lib/jschema/validator/enum.rb +23 -0
- data/lib/jschema/validator/format.rb +75 -0
- data/lib/jschema/validator/items.rb +79 -0
- data/lib/jschema/validator/max_items.rb +28 -0
- data/lib/jschema/validator/max_length.rb +23 -0
- data/lib/jschema/validator/max_properties.rb +31 -0
- data/lib/jschema/validator/maximum.rb +31 -0
- data/lib/jschema/validator/min_items.rb +28 -0
- data/lib/jschema/validator/min_length.rb +23 -0
- data/lib/jschema/validator/min_properties.rb +31 -0
- data/lib/jschema/validator/minimum.rb +31 -0
- data/lib/jschema/validator/multiple_of.rb +32 -0
- data/lib/jschema/validator/not.rb +23 -0
- data/lib/jschema/validator/one_of.rb +29 -0
- data/lib/jschema/validator/pattern.rb +36 -0
- data/lib/jschema/validator/properties.rb +106 -0
- data/lib/jschema/validator/required.rb +34 -0
- data/lib/jschema/validator/type.rb +57 -0
- data/lib/jschema/validator/unique_items.rb +27 -0
- data/test/assert_received.rb +15 -0
- data/test/string_length_validator_tests.rb +18 -0
- data/test/test_assert_received.rb +39 -0
- data/test/test_integration.rb +30 -0
- data/test/test_json_reference.rb +84 -0
- data/test/test_schema.rb +125 -0
- data/test/test_schema_ref.rb +11 -0
- data/test/test_validator.rb +29 -0
- data/test/validator/argument_is_array_of_schemas_tests.rb +22 -0
- data/test/validator/assertions.rb +56 -0
- data/test/validator/properties_limit_tests.rb +13 -0
- data/test/validator/schema_validation_helpers.rb +29 -0
- data/test/validator/test_all_of.rb +30 -0
- data/test/validator/test_any_of.rb +31 -0
- data/test/validator/test_dependencies.rb +106 -0
- data/test/validator/test_enum.rb +30 -0
- data/test/validator/test_format.rb +70 -0
- data/test/validator/test_items.rb +113 -0
- data/test/validator/test_max_items.rb +26 -0
- data/test/validator/test_max_length.rb +30 -0
- data/test/validator/test_max_properties.rb +29 -0
- data/test/validator/test_maximum.rb +58 -0
- data/test/validator/test_min_items.rb +27 -0
- data/test/validator/test_min_length.rb +30 -0
- data/test/validator/test_min_properties.rb +29 -0
- data/test/validator/test_minimum.rb +58 -0
- data/test/validator/test_multiple_of.rb +38 -0
- data/test/validator/test_not.rb +32 -0
- data/test/validator/test_one_of.rb +31 -0
- data/test/validator/test_pattern.rb +32 -0
- data/test/validator/test_properties.rb +136 -0
- data/test/validator/test_required.rb +30 -0
- data/test/validator/test_simple_validator.rb +100 -0
- data/test/validator/test_type.rb +97 -0
- data/test/validator/test_unique_items.rb +30 -0
- data/test/validator/validation_against_schemas_tests.rb +24 -0
- metadata +144 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 4564a821e55e9336df4d64292765c8e366e1e262
|
4
|
+
data.tar.gz: 990b4033d9123af1ee0b471d6117f2f570a250e1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cccc98abfaf52295b9d7f959a94bfbc2ea01310f71893018e586a2137dfaf72d93cca14ffe75af29326bd1aeed3ce511c8b452c2dc0d0999bd85d02a6bc9f233
|
7
|
+
data.tar.gz: 5f4c8f525be757b44e21626a846ca45484fb13eb9690a199cfb40da244726e2b00a7e83e331cee81385267baa6002d16b400f786847e277370bc177247208f26
|
data/lib/jschema.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
require 'uri'
|
3
|
+
require 'delegate'
|
4
|
+
|
5
|
+
require 'jschema/json_reference'
|
6
|
+
require 'jschema/schema_ref'
|
7
|
+
require 'jschema/schema'
|
8
|
+
require 'jschema/simple_validator'
|
9
|
+
require 'jschema/validator'
|
10
|
+
require 'jschema/string_length_validator'
|
11
|
+
require 'jschema/validator/max_length'
|
12
|
+
require 'jschema/validator/min_length'
|
13
|
+
require 'jschema/validator/pattern'
|
14
|
+
require 'jschema/validator/enum'
|
15
|
+
require 'jschema/validator/items'
|
16
|
+
require 'jschema/validator/required'
|
17
|
+
require 'jschema/validator/properties'
|
18
|
+
require 'jschema/validator/max_properties'
|
19
|
+
require 'jschema/validator/min_properties'
|
20
|
+
require 'jschema/validator/dependencies'
|
21
|
+
require 'jschema/validator/type'
|
22
|
+
require 'jschema/validator/all_of'
|
23
|
+
require 'jschema/validator/any_of'
|
24
|
+
require 'jschema/validator/one_of'
|
25
|
+
require 'jschema/validator/not'
|
26
|
+
require 'jschema/validator/max_items'
|
27
|
+
require 'jschema/validator/min_items'
|
28
|
+
require 'jschema/validator/unique_items'
|
29
|
+
require 'jschema/validator/multiple_of'
|
30
|
+
require 'jschema/validator/maximum'
|
31
|
+
require 'jschema/validator/minimum'
|
32
|
+
require 'jschema/validator/format'
|
33
|
+
|
34
|
+
module JSchema
|
35
|
+
class InvalidSchema < StandardError; end
|
36
|
+
class UnknownError < StandardError; end
|
37
|
+
|
38
|
+
def self.build(*args)
|
39
|
+
Schema.build(*args)
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module JSchema
|
2
|
+
module JSONReference
|
3
|
+
@mutex = Mutex.new
|
4
|
+
@schemas = {}
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def register_schema(schema)
|
8
|
+
schema_key = key(schema.uri, schema)
|
9
|
+
@mutex.synchronize do
|
10
|
+
@schemas[schema_key] = schema
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def dereference(uri, schema)
|
15
|
+
schema_key = key(uri, schema)
|
16
|
+
@mutex.synchronize do
|
17
|
+
@schemas[schema_key]
|
18
|
+
end || build_external_schema(uri, schema)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def build_external_schema(uri, schema)
|
24
|
+
unless valid_external_uri?(uri)
|
25
|
+
fail InvalidSchema, 'Invalid URI for external schema'
|
26
|
+
end
|
27
|
+
|
28
|
+
sch = JSON.parse download_schema(uri)
|
29
|
+
register_schema Schema.new(sch, uri, schema.parent)
|
30
|
+
rescue JSON::ParserError, Timeout::Error
|
31
|
+
raise InvalidSchema, 'Failed to download external schema'
|
32
|
+
end
|
33
|
+
|
34
|
+
def valid_external_uri?(uri)
|
35
|
+
uri.is_a?(URI::HTTP) && uri.absolute?
|
36
|
+
end
|
37
|
+
|
38
|
+
def download_schema(uri)
|
39
|
+
request = Net::HTTP::Get.new(uri.to_s)
|
40
|
+
request['Accept'] = 'application/json+schema'
|
41
|
+
|
42
|
+
http = Net::HTTP.new(uri.hostname, uri.port)
|
43
|
+
http.read_timeout = 1
|
44
|
+
http.continue_timeout = 1
|
45
|
+
http.open_timeout = 1
|
46
|
+
|
47
|
+
http.request(request).body
|
48
|
+
end
|
49
|
+
|
50
|
+
def key(uri, schema)
|
51
|
+
root_schema = root(schema)
|
52
|
+
"#{root_schema.object_id}:#{uri}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def root(schema)
|
56
|
+
root = schema
|
57
|
+
loop do
|
58
|
+
break if root.parent.nil?
|
59
|
+
root = root.parent
|
60
|
+
end
|
61
|
+
root
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module JSchema
|
2
|
+
class Schema
|
3
|
+
VERSION_ID = 'http://json-schema.org/draft-04/schema#'.freeze
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def build(sch = {}, parent = nil, id = nil)
|
7
|
+
schema = sch || {}
|
8
|
+
|
9
|
+
check_schema_version schema
|
10
|
+
|
11
|
+
if (json_reference = schema['$ref'])
|
12
|
+
ref_uri = URI(json_reference)
|
13
|
+
SchemaRef.new(ref_uri, parent)
|
14
|
+
else
|
15
|
+
uri = establish_uri(schema, parent, id)
|
16
|
+
jschema = new(schema, uri, parent)
|
17
|
+
register_definitions schema, jschema
|
18
|
+
JSONReference.register_schema jschema
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def check_schema_version(schema)
|
25
|
+
version = schema['$schema']
|
26
|
+
if version && version != VERSION_ID
|
27
|
+
fail InvalidSchema, 'Specified schema version is not supported'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# rubocop:disable MethodLength
|
32
|
+
def establish_uri(schema, parent, id)
|
33
|
+
this_id = URI(schema['id'] || id || '#')
|
34
|
+
|
35
|
+
# RFC 3986, cl. 5.1
|
36
|
+
if parent
|
37
|
+
if parent.uri.absolute?
|
38
|
+
parent.uri.merge(this_id).normalize
|
39
|
+
elsif parent.uri.path.empty?
|
40
|
+
URI('#' + File.join(parent.uri.fragment, id || '')) # FIXME
|
41
|
+
else
|
42
|
+
# RFC 3986, cl. 5.1.4
|
43
|
+
fail InvalidSchema, 'Can not establish a base URI'
|
44
|
+
end
|
45
|
+
else
|
46
|
+
this_id
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def register_definitions(schema, parent)
|
51
|
+
if (definitions = schema['definitions'])
|
52
|
+
definitions.each do |definition, sch|
|
53
|
+
schema_def = build(sch, parent, "definitions/#{definition}")
|
54
|
+
JSONReference.register_schema schema_def
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
attr_reader :uri, :parent
|
61
|
+
|
62
|
+
def valid?(instance)
|
63
|
+
validate(instance).empty?
|
64
|
+
end
|
65
|
+
|
66
|
+
def validate(instance)
|
67
|
+
@validators.map do |validator|
|
68
|
+
validator.validate(instance)
|
69
|
+
end.compact
|
70
|
+
end
|
71
|
+
|
72
|
+
def to_s
|
73
|
+
uri.to_s
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def initialize(schema, uri, parent)
|
79
|
+
@uri = uri
|
80
|
+
@parent = parent
|
81
|
+
@validators = Validator.build(schema, self)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module JSchema
|
2
|
+
class SimpleValidator
|
3
|
+
class << self
|
4
|
+
def build(schema, parent)
|
5
|
+
# Validator keywords must be explicitly specified.
|
6
|
+
fail UnknownError unless keywords
|
7
|
+
|
8
|
+
args = schema.values_at(*keywords)
|
9
|
+
new(*args, parent) unless args.compact.empty?
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
attr_accessor :keywords
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :parent
|
18
|
+
|
19
|
+
def initialize(*args, parent)
|
20
|
+
@parent = parent
|
21
|
+
|
22
|
+
if validate_args(*args)
|
23
|
+
post_initialize(*args)
|
24
|
+
else
|
25
|
+
fail InvalidSchema
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def valid?(instance)
|
30
|
+
validate(instance).nil?
|
31
|
+
end
|
32
|
+
|
33
|
+
def validate(instance)
|
34
|
+
if !applicable_types || applicable_types.include?(instance.class)
|
35
|
+
validate_instance(instance)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# Hook method
|
42
|
+
def applicable_types; end
|
43
|
+
|
44
|
+
def invalid_schema(keyword, value)
|
45
|
+
fail InvalidSchema, "Invalid `#{keyword}` value: #{value.inspect}"
|
46
|
+
end
|
47
|
+
|
48
|
+
# Argument validation helpers
|
49
|
+
|
50
|
+
def boolean?(value)
|
51
|
+
value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
52
|
+
end
|
53
|
+
|
54
|
+
def integer?(value)
|
55
|
+
value.is_a?(Fixnum) || value.is_a?(Bignum)
|
56
|
+
end
|
57
|
+
|
58
|
+
def greater_or_equal_to?(value, limit)
|
59
|
+
integer?(value) && value >= limit
|
60
|
+
end
|
61
|
+
|
62
|
+
def number?(value)
|
63
|
+
integer?(value) || value.is_a?(Float) || value.is_a?(BigDecimal)
|
64
|
+
end
|
65
|
+
|
66
|
+
def unique_non_empty_array?(value)
|
67
|
+
value.is_a?(Array) &&
|
68
|
+
!value.empty? &&
|
69
|
+
value.size == value.uniq.size
|
70
|
+
end
|
71
|
+
|
72
|
+
def schema_array?(value, id)
|
73
|
+
unique_non_empty_array?(value) &&
|
74
|
+
value.to_enum.with_index.all? do |schema, index|
|
75
|
+
full_id = [id, index].join('/')
|
76
|
+
valid_schema? schema, full_id
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def valid_schema?(schema, id)
|
81
|
+
schema.is_a?(Hash) && !!Schema.build(schema, parent, id)
|
82
|
+
rescue InvalidSchema
|
83
|
+
false
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module JSchema
|
2
|
+
class StringLengthValidator < SimpleValidator
|
3
|
+
private
|
4
|
+
|
5
|
+
def post_initialize(length_limit)
|
6
|
+
@length_limit = length_limit
|
7
|
+
end
|
8
|
+
|
9
|
+
def applicable_types
|
10
|
+
[String]
|
11
|
+
end
|
12
|
+
|
13
|
+
def valid_length_limit?(length_limit, min_length_limit)
|
14
|
+
greater_or_equal_to? length_limit, min_length_limit
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module JSchema
|
2
|
+
module Validator
|
3
|
+
def self.build(schema, parent)
|
4
|
+
if schema.is_a?(Hash)
|
5
|
+
constants.map do |validator_class_sym|
|
6
|
+
validator_class = const_get(validator_class_sym)
|
7
|
+
validator_class.build(schema, parent)
|
8
|
+
end.compact
|
9
|
+
else
|
10
|
+
[]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module JSchema
|
2
|
+
module Validator
|
3
|
+
class AllOf < SimpleValidator
|
4
|
+
private
|
5
|
+
|
6
|
+
self.keywords = ['allOf']
|
7
|
+
|
8
|
+
def validate_args(all_of)
|
9
|
+
schema_array?(all_of, 'allOf') || invalid_schema('allOf', all_of)
|
10
|
+
end
|
11
|
+
|
12
|
+
def post_initialize(all_of)
|
13
|
+
@all_of = all_of.map.with_index do |sch, index|
|
14
|
+
Schema.build(sch, parent, "allOf/#{index}")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate_instance(instance)
|
19
|
+
valid = @all_of.all? do |schema|
|
20
|
+
schema.valid?(instance)
|
21
|
+
end
|
22
|
+
|
23
|
+
unless valid
|
24
|
+
"#{instance} must be valid against all the schemas"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module JSchema
|
2
|
+
module Validator
|
3
|
+
class AnyOf < SimpleValidator
|
4
|
+
private
|
5
|
+
|
6
|
+
self.keywords = ['anyOf']
|
7
|
+
|
8
|
+
def validate_args(any_of)
|
9
|
+
schema_array?(any_of, 'anyOf') || invalid_schema('anyOf', any_of)
|
10
|
+
end
|
11
|
+
|
12
|
+
def post_initialize(any_of)
|
13
|
+
@any_of = any_of.map.with_index do |sch, index|
|
14
|
+
Schema.build(sch, parent, "anyOf/#{index}")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate_instance(instance)
|
19
|
+
valid = @any_of.any? do |schema|
|
20
|
+
schema.valid?(instance)
|
21
|
+
end
|
22
|
+
|
23
|
+
unless valid
|
24
|
+
"#{instance} must be valid against any of the schemas"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module JSchema
|
2
|
+
module Validator
|
3
|
+
class Dependencies < SimpleValidator
|
4
|
+
private
|
5
|
+
|
6
|
+
self.keywords = ['dependencies']
|
7
|
+
|
8
|
+
def validate_args(dependencies)
|
9
|
+
valid_dependencies?(dependencies) ||
|
10
|
+
invalid_schema('dependencies', dependencies)
|
11
|
+
end
|
12
|
+
|
13
|
+
def valid_dependencies?(dependencies)
|
14
|
+
dependencies.is_a?(Hash) &&
|
15
|
+
dependencies.values.all? do |dependency|
|
16
|
+
valid_schema_dependency?(dependency) ||
|
17
|
+
valid_property_dependency?(dependency)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def valid_schema_dependency?(dependency)
|
22
|
+
dependency.is_a?(Hash)
|
23
|
+
end
|
24
|
+
|
25
|
+
def valid_property_dependency?(dependency)
|
26
|
+
unique_non_empty_array?(dependency) &&
|
27
|
+
dependency.all? { |property| property.is_a?(String) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def post_initialize(dependencies)
|
31
|
+
@dependencies = dependencies
|
32
|
+
end
|
33
|
+
|
34
|
+
def validate_instance(instance)
|
35
|
+
@dependencies.each do |property, validator|
|
36
|
+
if instance.key?(property)
|
37
|
+
errors = validate_against_dependency(instance, validator, property)
|
38
|
+
unless errors.empty?
|
39
|
+
return errors.first
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end and nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def validate_against_dependency(instance, validator, property)
|
46
|
+
case validator
|
47
|
+
when Hash
|
48
|
+
schema = Schema.build(validator, parent, property)
|
49
|
+
schema.validate(instance)
|
50
|
+
when Array
|
51
|
+
required = Validator::Required.new(validator)
|
52
|
+
required.validate(instance)
|
53
|
+
else
|
54
|
+
fail UnknownError
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def applicable_types
|
59
|
+
[Hash]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|