compel 0.2.0 → 0.3.1
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 +4 -4
- data/README.md +88 -20
- data/lib/compel/builder/array.rb +31 -0
- data/lib/compel/builder/boolean.rb +7 -0
- data/lib/compel/builder/common.rb +2 -2
- data/lib/compel/builder/common_value.rb +4 -4
- data/lib/compel/builder/hash.rb +3 -1
- data/lib/compel/builder/methods.rb +5 -0
- data/lib/compel/builder/schema.rb +4 -0
- data/lib/compel/coercion/coercion.rb +47 -0
- data/lib/compel/coercion/nil_result.rb +13 -0
- data/lib/compel/coercion/result.rb +37 -0
- data/lib/compel/coercion/types/array.rb +15 -0
- data/lib/compel/coercion/{boolean.rb → types/boolean.rb} +1 -3
- data/lib/compel/coercion/types/date.rb +36 -0
- data/lib/compel/coercion/types/datetime.rb +36 -0
- data/lib/compel/coercion/{float.rb → types/float.rb} +2 -2
- data/lib/compel/coercion/{hash.rb → types/hash.rb} +2 -2
- data/lib/compel/coercion/{integer.rb → types/integer.rb} +2 -2
- data/lib/compel/coercion/{json.rb → types/json.rb} +2 -2
- data/lib/compel/coercion/{regexp.rb → types/regexp.rb} +2 -4
- data/lib/compel/coercion/{string.rb → types/string.rb} +3 -5
- data/lib/compel/coercion/types/time.rb +36 -0
- data/lib/compel/coercion/types/type.rb +29 -0
- data/lib/compel/contract.rb +14 -42
- data/lib/compel/errors.rb +13 -17
- data/lib/compel/exceptions/invalid_object_error.rb +9 -0
- data/lib/compel/result.rb +30 -0
- data/lib/compel/validation.rb +6 -4
- data/lib/compel/validators/array_validator.rb +107 -0
- data/lib/compel/validators/base.rb +11 -1
- data/lib/compel/validators/hash_validator.rb +77 -19
- data/lib/compel/validators/type_validator.rb +42 -11
- data/lib/compel/version.rb +1 -1
- data/lib/compel.rb +6 -15
- data/spec/compel/builder_spec.rb +354 -144
- data/spec/compel/coercion_spec.rb +16 -1
- data/spec/compel/compel_spec.rb +315 -302
- data/spec/compel/validation_spec.rb +97 -115
- data/spec/support/sinatra_app.rb +2 -2
- metadata +21 -15
- data/lib/compel/coercion/date.rb +0 -30
- data/lib/compel/coercion/datetime.rb +0 -30
- data/lib/compel/coercion/time.rb +0 -30
- data/lib/compel/coercion/type.rb +0 -17
- data/lib/compel/coercion.rb +0 -31
- data/lib/compel/exceptions/invalid_hash_error.rb +0 -9
data/lib/compel/contract.rb
CHANGED
@@ -2,59 +2,31 @@ module Compel
|
|
2
2
|
|
3
3
|
class Contract
|
4
4
|
|
5
|
-
attr_reader :
|
6
|
-
:serialized_errors
|
5
|
+
attr_reader :object, :schema
|
7
6
|
|
8
|
-
def initialize(
|
9
|
-
|
10
|
-
raise Compel::TypeError, 'must be an Hash'
|
11
|
-
end
|
12
|
-
|
13
|
-
@hash = Hashie::Mash.new(hash)
|
7
|
+
def initialize(object, schema)
|
8
|
+
@object = object
|
14
9
|
@schema = schema
|
15
|
-
@coerced_hash = Hashie::Mash.new
|
16
10
|
end
|
17
11
|
|
18
12
|
def validate
|
19
|
-
|
20
|
-
|
21
|
-
@errors = validator.errors
|
22
|
-
@coerced_hash = validator.output
|
23
|
-
|
24
|
-
self
|
25
|
-
end
|
26
|
-
|
27
|
-
def coerced_hash
|
28
|
-
# @hash has all params that are not affected by the validation
|
29
|
-
@hash.merge(@coerced_hash)
|
13
|
+
Result.new(setup!.validate)
|
30
14
|
end
|
31
15
|
|
32
|
-
|
33
|
-
coerced_hash.tap do |hash|
|
34
|
-
if !valid?
|
35
|
-
hash[:errors] = serialized_errors
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def valid?
|
41
|
-
@errors.empty?
|
42
|
-
end
|
16
|
+
private
|
43
17
|
|
44
|
-
def
|
45
|
-
|
18
|
+
def setup!
|
19
|
+
validator_klass.new(object, schema)
|
46
20
|
end
|
47
21
|
|
48
|
-
def
|
49
|
-
if
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
22
|
+
def validator_klass
|
23
|
+
if schema.type == Coercion::Hash
|
24
|
+
Validators::HashValidator
|
25
|
+
elsif schema.type == Coercion::Array
|
26
|
+
Validators::ArrayValidator
|
27
|
+
else
|
28
|
+
Validators::TypeValidator
|
55
29
|
end
|
56
|
-
|
57
|
-
coerced_hash
|
58
30
|
end
|
59
31
|
|
60
32
|
end
|
data/lib/compel/errors.rb
CHANGED
@@ -7,33 +7,29 @@ module Compel
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def add(key, error)
|
10
|
-
if error.
|
11
|
-
|
12
|
-
|
13
|
-
end
|
10
|
+
if error.empty?
|
11
|
+
return
|
12
|
+
end
|
14
13
|
|
14
|
+
if error.is_a?(Compel::Errors) || error.is_a?(Hash)
|
15
15
|
if @errors[key].nil?
|
16
16
|
@errors[key] = {}
|
17
17
|
end
|
18
18
|
|
19
19
|
@errors[key].merge!(error.to_hash)
|
20
|
+
else
|
21
|
+
if @errors[key].nil?
|
22
|
+
@errors[key] = []
|
23
|
+
end
|
20
24
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
if !error.is_a?(Array)
|
25
|
-
error = [error]
|
26
|
-
end
|
27
|
-
|
28
|
-
if error.empty?
|
29
|
-
return
|
30
|
-
end
|
25
|
+
if !error.is_a?(Array)
|
26
|
+
error = [error]
|
27
|
+
end
|
31
28
|
|
32
|
-
|
33
|
-
@errors[key] = []
|
29
|
+
@errors[key].concat(error)
|
34
30
|
end
|
35
31
|
|
36
|
-
@errors
|
32
|
+
@errors
|
37
33
|
end
|
38
34
|
|
39
35
|
def length
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Compel
|
2
|
+
|
3
|
+
class Result
|
4
|
+
|
5
|
+
attr_reader :value, :errors
|
6
|
+
|
7
|
+
def initialize(validator)
|
8
|
+
@valid = validator.valid?
|
9
|
+
@value = validator.serialize
|
10
|
+
@errors = validator.serialize_errors
|
11
|
+
end
|
12
|
+
|
13
|
+
def valid?
|
14
|
+
@valid
|
15
|
+
end
|
16
|
+
|
17
|
+
def raise?
|
18
|
+
if !valid?
|
19
|
+
exception = InvalidObjectError.new
|
20
|
+
exception.object = value
|
21
|
+
|
22
|
+
raise exception, 'object has errors'
|
23
|
+
end
|
24
|
+
|
25
|
+
value
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
data/lib/compel/validation.rb
CHANGED
@@ -3,6 +3,12 @@ module Compel
|
|
3
3
|
module Validation
|
4
4
|
|
5
5
|
def validate(value, type, options)
|
6
|
+
# if a value is required and not given,
|
7
|
+
# don't do any other validation
|
8
|
+
if !!options[:required] && value.nil?
|
9
|
+
return ['is required']
|
10
|
+
end
|
11
|
+
|
6
12
|
errors = []
|
7
13
|
|
8
14
|
options.each do |option, option_value|
|
@@ -10,13 +16,9 @@ module Compel
|
|
10
16
|
# https://github.com/mattt/sinatra-param
|
11
17
|
# by Mattt Thompson (@mattt)
|
12
18
|
case option.to_sym
|
13
|
-
when :required
|
14
|
-
errors << 'is required' if option_value && value.nil?
|
15
19
|
when :format
|
16
20
|
if type == Coercion::String && !value.nil?
|
17
21
|
errors << "must match format #{option_value.source}" unless value =~ option_value
|
18
|
-
else
|
19
|
-
errors << 'must be a string if using the format validation'
|
20
22
|
end
|
21
23
|
when :is
|
22
24
|
errors << "must be #{option_value}" unless value === option_value
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Compel
|
2
|
+
module Validators
|
3
|
+
|
4
|
+
class ArrayValidator < Base
|
5
|
+
|
6
|
+
attr_reader :items_schema
|
7
|
+
|
8
|
+
def initialize(input, schema)
|
9
|
+
super
|
10
|
+
|
11
|
+
@errors = Errors.new
|
12
|
+
@output = []
|
13
|
+
@items_schema = schema.options[:items]
|
14
|
+
end
|
15
|
+
|
16
|
+
def validate
|
17
|
+
unless array_valid?
|
18
|
+
return self
|
19
|
+
end
|
20
|
+
|
21
|
+
if items_schema.nil?
|
22
|
+
@output = input
|
23
|
+
return self
|
24
|
+
end
|
25
|
+
|
26
|
+
items_validator = \
|
27
|
+
ArrayItemsValidator.validate(input, items_schema)
|
28
|
+
|
29
|
+
@output = items_validator.output
|
30
|
+
|
31
|
+
unless items_validator.valid?
|
32
|
+
@errors = items_validator.errors
|
33
|
+
end
|
34
|
+
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def serialize_errors
|
39
|
+
@errors.to_hash
|
40
|
+
end
|
41
|
+
|
42
|
+
alias_method :serialize, :output
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def array_valid?
|
47
|
+
if !schema.required? && input.nil?
|
48
|
+
return false
|
49
|
+
end
|
50
|
+
|
51
|
+
array_errors = []
|
52
|
+
|
53
|
+
unless input.is_a?(Array)
|
54
|
+
array_errors << "'#{input}' is not a valid Array"
|
55
|
+
end
|
56
|
+
|
57
|
+
array_errors += \
|
58
|
+
Validation.validate(input, schema.type, schema.options)
|
59
|
+
|
60
|
+
unless array_errors.empty?
|
61
|
+
errors.add(:base, array_errors)
|
62
|
+
return false
|
63
|
+
end
|
64
|
+
|
65
|
+
true
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
class ArrayItemsValidator < Base
|
71
|
+
|
72
|
+
def initialize(input, schema)
|
73
|
+
super
|
74
|
+
|
75
|
+
@output = []
|
76
|
+
@errors = Errors.new
|
77
|
+
end
|
78
|
+
|
79
|
+
def validate
|
80
|
+
input.each_with_index do |item, index|
|
81
|
+
|
82
|
+
if !schema.required? && item.nil?
|
83
|
+
next
|
84
|
+
end
|
85
|
+
|
86
|
+
item_validator = \
|
87
|
+
if schema.type == Coercion::Hash
|
88
|
+
HashValidator.validate(item, schema)
|
89
|
+
else
|
90
|
+
TypeValidator.validate(item, schema)
|
91
|
+
end
|
92
|
+
|
93
|
+
output << item_validator.serialize
|
94
|
+
|
95
|
+
if !item_validator.valid?
|
96
|
+
# added errors for the index of that invalid array value
|
97
|
+
errors.add("#{index}", item_validator.serialize_errors)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
self
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
@@ -9,8 +9,18 @@ module Compel
|
|
9
9
|
:schema
|
10
10
|
|
11
11
|
def initialize(input, schema)
|
12
|
-
@input = input
|
12
|
+
@input = input.nil? ? schema.default_value : input
|
13
13
|
@schema = schema
|
14
|
+
@output = nil
|
15
|
+
@errors = []
|
16
|
+
end
|
17
|
+
|
18
|
+
def valid?
|
19
|
+
@errors.empty?
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.validate(input, schema)
|
23
|
+
new(input, schema).validate
|
14
24
|
end
|
15
25
|
|
16
26
|
end
|
@@ -1,45 +1,103 @@
|
|
1
1
|
# Validates values of an hash recursively
|
2
2
|
# output is an hash with coerced values
|
3
3
|
# errors is a Compel::Errors
|
4
|
-
|
5
|
-
# This is not beauty,
|
6
|
-
# but it's tested and working.
|
7
4
|
module Compel
|
8
5
|
module Validators
|
9
6
|
|
10
7
|
class HashValidator < Base
|
11
8
|
|
9
|
+
attr_reader :keys_schemas
|
10
|
+
|
12
11
|
def initialize(input, schema)
|
13
12
|
super
|
14
|
-
@output = Hashie::Mash.new
|
15
|
-
@errors = Errors.new
|
16
13
|
|
14
|
+
@errors = Errors.new
|
17
15
|
@keys_schemas = schema.options[:keys]
|
18
16
|
end
|
19
17
|
|
20
18
|
def validate
|
21
|
-
|
19
|
+
unless root_hash_valid?
|
20
|
+
return self
|
21
|
+
end
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
@input = Hashie::Mash.new(input)
|
24
|
+
|
25
|
+
keys_validator = \
|
26
|
+
HashKeysValidator.validate(input, keys_schemas)
|
27
|
+
|
28
|
+
@errors = keys_validator.errors
|
29
|
+
@output = keys_validator.output
|
30
|
+
|
31
|
+
self
|
32
|
+
end
|
27
33
|
|
28
|
-
|
29
|
-
|
34
|
+
def serialize
|
35
|
+
coerced = output.is_a?(Hash) ? input.merge(output) : Hashie::Mash.new
|
36
|
+
|
37
|
+
coerced.tap do |hash|
|
38
|
+
if !errors.empty?
|
39
|
+
hash[:errors] = serialize_errors
|
30
40
|
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def serialize_errors
|
45
|
+
errors.to_hash
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def root_hash_valid?
|
51
|
+
if !schema.required? && input.nil?
|
52
|
+
return false
|
53
|
+
end
|
54
|
+
|
55
|
+
root_hash = TypeValidator.validate(input, schema)
|
31
56
|
|
32
|
-
|
33
|
-
|
34
|
-
|
57
|
+
unless root_hash.valid?
|
58
|
+
errors.add(:base, root_hash.errors)
|
59
|
+
return false
|
60
|
+
end
|
61
|
+
|
62
|
+
true
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
35
66
|
|
36
|
-
|
37
|
-
|
67
|
+
class HashKeysValidator < Base
|
68
|
+
|
69
|
+
attr_reader :schemas
|
70
|
+
|
71
|
+
def initialize(input, schemas)
|
72
|
+
super
|
73
|
+
|
74
|
+
@output = {}
|
75
|
+
@errors = Errors.new
|
76
|
+
@schemas = schemas
|
77
|
+
end
|
78
|
+
|
79
|
+
def validate
|
80
|
+
schemas.keys.each do |key|
|
81
|
+
value = output[key].nil? ? input[key] : output[key]
|
82
|
+
|
83
|
+
validator = TypeValidator.validate(value, schemas[key])
|
84
|
+
|
85
|
+
unless validator.output.nil?
|
86
|
+
output[key] = validator.output
|
38
87
|
end
|
39
88
|
|
40
|
-
|
41
|
-
|
89
|
+
unless validator.valid?
|
90
|
+
errors.add(key, validator.errors)
|
91
|
+
next
|
42
92
|
end
|
93
|
+
|
94
|
+
if input[key].is_a?(Hash)
|
95
|
+
hash_validator = HashValidator.validate(input[key], schemas[key])
|
96
|
+
|
97
|
+
errors.add(key, hash_validator.errors)
|
98
|
+
output[key] = hash_validator.output
|
99
|
+
end
|
100
|
+
|
43
101
|
end
|
44
102
|
|
45
103
|
self
|
@@ -6,24 +6,55 @@ module Compel
|
|
6
6
|
|
7
7
|
class TypeValidator < Base
|
8
8
|
|
9
|
-
def initialize(input, schema)
|
10
|
-
super
|
11
|
-
@output = nil
|
12
|
-
end
|
13
|
-
|
14
9
|
def validate
|
15
|
-
|
10
|
+
if !schema.required? && input.nil?
|
11
|
+
return self
|
12
|
+
end
|
13
|
+
|
14
|
+
options = input, schema.type, schema.options
|
15
|
+
|
16
|
+
# coerce
|
17
|
+
coercion_result = Coercion.coerce(*options)
|
18
|
+
|
19
|
+
unless coercion_result.valid?
|
20
|
+
@errors = [coercion_result.error]
|
21
|
+
return self
|
22
|
+
end
|
23
|
+
|
24
|
+
@output = coercion_result.coerced
|
16
25
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
26
|
+
# validate
|
27
|
+
@errors = Validation.validate(*options)
|
28
|
+
|
29
|
+
# validate array values
|
30
|
+
if schema.type == Coercion::Array && errors.empty?
|
31
|
+
validate_array_values(input)
|
22
32
|
end
|
23
33
|
|
24
34
|
self
|
25
35
|
end
|
26
36
|
|
37
|
+
def validate_array_values(values)
|
38
|
+
result = Result.new \
|
39
|
+
ArrayValidator.validate(values, schema)
|
40
|
+
|
41
|
+
@output = result.value
|
42
|
+
|
43
|
+
if !result.valid?
|
44
|
+
# TODO: ArrayValidator should do this for me:
|
45
|
+
# remove invalid coerced index,
|
46
|
+
# and set the original value
|
47
|
+
result.errors.keys.each do |index|
|
48
|
+
@output[index.to_i] = values[index.to_i]
|
49
|
+
end
|
50
|
+
|
51
|
+
@errors = result.errors
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
alias_method :serialize, :output
|
56
|
+
alias_method :serialize_errors, :errors
|
57
|
+
|
27
58
|
end
|
28
59
|
|
29
60
|
end
|
data/lib/compel/version.rb
CHANGED
data/lib/compel.rb
CHANGED
@@ -2,29 +2,20 @@ require 'json'
|
|
2
2
|
require 'hashie'
|
3
3
|
require 'hashie/extensions/symbolize_keys'
|
4
4
|
|
5
|
-
require 'compel/
|
6
|
-
require 'compel/coercion/integer'
|
7
|
-
require 'compel/coercion/float'
|
8
|
-
require 'compel/coercion/string'
|
9
|
-
require 'compel/coercion/date'
|
10
|
-
require 'compel/coercion/time'
|
11
|
-
require 'compel/coercion/datetime'
|
12
|
-
require 'compel/coercion/hash'
|
13
|
-
require 'compel/coercion/json'
|
14
|
-
require 'compel/coercion/boolean'
|
15
|
-
require 'compel/coercion/regexp'
|
16
|
-
|
17
|
-
require 'compel/exceptions/invalid_hash_error'
|
5
|
+
require 'compel/exceptions/invalid_object_error'
|
18
6
|
require 'compel/exceptions/validation_error'
|
19
7
|
require 'compel/exceptions/type_error'
|
20
8
|
|
9
|
+
require 'compel/result'
|
10
|
+
|
21
11
|
require 'compel/validators/base'
|
22
12
|
require 'compel/validators/type_validator'
|
23
13
|
require 'compel/validators/hash_validator'
|
14
|
+
require 'compel/validators/array_validator'
|
24
15
|
|
25
16
|
require 'compel/builder/methods'
|
17
|
+
require 'compel/coercion/coercion'
|
26
18
|
require 'compel/contract'
|
27
|
-
require 'compel/coercion'
|
28
19
|
require 'compel/validation'
|
29
20
|
require 'compel/errors'
|
30
21
|
|
@@ -41,7 +32,7 @@ module Compel
|
|
41
32
|
end
|
42
33
|
|
43
34
|
def self.run(params, schema)
|
44
|
-
Contract.new(params, schema).validate
|
35
|
+
Contract.new(params, schema).validate
|
45
36
|
end
|
46
37
|
|
47
38
|
end
|