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