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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +88 -20
  3. data/lib/compel/builder/array.rb +31 -0
  4. data/lib/compel/builder/boolean.rb +7 -0
  5. data/lib/compel/builder/common.rb +2 -2
  6. data/lib/compel/builder/common_value.rb +4 -4
  7. data/lib/compel/builder/hash.rb +3 -1
  8. data/lib/compel/builder/methods.rb +5 -0
  9. data/lib/compel/builder/schema.rb +4 -0
  10. data/lib/compel/coercion/coercion.rb +47 -0
  11. data/lib/compel/coercion/nil_result.rb +13 -0
  12. data/lib/compel/coercion/result.rb +37 -0
  13. data/lib/compel/coercion/types/array.rb +15 -0
  14. data/lib/compel/coercion/{boolean.rb → types/boolean.rb} +1 -3
  15. data/lib/compel/coercion/types/date.rb +36 -0
  16. data/lib/compel/coercion/types/datetime.rb +36 -0
  17. data/lib/compel/coercion/{float.rb → types/float.rb} +2 -2
  18. data/lib/compel/coercion/{hash.rb → types/hash.rb} +2 -2
  19. data/lib/compel/coercion/{integer.rb → types/integer.rb} +2 -2
  20. data/lib/compel/coercion/{json.rb → types/json.rb} +2 -2
  21. data/lib/compel/coercion/{regexp.rb → types/regexp.rb} +2 -4
  22. data/lib/compel/coercion/{string.rb → types/string.rb} +3 -5
  23. data/lib/compel/coercion/types/time.rb +36 -0
  24. data/lib/compel/coercion/types/type.rb +29 -0
  25. data/lib/compel/contract.rb +14 -42
  26. data/lib/compel/errors.rb +13 -17
  27. data/lib/compel/exceptions/invalid_object_error.rb +9 -0
  28. data/lib/compel/result.rb +30 -0
  29. data/lib/compel/validation.rb +6 -4
  30. data/lib/compel/validators/array_validator.rb +107 -0
  31. data/lib/compel/validators/base.rb +11 -1
  32. data/lib/compel/validators/hash_validator.rb +77 -19
  33. data/lib/compel/validators/type_validator.rb +42 -11
  34. data/lib/compel/version.rb +1 -1
  35. data/lib/compel.rb +6 -15
  36. data/spec/compel/builder_spec.rb +354 -144
  37. data/spec/compel/coercion_spec.rb +16 -1
  38. data/spec/compel/compel_spec.rb +315 -302
  39. data/spec/compel/validation_spec.rb +97 -115
  40. data/spec/support/sinatra_app.rb +2 -2
  41. metadata +21 -15
  42. data/lib/compel/coercion/date.rb +0 -30
  43. data/lib/compel/coercion/datetime.rb +0 -30
  44. data/lib/compel/coercion/time.rb +0 -30
  45. data/lib/compel/coercion/type.rb +0 -17
  46. data/lib/compel/coercion.rb +0 -31
  47. data/lib/compel/exceptions/invalid_hash_error.rb +0 -9
@@ -2,59 +2,31 @@ module Compel
2
2
 
3
3
  class Contract
4
4
 
5
- attr_reader :errors,
6
- :serialized_errors
5
+ attr_reader :object, :schema
7
6
 
8
- def initialize(hash, schema)
9
- if hash.nil? || !Coercion.valid?(hash, Hash)
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
- validator = Validators::HashValidator.new(@hash, @schema).validate
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
- def serialize
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 serialized_errors
45
- @errors.to_hash
18
+ def setup!
19
+ validator_klass.new(object, schema)
46
20
  end
47
21
 
48
- def raise?
49
- if !valid?
50
- exception = InvalidHashError.new
51
- exception.object = coerced_hash
52
- exception.errors = serialized_errors
53
-
54
- raise exception, 'hash has errors'
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.is_a?(Compel::Errors)
11
- if error.empty?
12
- return
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
- return
22
- end
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
- if @errors[key].nil?
33
- @errors[key] = []
29
+ @errors[key].concat(error)
34
30
  end
35
31
 
36
- @errors[key].concat(error)
32
+ @errors
37
33
  end
38
34
 
39
35
  def length
@@ -0,0 +1,9 @@
1
+ module Compel
2
+
3
+ class InvalidObjectError < StandardError
4
+
5
+ attr_accessor :object
6
+
7
+ end
8
+
9
+ end
@@ -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
@@ -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
- @keys_schemas.keys.each do |param_name|
19
+ unless root_hash_valid?
20
+ return self
21
+ end
22
22
 
23
- if (@input[param_name].is_a?(Hash))
24
- hash_validator = \
25
- HashValidator.new(@input[param_name], @keys_schemas[param_name])
26
- .validate
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
- @errors.add(param_name, hash_validator.errors)
29
- @output[param_name] = hash_validator.output
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
- type_validator = \
33
- TypeValidator.new(@output[param_name].nil? ? @input[param_name] : @output[param_name], @keys_schemas[param_name])
34
- .validate
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
- if !type_validator.output.nil?
37
- @output[param_name] = type_validator.output
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
- if type_validator.errors
41
- @errors.add(param_name, type_validator.errors)
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
- value = input.nil? ? schema.default_value : input
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
- 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]
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
@@ -1,3 +1,3 @@
1
1
  module Compel
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.1'
3
3
  end
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/coercion/type'
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.serialize
35
+ Contract.new(params, schema).validate
45
36
  end
46
37
 
47
38
  end