compel 0.2.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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