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