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
@@ -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
|