compel 0.1.3 → 0.2.0
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/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
|