jschema 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,23 @@
|
|
1
|
+
module JSchema
|
2
|
+
module Validator
|
3
|
+
class Not < SimpleValidator
|
4
|
+
private
|
5
|
+
|
6
|
+
self.keywords = ['not']
|
7
|
+
|
8
|
+
def validate_args(schema)
|
9
|
+
valid_schema?(schema, 'not') || invalid_schema('not', schema)
|
10
|
+
end
|
11
|
+
|
12
|
+
def post_initialize(not_schema)
|
13
|
+
@schema = Schema.build(not_schema, parent, 'not')
|
14
|
+
end
|
15
|
+
|
16
|
+
def validate_instance(instance)
|
17
|
+
if @schema.valid?(instance)
|
18
|
+
"#{instance} must not validate against #{@schema}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module JSchema
|
2
|
+
module Validator
|
3
|
+
class OneOf < SimpleValidator
|
4
|
+
private
|
5
|
+
|
6
|
+
self.keywords = ['oneOf']
|
7
|
+
|
8
|
+
def validate_args(one_of)
|
9
|
+
schema_array?(one_of, 'oneOf') || invalid_schema('oneOf', one_of)
|
10
|
+
end
|
11
|
+
|
12
|
+
def post_initialize(one_of)
|
13
|
+
@one_of = one_of.map.with_index do |sch, index|
|
14
|
+
Schema.build(sch, parent, "oneOf/#{index}")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate_instance(instance)
|
19
|
+
valid = @one_of.one? do |schema|
|
20
|
+
schema.valid?(instance)
|
21
|
+
end
|
22
|
+
|
23
|
+
unless valid
|
24
|
+
"#{instance} must be valid against exactly one schema"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module JSchema
|
2
|
+
module Validator
|
3
|
+
class Pattern < SimpleValidator
|
4
|
+
private
|
5
|
+
|
6
|
+
# Fix because of Rubinius
|
7
|
+
unless defined? PrimitiveFailure
|
8
|
+
class PrimitiveFailure < Exception
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
self.keywords = ['pattern']
|
13
|
+
|
14
|
+
def validate_args(pattern)
|
15
|
+
Regexp.new(pattern)
|
16
|
+
true
|
17
|
+
rescue TypeError, PrimitiveFailure
|
18
|
+
invalid_schema 'pattern', pattern
|
19
|
+
end
|
20
|
+
|
21
|
+
def post_initialize(pattern)
|
22
|
+
@pattern = pattern
|
23
|
+
end
|
24
|
+
|
25
|
+
def validate_instance(instance)
|
26
|
+
unless instance.match(@pattern)
|
27
|
+
"#{instance} must match pattern #{@pattern.inspect}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def applicable_types
|
32
|
+
[String]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module JSchema
|
2
|
+
module Validator
|
3
|
+
class Properties < SimpleValidator
|
4
|
+
private
|
5
|
+
|
6
|
+
self.keywords = [
|
7
|
+
'properties',
|
8
|
+
'patternProperties',
|
9
|
+
'additionalProperties'
|
10
|
+
]
|
11
|
+
|
12
|
+
def validate_args(properties, pattern_properties, additional_properties)
|
13
|
+
validate_properties(properties)
|
14
|
+
validate_pattern_properties(pattern_properties)
|
15
|
+
validate_additional_properties(additional_properties)
|
16
|
+
end
|
17
|
+
|
18
|
+
def validate_properties(properties)
|
19
|
+
valid_properties?(properties) ||
|
20
|
+
invalid_schema('properties', properties)
|
21
|
+
end
|
22
|
+
|
23
|
+
def validate_pattern_properties(pattern_properties)
|
24
|
+
valid_properties?(pattern_properties) ||
|
25
|
+
invalid_schema('patternProperties', pattern_properties)
|
26
|
+
end
|
27
|
+
|
28
|
+
def validate_additional_properties(additional_properties)
|
29
|
+
(valid_properties?(additional_properties) ||
|
30
|
+
boolean?(additional_properties)) ||
|
31
|
+
invalid_schema('additionalProperties', additional_properties)
|
32
|
+
end
|
33
|
+
|
34
|
+
def valid_properties?(properties)
|
35
|
+
properties.nil? ||
|
36
|
+
(properties.is_a?(Hash) && properties.all? { |_, v| v.is_a?(Hash) })
|
37
|
+
end
|
38
|
+
|
39
|
+
def post_initialize(properties, pattern_properties, additional_properties)
|
40
|
+
@properties = properties
|
41
|
+
@additional_properties = additional_properties
|
42
|
+
@pattern_properties = pattern_properties
|
43
|
+
end
|
44
|
+
|
45
|
+
def applicable_types
|
46
|
+
[Hash]
|
47
|
+
end
|
48
|
+
|
49
|
+
def validate_instance(instance)
|
50
|
+
instance.each do |field, value|
|
51
|
+
schemas = schemas_for(field)
|
52
|
+
|
53
|
+
if schemas.empty? && @additional_properties == false
|
54
|
+
return "#{instance} must not have any additional fields"
|
55
|
+
end
|
56
|
+
|
57
|
+
schemas.each do |schema|
|
58
|
+
validation_errors = schema.validate(value)
|
59
|
+
unless validation_errors.empty?
|
60
|
+
return validation_errors.first
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def schemas_for(field)
|
70
|
+
[
|
71
|
+
properties_schema(field),
|
72
|
+
additional_properties_schema(field),
|
73
|
+
*pattern_properties_schema(field)
|
74
|
+
].compact
|
75
|
+
end
|
76
|
+
|
77
|
+
def properties_schema(field)
|
78
|
+
if @properties.is_a?(Hash)
|
79
|
+
if (sch = @properties[field])
|
80
|
+
Schema.build(sch, parent, 'properties')
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def additional_properties_schema(field)
|
86
|
+
if @additional_properties.is_a?(Hash)
|
87
|
+
if (sch = @additional_properties[field])
|
88
|
+
Schema.build(sch, parent, 'additionalProperties')
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def pattern_properties_schema(field)
|
94
|
+
schemas = []
|
95
|
+
if @pattern_properties.is_a?(Hash)
|
96
|
+
@pattern_properties.each do |pattern, sch|
|
97
|
+
if field.match(pattern)
|
98
|
+
schemas << Schema.build(sch, parent, 'patternProperties')
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
schemas
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module JSchema
|
2
|
+
module Validator
|
3
|
+
class Required < SimpleValidator
|
4
|
+
private
|
5
|
+
|
6
|
+
self.keywords = ['required']
|
7
|
+
|
8
|
+
def validate_args(required)
|
9
|
+
valid_required?(required) || invalid_schema('required', required)
|
10
|
+
end
|
11
|
+
|
12
|
+
def post_initialize(required)
|
13
|
+
@required = required
|
14
|
+
end
|
15
|
+
|
16
|
+
def applicable_types
|
17
|
+
[Hash]
|
18
|
+
end
|
19
|
+
|
20
|
+
def validate_instance(instance)
|
21
|
+
@required.each do |required_property|
|
22
|
+
if instance[required_property].nil?
|
23
|
+
return "#{instance} must have property `#{required_property}`"
|
24
|
+
end
|
25
|
+
end and nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def valid_required?(required)
|
29
|
+
unique_non_empty_array?(required) &&
|
30
|
+
required.all? { |req| req.is_a?(String) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module JSchema
|
2
|
+
module Validator
|
3
|
+
class Type < SimpleValidator
|
4
|
+
private
|
5
|
+
|
6
|
+
self.keywords = ['type']
|
7
|
+
|
8
|
+
def validate_args(type)
|
9
|
+
if type.is_a?(String) || unique_non_empty_array?(type)
|
10
|
+
true
|
11
|
+
else
|
12
|
+
invalid_schema 'type', type
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def post_initialize(type)
|
17
|
+
@json_types = Array(type)
|
18
|
+
@ruby_classes = @json_types.map do |json_type|
|
19
|
+
json_type_to_ruby_class(json_type)
|
20
|
+
end.flatten.compact
|
21
|
+
end
|
22
|
+
|
23
|
+
def validate_instance(instance)
|
24
|
+
unless @ruby_classes.one? { |type| instance.is_a?(type) }
|
25
|
+
error_message(instance)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def json_type_to_ruby_class(json_type)
|
30
|
+
case json_type
|
31
|
+
when 'object' then Hash
|
32
|
+
when 'null' then NilClass
|
33
|
+
when 'string' then String
|
34
|
+
when 'integer' then [Fixnum, Bignum]
|
35
|
+
when 'array' then Array
|
36
|
+
when 'boolean' then [TrueClass, FalseClass]
|
37
|
+
when 'number' then [Fixnum, Float, BigDecimal, Bignum]
|
38
|
+
else invalid_schema('type', json_type)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def error_message(instance)
|
43
|
+
types =
|
44
|
+
case @json_types.size
|
45
|
+
when 1
|
46
|
+
@json_types.first
|
47
|
+
when 2
|
48
|
+
@json_types.join(' or ')
|
49
|
+
when 3..Float::INFINITY
|
50
|
+
@json_types[0..-2].join(', ') << ", or #{@json_types.last}"
|
51
|
+
end
|
52
|
+
|
53
|
+
"#{instance.inspect} must be a #{types}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module JSchema
|
2
|
+
module Validator
|
3
|
+
class UniqueItems < SimpleValidator
|
4
|
+
private
|
5
|
+
|
6
|
+
self.keywords = ['uniqueItems']
|
7
|
+
|
8
|
+
def validate_args(unique_items)
|
9
|
+
boolean?(unique_items) || invalid_schema('uniqueItems', unique_items)
|
10
|
+
end
|
11
|
+
|
12
|
+
def post_initialize(unique_items)
|
13
|
+
@unique_items = unique_items
|
14
|
+
end
|
15
|
+
|
16
|
+
def validate_instance(instance)
|
17
|
+
if @unique_items && instance.size != instance.uniq.size
|
18
|
+
"#{instance} must contain only unique items"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def applicable_types
|
23
|
+
[Array]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module MiniTest
|
2
|
+
module Assertions
|
3
|
+
def assert_received(receiver, message, args = [])
|
4
|
+
mock = MiniTest::Mock.new
|
5
|
+
mock.expect(message, nil, args)
|
6
|
+
receiver_stub = ->(*arg) { mock.__send__ message, *arg }
|
7
|
+
|
8
|
+
receiver.stub message, receiver_stub do
|
9
|
+
yield if block_given?
|
10
|
+
end
|
11
|
+
|
12
|
+
mock.verify
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module StringLengthValidatorTests
|
2
|
+
def test_that_argument_can_be_big_integer
|
3
|
+
validator(2**64)
|
4
|
+
end
|
5
|
+
|
6
|
+
def test_that_argument_is_integer
|
7
|
+
assert_raises(JSchema::InvalidSchema) { validator('invalid') }
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_that_argument_is_not_null
|
11
|
+
assert_raises(JSchema::InvalidSchema) { validator(nil) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_that_validation_always_passes_for_non_string_instances
|
15
|
+
assert validator(1).valid?(nil)
|
16
|
+
assert validator(1).valid?([1, 2])
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require_relative 'assert_received'
|
3
|
+
|
4
|
+
class TestAssertReceived < Minitest::Test
|
5
|
+
def test_message_sending_without_params
|
6
|
+
assert_received(Object, :class) do
|
7
|
+
Object.class
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_message_sending_with_params
|
12
|
+
assert_received(String, :==, [Fixnum]) do
|
13
|
+
String == Fixnum
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_assertion_failure_when_message_is_not_received
|
18
|
+
assert_raises(MockExpectationError) do
|
19
|
+
assert_received(String, :class) do
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_assertion_failure_when_unexpected_message_is_received
|
25
|
+
assert_raises(MockExpectationError) do
|
26
|
+
assert_received(String, :class) do
|
27
|
+
String.name
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_assertion_failure_when_unexpected_params_are_received
|
33
|
+
assert_raises(MockExpectationError) do
|
34
|
+
assert_received(String, :==, [Fixnum]) do
|
35
|
+
String == Bignum
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'webmock/minitest'
|
3
|
+
require 'jschema'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
class TestIntegration < Minitest::Test
|
7
|
+
def test_simple_schema
|
8
|
+
stub_request(:get, 'http://json-schema.org/geo')
|
9
|
+
.to_return(body: Pathname.new('test/fixtures/geo.json'))
|
10
|
+
|
11
|
+
validate 'json_schema1.json', 'json_data1.json'
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_advanced_schema
|
15
|
+
validate 'json_schema2.json', 'json_data2.json'
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def validate(schema_file, data_file)
|
21
|
+
sch = json_fixture(schema_file).freeze
|
22
|
+
schema = JSchema::Schema.build(sch)
|
23
|
+
data = json_fixture(data_file)
|
24
|
+
assert schema.valid?(data)
|
25
|
+
end
|
26
|
+
|
27
|
+
def json_fixture(filename)
|
28
|
+
JSON.parse open(File.join('test', 'fixtures', filename)).read
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'webmock/minitest'
|
3
|
+
require 'jschema'
|
4
|
+
require 'ostruct'
|
5
|
+
|
6
|
+
require_relative 'assert_received'
|
7
|
+
|
8
|
+
class TestJSONReference < Minitest::Test
|
9
|
+
def test_schema_registration_and_dereferencing
|
10
|
+
schema = generate_schema('registered')
|
11
|
+
JSchema::JSONReference.register_schema schema
|
12
|
+
assert_equal dereference(schema), schema
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_schema_dereferencing_with_same_uri
|
16
|
+
schema1 = generate_schema('uri')
|
17
|
+
schema2 = generate_schema('uri')
|
18
|
+
JSchema::JSONReference.register_schema schema1
|
19
|
+
JSchema::JSONReference.register_schema schema2
|
20
|
+
|
21
|
+
assert_equal dereference(schema1), schema1
|
22
|
+
assert_equal dereference(schema2), schema2
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_dereferencing_external_schema
|
26
|
+
external_schema = { 'type' => 'number' }
|
27
|
+
schema_uri = 'http://example.com/geo'
|
28
|
+
|
29
|
+
expected_schema_args = [external_schema, URI(schema_uri), nil]
|
30
|
+
assert_received JSchema::Schema, :new, expected_schema_args do
|
31
|
+
JSchema::JSONReference.stub :register_schema, nil do
|
32
|
+
dereference_external_schema schema_uri, external_schema.to_json
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_that_external_schema_is_cached_after_dereferencing
|
38
|
+
schema = Object.new
|
39
|
+
JSchema::Schema.stub :new, schema do
|
40
|
+
assert_received JSchema::JSONReference, :register_schema, [schema] do
|
41
|
+
dereference_external_schema 'http://example.com', '{}'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_dereferencing_external_schema_when_it_is_not_valid
|
47
|
+
assert_raises(JSchema::InvalidSchema) do
|
48
|
+
dereference_external_schema 'http://example.com/', '}'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_dereferencing_external_schema_when_it_is_not_available
|
53
|
+
assert_raises(JSchema::InvalidSchema) do
|
54
|
+
stub_request(:get, //).to_timeout
|
55
|
+
dereference generate_schema('http://example.com/')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_dereferencing_external_schema_when_protocol_is_not_supported
|
60
|
+
assert_raises(JSchema::InvalidSchema) do
|
61
|
+
dereference_external_schema 'ftp://example.com/', '{}'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def dereference_external_schema(uri, response_schema)
|
68
|
+
stub_request(:get, uri).to_return(body: response_schema)
|
69
|
+
dereference generate_schema(uri)
|
70
|
+
end
|
71
|
+
|
72
|
+
def dereference(schema)
|
73
|
+
JSchema::JSONReference.dereference(schema.uri, schema)
|
74
|
+
end
|
75
|
+
|
76
|
+
def generate_schema(uri)
|
77
|
+
OpenStruct.new(uri: URI(uri), id: generate_id)
|
78
|
+
end
|
79
|
+
|
80
|
+
def generate_id
|
81
|
+
@id ||= 0
|
82
|
+
@id += 1
|
83
|
+
end
|
84
|
+
end
|