compel 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +7 -0
  3. data/README.md +65 -54
  4. data/lib/compel/builder/boolean.rb +13 -0
  5. data/lib/compel/builder/common.rb +24 -0
  6. data/lib/compel/builder/common_value.rb +34 -0
  7. data/lib/compel/builder/date.rb +23 -0
  8. data/lib/compel/builder/datetime.rb +23 -0
  9. data/lib/compel/builder/float.rb +15 -0
  10. data/lib/compel/builder/hash.rb +22 -0
  11. data/lib/compel/builder/integer.rb +15 -0
  12. data/lib/compel/builder/json.rb +13 -0
  13. data/lib/compel/builder/methods.rb +60 -0
  14. data/lib/compel/builder/schema.rb +27 -0
  15. data/lib/compel/builder/string.rb +30 -0
  16. data/lib/compel/builder/time.rb +23 -0
  17. data/lib/compel/coercion/boolean.rb +1 -1
  18. data/lib/compel/coercion/date.rb +19 -2
  19. data/lib/compel/coercion/datetime.rb +19 -2
  20. data/lib/compel/coercion/float.rb +1 -1
  21. data/lib/compel/coercion/hash.rb +1 -1
  22. data/lib/compel/coercion/integer.rb +1 -1
  23. data/lib/compel/coercion/json.rb +1 -1
  24. data/lib/compel/coercion/regexp.rb +17 -0
  25. data/lib/compel/coercion/string.rb +1 -1
  26. data/lib/compel/coercion/time.rb +19 -2
  27. data/lib/compel/coercion/type.rb +0 -13
  28. data/lib/compel/coercion.rb +8 -10
  29. data/lib/compel/contract.rb +18 -62
  30. data/lib/compel/exceptions/invalid_hash_error.rb +9 -0
  31. data/lib/compel/exceptions/type_error.rb +7 -0
  32. data/lib/compel/exceptions/validation_error.rb +7 -0
  33. data/lib/compel/validation.rb +36 -50
  34. data/lib/compel/validators/base.rb +19 -0
  35. data/lib/compel/validators/hash_validator.rb +51 -0
  36. data/lib/compel/validators/type_validator.rb +30 -0
  37. data/lib/compel/version.rb +1 -1
  38. data/lib/compel.rb +16 -11
  39. data/spec/compel/builder_spec.rb +226 -0
  40. data/spec/compel/coercion_spec.rb +85 -18
  41. data/spec/compel/compel_spec.rb +368 -160
  42. data/spec/compel/sinatra_integration_spec.rb +73 -0
  43. data/spec/compel/validation_spec.rb +122 -8
  44. data/spec/spec_helper.rb +19 -0
  45. data/spec/support/sinatra_app.rb +43 -0
  46. metadata +28 -10
  47. data/lib/compel/invalid_params_error.rb +0 -9
  48. data/lib/compel/param.rb +0 -46
  49. data/lib/compel/param_type_error.rb +0 -7
  50. data/lib/compel/param_validation_error.rb +0 -7
  51. data/spec/compel/contract_spec.rb +0 -36
  52. data/spec/compel/param_spec.rb +0 -25
@@ -3,7 +3,7 @@ module Compel
3
3
 
4
4
  class String < Type
5
5
 
6
- def coerce
6
+ def coerce!
7
7
  if !value.is_a?(::String)
8
8
  fail
9
9
  end
@@ -3,8 +3,25 @@ module Compel
3
3
 
4
4
  class Time < Type
5
5
 
6
- def coerce
7
- ::Time.parse(value)
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
@@ -11,19 +11,6 @@ module Compel
11
11
  @options = options
12
12
  end
13
13
 
14
- def parse
15
- @value = value
16
- end
17
-
18
- def serialize
19
- raise '#serialize? should be implemented'
20
- end
21
-
22
- def coerce!
23
- parse
24
- coerce
25
- end
26
-
27
14
  end
28
15
 
29
16
  end
@@ -10,20 +10,18 @@ module Compel
10
10
  return nil if value.nil?
11
11
 
12
12
  begin
13
- klass = compel_type?(type) ? type : get_compel_type_klass(type)
14
-
15
- return klass.new(value, options).coerce!
13
+ coercion_klass(type).new(value, options).coerce!
14
+ rescue Compel::TypeError => exception
15
+ raise exception
16
16
  rescue
17
- raise ParamTypeError, "'#{value}' is not a valid #{type}"
18
- end
19
- end
17
+ type_split = "#{type}".split('::')
20
18
 
21
- def compel_type?(type)
22
- type.to_s.split('::')[0..1].join('::') == 'Compel::Coercion'
19
+ raise Compel::TypeError, "'#{value}' is not a valid #{type_split[-1]}"
20
+ end
23
21
  end
24
22
 
25
- def get_compel_type_klass(type)
26
- const_get("Compel::Coercion::#{type}")
23
+ def coercion_klass(type)
24
+ Compel::Coercion.const_get("#{type}")
27
25
  end
28
26
 
29
27
  extend self
@@ -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(params, &block)
10
- if params.nil? || !Coercion.valid?(params, Hash)
11
- raise ParamTypeError, 'params must be an Hash'
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
- @errors = Errors.new
15
- @params = Hashie::Mash.new(params)
16
- @conditions = Hashie::Mash.new
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
- @conditions.values.each do |param|
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, &param.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
- self
64
- end
21
+ @errors = validator.errors
22
+ @coerced_hash = validator.output
65
23
 
66
- def param(name, type, options = {}, &block)
67
- @conditions[name] = \
68
- Param.new(name, type, @params.delete(name), options, &block)
24
+ self
69
25
  end
70
26
 
71
- def coerced_params
72
- # @params has all params that are not affected by the validation
73
- @params.merge(@coerced_params)
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
- coerced_params.tap do |hash|
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 = InvalidParamsError.new
95
- exception.params = coerced_params
50
+ exception = InvalidHashError.new
51
+ exception.object = coerced_hash
96
52
  exception.errors = serialized_errors
97
53
 
98
- raise exception, 'params are invalid'
54
+ raise exception, 'hash has errors'
99
55
  end
100
56
 
101
- coerced_params
57
+ coerced_hash
102
58
  end
103
59
 
104
60
  end
@@ -0,0 +1,9 @@
1
+ module Compel
2
+
3
+ class InvalidHashError < StandardError
4
+
5
+ attr_accessor :object, :errors
6
+
7
+ end
8
+
9
+ end
@@ -0,0 +1,7 @@
1
+ module Compel
2
+
3
+ class TypeError < StandardError
4
+
5
+ end
6
+
7
+ end
@@ -0,0 +1,7 @@
1
+ module Compel
2
+
3
+ class ValidatorError < StandardError
4
+
5
+ end
6
+
7
+ end
@@ -2,63 +2,49 @@ module Compel
2
2
 
3
3
  module Validation
4
4
 
5
- def valid?(value, options)
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
- begin
25
- case option.to_sym
26
- when :required
27
- raise ParamValidationError, 'is required' if option_value && value.nil?
28
- when :blank
29
- raise ParamValidationError, 'cannot be blank' if !option_value && case value
30
- when String
31
- !(/\S/ === value)
32
- when Array, Hash
33
- value.empty?
34
- else
35
- value.nil?
36
- end
37
- when :format
38
- raise ParamValidationError, 'must be a string if using the format validation' unless value.kind_of?(String)
39
- raise ParamValidationError, "must match format #{option_value}" unless value =~ option_value
40
- when :is
41
- raise ParamValidationError, "must be #{option_value}" unless value === option_value
42
- when :in, :within, :range
43
- raise ParamValidationError, "must be within #{option_value}" unless value.nil? || case option_value
44
- when Range
45
- option_value.include?(value)
46
- else
47
- Array(option_value).include?(value)
48
- end
49
- when :min
50
- raise ParamValidationError, "cannot be less than #{option_value}" unless value.nil? || option_value <= value
51
- when :max
52
- raise ParamValidationError, "cannot be greater than #{option_value}" unless value.nil? || option_value >= value
53
- when :length
54
- raise ParamValidationError, "cannot have length different than #{option_value}" unless value.nil? || option_value == "#{value}".length
55
- when :min_length
56
- raise ParamValidationError, "cannot have length less than #{option_value}" unless value.nil? || option_value <= value.length
57
- when :max_length
58
- raise ParamValidationError, "cannot have length greater than #{option_value}" unless value.nil? || option_value >= value.length
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,19 @@
1
+ module Compel
2
+ module Validators
3
+
4
+ class Base
5
+
6
+ attr_reader :input,
7
+ :output,
8
+ :errors,
9
+ :schema
10
+
11
+ def initialize(input, schema)
12
+ @input = input
13
+ @schema = schema
14
+ end
15
+
16
+ end
17
+
18
+ end
19
+ end
@@ -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
@@ -1,3 +1,3 @@
1
1
  module Compel
2
- VERSION = '0.1.3'
2
+ VERSION = '0.2.0'
3
3
  end
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/invalid_params_error'
17
- require 'compel/param_validation_error'
18
- require 'compel/param_type_error'
17
+ require 'compel/exceptions/invalid_hash_error'
18
+ require 'compel/exceptions/validation_error'
19
+ require 'compel/exceptions/type_error'
19
20
 
20
- require 'compel/param'
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
- Boolean = Coercion::Boolean
33
+ extend Builder::Methods
29
34
 
30
- def self.run!(params, &block)
31
- Contract.new(params, &block).validate.raise?
35
+ def self.run!(params, schema)
36
+ Contract.new(params, schema).validate.raise?
32
37
  end
33
38
 
34
- def self.run?(params, &block)
35
- Contract.new(params, &block).validate.valid?
39
+ def self.run?(params, schema)
40
+ Contract.new(params, schema).validate.valid?
36
41
  end
37
42
 
38
- def self.run(params, &block)
39
- Contract.new(params, &block).validate.serialize
43
+ def self.run(params, schema)
44
+ Contract.new(params, schema).validate.serialize
40
45
  end
41
46
 
42
47
  end