jschema 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/lib/jschema.rb +41 -0
  3. data/lib/jschema/json_reference.rb +65 -0
  4. data/lib/jschema/schema.rb +84 -0
  5. data/lib/jschema/schema_ref.rb +13 -0
  6. data/lib/jschema/simple_validator.rb +86 -0
  7. data/lib/jschema/string_length_validator.rb +17 -0
  8. data/lib/jschema/validator.rb +14 -0
  9. data/lib/jschema/validator/all_of.rb +29 -0
  10. data/lib/jschema/validator/any_of.rb +29 -0
  11. data/lib/jschema/validator/dependencies.rb +63 -0
  12. data/lib/jschema/validator/enum.rb +23 -0
  13. data/lib/jschema/validator/format.rb +75 -0
  14. data/lib/jschema/validator/items.rb +79 -0
  15. data/lib/jschema/validator/max_items.rb +28 -0
  16. data/lib/jschema/validator/max_length.rb +23 -0
  17. data/lib/jschema/validator/max_properties.rb +31 -0
  18. data/lib/jschema/validator/maximum.rb +31 -0
  19. data/lib/jschema/validator/min_items.rb +28 -0
  20. data/lib/jschema/validator/min_length.rb +23 -0
  21. data/lib/jschema/validator/min_properties.rb +31 -0
  22. data/lib/jschema/validator/minimum.rb +31 -0
  23. data/lib/jschema/validator/multiple_of.rb +32 -0
  24. data/lib/jschema/validator/not.rb +23 -0
  25. data/lib/jschema/validator/one_of.rb +29 -0
  26. data/lib/jschema/validator/pattern.rb +36 -0
  27. data/lib/jschema/validator/properties.rb +106 -0
  28. data/lib/jschema/validator/required.rb +34 -0
  29. data/lib/jschema/validator/type.rb +57 -0
  30. data/lib/jschema/validator/unique_items.rb +27 -0
  31. data/test/assert_received.rb +15 -0
  32. data/test/string_length_validator_tests.rb +18 -0
  33. data/test/test_assert_received.rb +39 -0
  34. data/test/test_integration.rb +30 -0
  35. data/test/test_json_reference.rb +84 -0
  36. data/test/test_schema.rb +125 -0
  37. data/test/test_schema_ref.rb +11 -0
  38. data/test/test_validator.rb +29 -0
  39. data/test/validator/argument_is_array_of_schemas_tests.rb +22 -0
  40. data/test/validator/assertions.rb +56 -0
  41. data/test/validator/properties_limit_tests.rb +13 -0
  42. data/test/validator/schema_validation_helpers.rb +29 -0
  43. data/test/validator/test_all_of.rb +30 -0
  44. data/test/validator/test_any_of.rb +31 -0
  45. data/test/validator/test_dependencies.rb +106 -0
  46. data/test/validator/test_enum.rb +30 -0
  47. data/test/validator/test_format.rb +70 -0
  48. data/test/validator/test_items.rb +113 -0
  49. data/test/validator/test_max_items.rb +26 -0
  50. data/test/validator/test_max_length.rb +30 -0
  51. data/test/validator/test_max_properties.rb +29 -0
  52. data/test/validator/test_maximum.rb +58 -0
  53. data/test/validator/test_min_items.rb +27 -0
  54. data/test/validator/test_min_length.rb +30 -0
  55. data/test/validator/test_min_properties.rb +29 -0
  56. data/test/validator/test_minimum.rb +58 -0
  57. data/test/validator/test_multiple_of.rb +38 -0
  58. data/test/validator/test_not.rb +32 -0
  59. data/test/validator/test_one_of.rb +31 -0
  60. data/test/validator/test_pattern.rb +32 -0
  61. data/test/validator/test_properties.rb +136 -0
  62. data/test/validator/test_required.rb +30 -0
  63. data/test/validator/test_simple_validator.rb +100 -0
  64. data/test/validator/test_type.rb +97 -0
  65. data/test/validator/test_unique_items.rb +30 -0
  66. data/test/validator/validation_against_schemas_tests.rb +24 -0
  67. 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,13 @@
1
+ module JSchema
2
+ # Schema reference is lazy evaluated.
3
+ class SchemaRef < Delegator
4
+ def initialize(uri, parent)
5
+ @uri = uri
6
+ @parent = parent
7
+ end
8
+
9
+ def __getobj__
10
+ @schema ||= JSONReference.dereference(@uri, @parent)
11
+ end
12
+ end
13
+ 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