compel 0.1.3 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +7 -0
- data/README.md +65 -54
- data/lib/compel/builder/boolean.rb +13 -0
- data/lib/compel/builder/common.rb +24 -0
- data/lib/compel/builder/common_value.rb +34 -0
- data/lib/compel/builder/date.rb +23 -0
- data/lib/compel/builder/datetime.rb +23 -0
- data/lib/compel/builder/float.rb +15 -0
- data/lib/compel/builder/hash.rb +22 -0
- data/lib/compel/builder/integer.rb +15 -0
- data/lib/compel/builder/json.rb +13 -0
- data/lib/compel/builder/methods.rb +60 -0
- data/lib/compel/builder/schema.rb +27 -0
- data/lib/compel/builder/string.rb +30 -0
- data/lib/compel/builder/time.rb +23 -0
- data/lib/compel/coercion/boolean.rb +1 -1
- data/lib/compel/coercion/date.rb +19 -2
- data/lib/compel/coercion/datetime.rb +19 -2
- data/lib/compel/coercion/float.rb +1 -1
- data/lib/compel/coercion/hash.rb +1 -1
- data/lib/compel/coercion/integer.rb +1 -1
- data/lib/compel/coercion/json.rb +1 -1
- data/lib/compel/coercion/regexp.rb +17 -0
- data/lib/compel/coercion/string.rb +1 -1
- data/lib/compel/coercion/time.rb +19 -2
- data/lib/compel/coercion/type.rb +0 -13
- data/lib/compel/coercion.rb +8 -10
- data/lib/compel/contract.rb +18 -62
- data/lib/compel/exceptions/invalid_hash_error.rb +9 -0
- data/lib/compel/exceptions/type_error.rb +7 -0
- data/lib/compel/exceptions/validation_error.rb +7 -0
- data/lib/compel/validation.rb +36 -50
- data/lib/compel/validators/base.rb +19 -0
- data/lib/compel/validators/hash_validator.rb +51 -0
- data/lib/compel/validators/type_validator.rb +30 -0
- data/lib/compel/version.rb +1 -1
- data/lib/compel.rb +16 -11
- data/spec/compel/builder_spec.rb +226 -0
- data/spec/compel/coercion_spec.rb +85 -18
- data/spec/compel/compel_spec.rb +368 -160
- data/spec/compel/sinatra_integration_spec.rb +73 -0
- data/spec/compel/validation_spec.rb +122 -8
- data/spec/spec_helper.rb +19 -0
- data/spec/support/sinatra_app.rb +43 -0
- metadata +28 -10
- data/lib/compel/invalid_params_error.rb +0 -9
- data/lib/compel/param.rb +0 -46
- data/lib/compel/param_type_error.rb +0 -7
- data/lib/compel/param_validation_error.rb +0 -7
- data/spec/compel/contract_spec.rb +0 -36
- data/spec/compel/param_spec.rb +0 -25
data/lib/compel/coercion/time.rb
CHANGED
@@ -3,8 +3,25 @@ module Compel
|
|
3
3
|
|
4
4
|
class Time < Type
|
5
5
|
|
6
|
-
def coerce
|
7
|
-
|
6
|
+
def coerce!
|
7
|
+
format = options[:format] || '%FT%T'
|
8
|
+
|
9
|
+
if value.is_a?(::Time)
|
10
|
+
@value = value.strftime(format)
|
11
|
+
end
|
12
|
+
|
13
|
+
coerced = ::Time.strptime(value, format)
|
14
|
+
|
15
|
+
if coerced.strftime(format) == value
|
16
|
+
return coerced
|
17
|
+
end
|
18
|
+
|
19
|
+
fail
|
20
|
+
|
21
|
+
rescue
|
22
|
+
raise \
|
23
|
+
Compel::TypeError,
|
24
|
+
"'#{value}' is not a parsable time with format: #{format}"
|
8
25
|
end
|
9
26
|
|
10
27
|
end
|
data/lib/compel/coercion/type.rb
CHANGED
data/lib/compel/coercion.rb
CHANGED
@@ -10,20 +10,18 @@ module Compel
|
|
10
10
|
return nil if value.nil?
|
11
11
|
|
12
12
|
begin
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
coercion_klass(type).new(value, options).coerce!
|
14
|
+
rescue Compel::TypeError => exception
|
15
|
+
raise exception
|
16
16
|
rescue
|
17
|
-
|
18
|
-
end
|
19
|
-
end
|
17
|
+
type_split = "#{type}".split('::')
|
20
18
|
|
21
|
-
|
22
|
-
|
19
|
+
raise Compel::TypeError, "'#{value}' is not a valid #{type_split[-1]}"
|
20
|
+
end
|
23
21
|
end
|
24
22
|
|
25
|
-
def
|
26
|
-
const_get("
|
23
|
+
def coercion_klass(type)
|
24
|
+
Compel::Coercion.const_get("#{type}")
|
27
25
|
end
|
28
26
|
|
29
27
|
extend self
|
data/lib/compel/contract.rb
CHANGED
@@ -3,78 +3,34 @@ module Compel
|
|
3
3
|
class Contract
|
4
4
|
|
5
5
|
attr_reader :errors,
|
6
|
-
:conditions,
|
7
6
|
:serialized_errors
|
8
7
|
|
9
|
-
def initialize(
|
10
|
-
if
|
11
|
-
raise
|
8
|
+
def initialize(hash, schema)
|
9
|
+
if hash.nil? || !Coercion.valid?(hash, Hash)
|
10
|
+
raise Compel::TypeError, 'must be an Hash'
|
12
11
|
end
|
13
12
|
|
14
|
-
@
|
15
|
-
@
|
16
|
-
@
|
17
|
-
@coerced_params = Hashie::Mash.new
|
18
|
-
|
19
|
-
instance_eval(&block)
|
13
|
+
@hash = Hashie::Mash.new(hash)
|
14
|
+
@schema = schema
|
15
|
+
@coerced_hash = Hashie::Mash.new
|
20
16
|
end
|
21
17
|
|
22
18
|
def validate
|
23
|
-
@
|
24
|
-
begin
|
25
|
-
# If it is an Hash and it was given conditions for that Hash,
|
26
|
-
# build a new Compel::Contract form inner conditions
|
27
|
-
if (param.hash? && param.conditions?)
|
28
|
-
|
29
|
-
# If this param is required, a value must be given to build the Compel::Contract
|
30
|
-
# otherwise, only build it if is given a value for the param
|
31
|
-
if (param.required? && !param.value.nil?) || !param.value.nil?
|
32
|
-
contract = Contract.new(param.value, ¶m.conditions).validate
|
33
|
-
|
34
|
-
@errors.add(param.name, contract.errors)
|
35
|
-
|
36
|
-
# Update the param value with coerced values to use later
|
37
|
-
# when coercing param parent
|
38
|
-
@coerced_params[param.name] = contract.coerced_params
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
# All values must coerce before going through validation,
|
43
|
-
# raise exception to avoid validation
|
44
|
-
|
45
|
-
# If the param value has already been coerced from digging into child Hash
|
46
|
-
# use that value instead, so we don't lose the previous coerced values
|
47
|
-
coerced_value = Coercion.coerce! \
|
48
|
-
(@coerced_params[param.name].nil? ? param.value : @coerced_params[param.name]), param.type, param.options
|
49
|
-
|
50
|
-
# Only add to coerced values if not nil
|
51
|
-
if !coerced_value.nil?
|
52
|
-
@coerced_params[param.name] = coerced_value
|
53
|
-
end
|
54
|
-
|
55
|
-
@errors.add \
|
56
|
-
param.name, Validation.validate(param.value, param.options)
|
57
|
-
|
58
|
-
rescue Compel::ParamTypeError => exception
|
59
|
-
@errors.add(param.name, exception.message)
|
60
|
-
end
|
61
|
-
end
|
19
|
+
validator = Validators::HashValidator.new(@hash, @schema).validate
|
62
20
|
|
63
|
-
|
64
|
-
|
21
|
+
@errors = validator.errors
|
22
|
+
@coerced_hash = validator.output
|
65
23
|
|
66
|
-
|
67
|
-
@conditions[name] = \
|
68
|
-
Param.new(name, type, @params.delete(name), options, &block)
|
24
|
+
self
|
69
25
|
end
|
70
26
|
|
71
|
-
def
|
72
|
-
# @
|
73
|
-
@
|
27
|
+
def coerced_hash
|
28
|
+
# @hash has all params that are not affected by the validation
|
29
|
+
@hash.merge(@coerced_hash)
|
74
30
|
end
|
75
31
|
|
76
32
|
def serialize
|
77
|
-
|
33
|
+
coerced_hash.tap do |hash|
|
78
34
|
if !valid?
|
79
35
|
hash[:errors] = serialized_errors
|
80
36
|
end
|
@@ -91,14 +47,14 @@ module Compel
|
|
91
47
|
|
92
48
|
def raise?
|
93
49
|
if !valid?
|
94
|
-
exception =
|
95
|
-
exception.
|
50
|
+
exception = InvalidHashError.new
|
51
|
+
exception.object = coerced_hash
|
96
52
|
exception.errors = serialized_errors
|
97
53
|
|
98
|
-
raise exception, '
|
54
|
+
raise exception, 'hash has errors'
|
99
55
|
end
|
100
56
|
|
101
|
-
|
57
|
+
coerced_hash
|
102
58
|
end
|
103
59
|
|
104
60
|
end
|
data/lib/compel/validation.rb
CHANGED
@@ -2,63 +2,49 @@ module Compel
|
|
2
2
|
|
3
3
|
module Validation
|
4
4
|
|
5
|
-
def
|
6
|
-
validate(value, options).length == 0
|
7
|
-
end
|
8
|
-
|
9
|
-
def validate!(value, options)
|
10
|
-
errors = validate(value, options)
|
11
|
-
|
12
|
-
if errors.length > 0
|
13
|
-
raise ParamValidationError, errors[0]
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def validate(value, options)
|
5
|
+
def validate(value, type, options)
|
18
6
|
errors = []
|
19
7
|
|
20
8
|
options.each do |option, option_value|
|
21
9
|
# most of this code snippet is from sinatra-param gem
|
22
10
|
# https://github.com/mattt/sinatra-param
|
23
11
|
# by Mattt Thompson (@mattt)
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
when
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
12
|
+
case option.to_sym
|
13
|
+
when :required
|
14
|
+
errors << 'is required' if option_value && value.nil?
|
15
|
+
when :format
|
16
|
+
if type == Coercion::String && !value.nil?
|
17
|
+
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
|
+
end
|
21
|
+
when :is
|
22
|
+
errors << "must be #{option_value}" unless value === option_value
|
23
|
+
when :in, :within, :range
|
24
|
+
errors << "must be within #{option_value}" unless value.nil? || case option_value
|
25
|
+
when Range
|
26
|
+
option_value.include?(value)
|
27
|
+
else
|
28
|
+
Array(option_value).include?(value)
|
29
|
+
end
|
30
|
+
when :min
|
31
|
+
errors << "cannot be less than #{option_value}" unless value.nil? || option_value <= value
|
32
|
+
when :max
|
33
|
+
errors << "cannot be greater than #{option_value}" unless value.nil? || option_value >= value
|
34
|
+
when :length
|
35
|
+
errors << "cannot have length different than #{option_value}" unless value.nil? || option_value == "#{value}".length
|
36
|
+
when :min_length
|
37
|
+
unless value.kind_of?(String)
|
38
|
+
errors << 'must be a string if using the min_length validation'
|
39
|
+
else
|
40
|
+
errors << "cannot have length less than #{option_value}" unless value.nil? || option_value <= value.length
|
41
|
+
end
|
42
|
+
when :max_length
|
43
|
+
unless value.kind_of?(String)
|
44
|
+
errors << 'must be a string if using the max_length validation'
|
45
|
+
else
|
46
|
+
errors << "cannot have length greater than #{option_value}" unless value.nil? || option_value >= value.length
|
59
47
|
end
|
60
|
-
rescue ParamValidationError => exception
|
61
|
-
errors << exception.message
|
62
48
|
end
|
63
49
|
end
|
64
50
|
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# Validates values of an hash recursively
|
2
|
+
# output is an hash with coerced values
|
3
|
+
# errors is a Compel::Errors
|
4
|
+
|
5
|
+
# This is not beauty,
|
6
|
+
# but it's tested and working.
|
7
|
+
module Compel
|
8
|
+
module Validators
|
9
|
+
|
10
|
+
class HashValidator < Base
|
11
|
+
|
12
|
+
def initialize(input, schema)
|
13
|
+
super
|
14
|
+
@output = Hashie::Mash.new
|
15
|
+
@errors = Errors.new
|
16
|
+
|
17
|
+
@keys_schemas = schema.options[:keys]
|
18
|
+
end
|
19
|
+
|
20
|
+
def validate
|
21
|
+
@keys_schemas.keys.each do |param_name|
|
22
|
+
|
23
|
+
if (@input[param_name].is_a?(Hash))
|
24
|
+
hash_validator = \
|
25
|
+
HashValidator.new(@input[param_name], @keys_schemas[param_name])
|
26
|
+
.validate
|
27
|
+
|
28
|
+
@errors.add(param_name, hash_validator.errors)
|
29
|
+
@output[param_name] = hash_validator.output
|
30
|
+
end
|
31
|
+
|
32
|
+
type_validator = \
|
33
|
+
TypeValidator.new(@output[param_name].nil? ? @input[param_name] : @output[param_name], @keys_schemas[param_name])
|
34
|
+
.validate
|
35
|
+
|
36
|
+
if !type_validator.output.nil?
|
37
|
+
@output[param_name] = type_validator.output
|
38
|
+
end
|
39
|
+
|
40
|
+
if type_validator.errors
|
41
|
+
@errors.add(param_name, type_validator.errors)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# Validates a type, given an input, type and options
|
2
|
+
# output is a coerced value
|
3
|
+
# error is an array of strings
|
4
|
+
module Compel
|
5
|
+
module Validators
|
6
|
+
|
7
|
+
class TypeValidator < Base
|
8
|
+
|
9
|
+
def initialize(input, schema)
|
10
|
+
super
|
11
|
+
@output = nil
|
12
|
+
end
|
13
|
+
|
14
|
+
def validate
|
15
|
+
value = input.nil? ? schema.default_value : input
|
16
|
+
|
17
|
+
begin
|
18
|
+
@output = Coercion.coerce!(value, schema.type, schema.options)
|
19
|
+
@errors = Validation.validate(value, schema.type, schema.options)
|
20
|
+
rescue Compel::TypeError => exception
|
21
|
+
@errors = [exception.message]
|
22
|
+
end
|
23
|
+
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
data/lib/compel/version.rb
CHANGED
data/lib/compel.rb
CHANGED
@@ -12,12 +12,17 @@ require 'compel/coercion/datetime'
|
|
12
12
|
require 'compel/coercion/hash'
|
13
13
|
require 'compel/coercion/json'
|
14
14
|
require 'compel/coercion/boolean'
|
15
|
+
require 'compel/coercion/regexp'
|
15
16
|
|
16
|
-
require 'compel/
|
17
|
-
require 'compel/
|
18
|
-
require 'compel/
|
17
|
+
require 'compel/exceptions/invalid_hash_error'
|
18
|
+
require 'compel/exceptions/validation_error'
|
19
|
+
require 'compel/exceptions/type_error'
|
19
20
|
|
20
|
-
require 'compel/
|
21
|
+
require 'compel/validators/base'
|
22
|
+
require 'compel/validators/type_validator'
|
23
|
+
require 'compel/validators/hash_validator'
|
24
|
+
|
25
|
+
require 'compel/builder/methods'
|
21
26
|
require 'compel/contract'
|
22
27
|
require 'compel/coercion'
|
23
28
|
require 'compel/validation'
|
@@ -25,18 +30,18 @@ require 'compel/errors'
|
|
25
30
|
|
26
31
|
module Compel
|
27
32
|
|
28
|
-
|
33
|
+
extend Builder::Methods
|
29
34
|
|
30
|
-
def self.run!(params,
|
31
|
-
Contract.new(params,
|
35
|
+
def self.run!(params, schema)
|
36
|
+
Contract.new(params, schema).validate.raise?
|
32
37
|
end
|
33
38
|
|
34
|
-
def self.run?(params,
|
35
|
-
Contract.new(params,
|
39
|
+
def self.run?(params, schema)
|
40
|
+
Contract.new(params, schema).validate.valid?
|
36
41
|
end
|
37
42
|
|
38
|
-
def self.run(params,
|
39
|
-
Contract.new(params,
|
43
|
+
def self.run(params, schema)
|
44
|
+
Contract.new(params, schema).validate.serialize
|
40
45
|
end
|
41
46
|
|
42
47
|
end
|