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