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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4ca24b865ca3552e7fb66b3b5fa3cb54522c69f6
4
- data.tar.gz: 06342b5d60518ad7f05fc4fcd380ad6dce9b1017
3
+ metadata.gz: 258250ae836758b145cc384b581c628a696260b8
4
+ data.tar.gz: 1298e32dee4358430f051fcb378785e487b4123a
5
5
  SHA512:
6
- metadata.gz: eadd3a9cf227017eb8ed57431ba6b6b687ef2e76cee95de5dc89c9691201ff8cb2ce6afb96e7061314f07b2bc3fda5d0660bb3345e2784086f9e28d31ad90ad8
7
- data.tar.gz: d2adaa09f03406c98f87bdc0b816b30a3983a65bf306bab7ef8fc7cd6873302d024173d3cf3ca3122c0595d903962fa154a763e66e31c8f5e7c434927e214b08
6
+ metadata.gz: 69e0028f7dc235d8a8cf5e8a2bd3af74c59c11c7c37cf933a041447360444774e0590d38bad735ee8cf8463f602f48d38981394c7f109519ef604effc0da9d60
7
+ data.tar.gz: a580c0ff252daecc215142f323ca35f4d874cd62893da793ff5175f7f4c1d1491ba1272608043681dbed2ee87c9db17077407718c78b7556ba076ef5223404c9
data/README.md CHANGED
@@ -2,7 +2,7 @@ Compel
2
2
  ==========================
3
3
  ![](https://travis-ci.org/joaquimadraz/compel.svg)
4
4
  [![Code Climate](https://codeclimate.com/github/joaquimadraz/compel/badges/gpa.svg)](https://codeclimate.com/github/joaquimadraz/compel)
5
- [![Test Coverage](https://codeclimate.com/github/joaquimadraz/compel/badges/coverage.svg)](https://codeclimate.com/github/joaquimadraz/compel)
5
+ [![Test Coverage](https://codeclimate.com/github/joaquimadraz/compel/badges/coverage.svg)](https://codeclimate.com/github/joaquimadraz/compel/coverage)
6
6
 
7
7
  Ruby Hash Coercion and Validation
8
8
 
@@ -12,7 +12,7 @@ The motivation was to create an integration for [RestMyCase](https://github.com/
12
12
 
13
13
  Based on the same principle from [Grape](https://github.com/ruby-grape/grape) framework and [sinatra-param](https://github.com/mattt/sinatra-param) gem to validate request params. The schema builder is based on [Joi](https://github.com/hapijs/joi).
14
14
 
15
- ### Example
15
+ ### Usage
16
16
 
17
17
  ```ruby
18
18
  object = {
@@ -67,23 +67,91 @@ There are 3 ways to run validations:
67
67
  Method | Behaviour
68
68
  ------------- | -------------
69
69
  `#run` | Validates and returns an Hash with coerced params plus a `:errors` key with a _Rails like_ Hash of errors if any.
70
- `#run!` | Validates and raises `Compel::InvalidHashError` exception with the coerced params and errors.
70
+ `#run!` | Validates and raises `Compel::InvalidObjectError` exception with the coerced params and errors.
71
71
  `#run?` | Validates and returns true or false.
72
+ `schema#validate` | Chech below
72
73
 
73
- ### Types
74
-
75
- - `#integer`
76
- - `#float`
77
- - `#string`
78
- - `#json`
79
- - ex: `"{\"a\":1,\"b\":2,\"c\":3}"`
80
- - `#hash`
81
- - ex: `{ a: 1, b: 2, c: 3 }`
82
- - `#date`
83
- - `#time`
84
- - `#datetime`
85
- - `#boolean`,
86
- - ex: `1`/`0`, `true`/`false`, `t`/`f`, `yes`/`no`, `y`/`n`
74
+ ==========================
75
+
76
+ ### Schema Builder API
77
+ - `Compel#array` *
78
+ - `is(``array``)`
79
+ - `#items(``schema``)`
80
+ ```ruby
81
+ * ex: [1, 2, 3], [{ a: 1, b: 2}, { a: 3, b: 4 }]
82
+ ```
83
+
84
+ - `Compel#hash` *
85
+ - `#keys(``schema_hash``)`
86
+ ```ruby
87
+ * ex: { a: 1, b: 2, c: 3 }
88
+ ```
89
+
90
+ - `Compel.date` *
91
+ - `#format(``ruby_date_format``)`
92
+ - `iso8601`, set format to: `%Y-%m-%d`
93
+
94
+ - `Compel.datetime & Compel.time` *
95
+ - `#format(``ruby_date_format``)`
96
+ - `#iso8601`, set format to: `%FT%T`
97
+
98
+ - `Compel#string` **
99
+ - `#format(``regexp``)`
100
+ - `#min_length(``integer``)`
101
+ - `#max_length(``integer``)`
102
+
103
+ - `Compel#json` *
104
+ ```ruby
105
+ * ex: "{\"a\":1,\"b\":2,\"c\":3}"
106
+ ```
107
+
108
+ - `Compel#boolean` *
109
+ ```ruby
110
+ * ex: 1/0, true/false, 't'/'f', 'yes'/'no', 'y'/'n'
111
+ ```
112
+
113
+ - `Compel#integer` **
114
+
115
+ - `Compel#float` **
116
+
117
+ (\*) **Common methods**
118
+ - `required`
119
+ - `default(``value``)`
120
+
121
+ (\*\*) **Common value methods**
122
+ - `is(``value``)`
123
+ - `in(``array``)`
124
+ - `length(``integer``)`
125
+ - `min(``value``)`
126
+ - `max(``value``)`
127
+
128
+
129
+ ### Schema Validate
130
+
131
+ For straight forward validations, you can call `#validate` on schema and it will return a `Compel::Result` object.
132
+
133
+ ```ruby
134
+ result = Compel.string
135
+ .format(/^\d{4}-\d{3}$/)
136
+ .required
137
+ .validate('1234')
138
+
139
+ puts result.errors
140
+ # => ["must match format ^\\d{4}-\\d{3}$"]
141
+ ```
142
+
143
+ #### Compel Result
144
+
145
+ Simple object that encapsulates a validation result.
146
+
147
+ Method | Behaviour
148
+ ------------- | -------------
149
+ `#value` | the coerced value or the input value is invalid
150
+ `#errors` | array of errors is any.
151
+ `#valid?` | `true` or `false`
152
+ `#raise?` | raises a `Compel::InvalidObjectError` if invalid, otherwise returns `#value`
153
+
154
+ ==========================
87
155
 
88
156
  ### Sinatra Integration
89
157
 
@@ -107,9 +175,9 @@ class App < Sinatra::Base
107
175
 
108
176
  end
109
177
 
110
- error Compel::InvalidHashError do |exception|
178
+ error Compel::InvalidObjectError do |exception|
111
179
  status 400
112
- { errors: exception.errors }.to_json
180
+ { errors: exception.object[:errors] }.to_json
113
181
  end
114
182
 
115
183
  configure :development do
@@ -143,7 +211,7 @@ And then execute:
143
211
 
144
212
  ### TODO
145
213
 
146
- - Write more Documentation (check specs for now ;)
214
+ - Write Advanced Documentation (check specs for now ;)
147
215
  - Rails integration
148
216
  - [RestMyCase](https://github.com/goncalvesjoao/rest_my_case) integration
149
217
 
@@ -0,0 +1,31 @@
1
+ module Compel
2
+ module Builder
3
+
4
+ class Array < Schema
5
+
6
+ def initialize
7
+ super(Coercion::Array)
8
+ end
9
+
10
+ def items(schema)
11
+ if !schema.is_a?(Schema)
12
+ raise Compel::TypeError, '#items must be a valid Schema'
13
+ end
14
+
15
+ options[:items] = schema
16
+ self
17
+ end
18
+
19
+ def is(value)
20
+ options[:is] = Coercion.coerce!(value, ::Array)
21
+ self
22
+ end
23
+
24
+ def validate(object)
25
+ Contract.new(object, self).validate
26
+ end
27
+
28
+ end
29
+
30
+ end
31
+ end
@@ -7,6 +7,13 @@ module Compel
7
7
  super(Coercion::Boolean)
8
8
  end
9
9
 
10
+ def is(value)
11
+ Coercion.coerce!(value, Coercion::Boolean)
12
+ options[:is] = value
13
+
14
+ self
15
+ end
16
+
10
17
  end
11
18
 
12
19
  end
@@ -3,8 +3,8 @@ module Compel
3
3
 
4
4
  module Common
5
5
 
6
- def is(value)
7
- options[:is] = value
6
+ def length(value)
7
+ options[:length] = Coercion.coerce!(value, ::Integer)
8
8
  self
9
9
  end
10
10
 
@@ -3,13 +3,13 @@ module Compel
3
3
 
4
4
  module CommonValue
5
5
 
6
- def in(value)
7
- options[:in] = value
6
+ def is(value)
7
+ options[:is] = value
8
8
  self
9
9
  end
10
10
 
11
- def length(value)
12
- options[:length] = Coercion.coerce!(value, ::Integer)
11
+ def in(value)
12
+ options[:in] = value
13
13
  self
14
14
  end
15
15
 
@@ -5,6 +5,8 @@ module Compel
5
5
 
6
6
  def initialize
7
7
  super(Coercion::Hash)
8
+
9
+ options[:keys] = {}
8
10
  end
9
11
 
10
12
  def keys(hash)
@@ -13,7 +15,7 @@ module Compel
13
15
  end
14
16
 
15
17
  def validate(object)
16
- Contract.new(object, self).validate.serialize
18
+ Contract.new(object, self).validate
17
19
  end
18
20
 
19
21
  end
@@ -10,6 +10,7 @@ require 'compel/builder/datetime'
10
10
  require 'compel/builder/time'
11
11
  require 'compel/builder/date'
12
12
  require 'compel/builder/boolean'
13
+ require 'compel/builder/array'
13
14
 
14
15
  module Compel
15
16
  module Builder
@@ -52,6 +53,10 @@ module Compel
52
53
  Builder::Boolean.new
53
54
  end
54
55
 
56
+ def array
57
+ Builder::Array.new
58
+ end
59
+
55
60
  extend self
56
61
 
57
62
  end
@@ -21,6 +21,10 @@ module Compel
21
21
  options[:default]
22
22
  end
23
23
 
24
+ def validate(object)
25
+ Contract.new(object, self).validate
26
+ end
27
+
24
28
  end
25
29
 
26
30
  end
@@ -0,0 +1,47 @@
1
+ require 'compel/coercion/types/type'
2
+ require 'compel/coercion/types/integer'
3
+ require 'compel/coercion/types/float'
4
+ require 'compel/coercion/types/string'
5
+ require 'compel/coercion/types/date'
6
+ require 'compel/coercion/types/time'
7
+ require 'compel/coercion/types/datetime'
8
+ require 'compel/coercion/types/hash'
9
+ require 'compel/coercion/types/json'
10
+ require 'compel/coercion/types/boolean'
11
+ require 'compel/coercion/types/regexp'
12
+ require 'compel/coercion/types/array'
13
+
14
+ require 'compel/coercion/result'
15
+ require 'compel/coercion/nil_result'
16
+
17
+ module Compel
18
+
19
+ module Coercion
20
+
21
+ def valid?(value, type, options = {})
22
+ coerce(value, type, options).valid?
23
+ end
24
+
25
+ def coerce!(value, type, options = {})
26
+ result = coerce(value, type, options)
27
+
28
+ unless result.valid?
29
+ raise Compel::TypeError, result.error
30
+ end
31
+
32
+ result.coerced
33
+ end
34
+
35
+ def coerce(value, type, options = {})
36
+ return NilResult.new if value.nil?
37
+
38
+ coercion_klass = Compel::Coercion.const_get("#{type}")
39
+
40
+ coercion_klass.new(value, options).coerce
41
+ end
42
+
43
+ extend self
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,13 @@
1
+ module Compel
2
+ module Coercion
3
+
4
+ class NilResult < Result
5
+
6
+ def initialize
7
+ super(nil, nil, nil)
8
+ end
9
+
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,37 @@
1
+ module Compel
2
+ module Coercion
3
+
4
+ class Result
5
+
6
+ attr_reader :coerced,
7
+ :value,
8
+ :klass,
9
+ :error
10
+
11
+ def initialize(coerced, value, klass, coercion_error = nil)
12
+ @coerced = coerced
13
+ @value = value
14
+ @klass = klass
15
+ @error = coercion_error.nil? ? standard_error : coercion_error
16
+ end
17
+
18
+ def valid?
19
+ @error.nil?
20
+ end
21
+
22
+ private
23
+
24
+ def standard_error
25
+ if !klass.nil? && coerced.nil?
26
+ "'#{value}' is not a valid #{klass_final_type}"
27
+ end
28
+ end
29
+
30
+ def klass_final_type
31
+ "#{klass}".split('::')[-1]
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+ end
@@ -0,0 +1,15 @@
1
+ module Compel
2
+ module Coercion
3
+
4
+ class Array < Type
5
+
6
+ def coerce_value
7
+ if value.is_a?(::Array)
8
+ value
9
+ end
10
+ end
11
+
12
+ end
13
+
14
+ end
15
+ end
@@ -3,7 +3,7 @@ module Compel
3
3
 
4
4
  class Boolean < Type
5
5
 
6
- def coerce!
6
+ def coerce_value
7
7
  if /(false|f|no|n|0)$/i === "#{value}"
8
8
  return false
9
9
  end
@@ -11,8 +11,6 @@ module Compel
11
11
  if /(true|t|yes|y|1)$/i === "#{value}"
12
12
  return true
13
13
  end
14
-
15
- fail
16
14
  end
17
15
 
18
16
  end
@@ -0,0 +1,36 @@
1
+ module Compel
2
+ module Coercion
3
+
4
+ class Date < Type
5
+
6
+ attr_reader :format
7
+
8
+ def coerce_value
9
+ @format = options[:format] || '%Y-%m-%d'
10
+
11
+ if value.is_a?(::Date)
12
+ @value = value.strftime(format)
13
+ end
14
+
15
+ coerced = ::Date.strptime(value, format)
16
+
17
+ if coerced.strftime(format) == value
18
+ return coerced
19
+ end
20
+
21
+ build_error_result
22
+
23
+ rescue
24
+ build_error_result
25
+ end
26
+
27
+ def build_error_result
28
+ custom_error = "'#{value}' is not a parsable date with format: #{format}"
29
+
30
+ Result.new(nil, value, self.class, custom_error)
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,36 @@
1
+ module Compel
2
+ module Coercion
3
+
4
+ class DateTime < Type
5
+
6
+ attr_reader :format
7
+
8
+ def coerce_value
9
+ @format = options[:format] || '%FT%T'
10
+
11
+ if value.is_a?(::DateTime)
12
+ @value = value.strftime(format)
13
+ end
14
+
15
+ coerced = ::DateTime.strptime(value, format)
16
+
17
+ if coerced.strftime(format) == value
18
+ return coerced
19
+ end
20
+
21
+ build_error_result
22
+
23
+ rescue
24
+ build_error_result
25
+ end
26
+
27
+ def build_error_result
28
+ custom_error = "'#{value}' is not a parsable datetime with format: #{format}"
29
+
30
+ Result.new(nil, value, self.class, custom_error)
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end
@@ -3,8 +3,8 @@ module Compel
3
3
 
4
4
  class Float < Type
5
5
 
6
- def coerce!
7
- Float(value)
6
+ def coerce_value
7
+ Float(value) rescue nil
8
8
  end
9
9
 
10
10
  end
@@ -3,8 +3,8 @@ module Compel
3
3
 
4
4
  class Hash < Type
5
5
 
6
- def coerce!
7
- Hashie::Mash.new(value).to_hash
6
+ def coerce_value
7
+ Hashie::Mash.new(value).to_hash rescue nil
8
8
  end
9
9
 
10
10
  end
@@ -3,8 +3,8 @@ module Compel
3
3
 
4
4
  class Integer < Type
5
5
 
6
- def coerce!
7
- Integer(value)
6
+ def coerce_value
7
+ Integer(value) rescue nil
8
8
  end
9
9
 
10
10
  end
@@ -3,8 +3,8 @@ module Compel
3
3
 
4
4
  class JSON < Type
5
5
 
6
- def coerce!
7
- ::JSON.parse(value)
6
+ def coerce_value
7
+ ::JSON.parse(value) rescue nil
8
8
  end
9
9
 
10
10
  end
@@ -3,12 +3,10 @@ module Compel
3
3
 
4
4
  class Regexp < Type
5
5
 
6
- def coerce!
6
+ def coerce_value
7
7
  if value.is_a?(::Regexp)
8
- return value
8
+ value
9
9
  end
10
-
11
- fail
12
10
  end
13
11
 
14
12
  end
@@ -3,12 +3,10 @@ module Compel
3
3
 
4
4
  class String < Type
5
5
 
6
- def coerce!
7
- if !value.is_a?(::String)
8
- fail
6
+ def coerce_value
7
+ if value.is_a?(::String)
8
+ value
9
9
  end
10
-
11
- value
12
10
  end
13
11
 
14
12
  end
@@ -0,0 +1,36 @@
1
+ module Compel
2
+ module Coercion
3
+
4
+ class Time < Type
5
+
6
+ attr_reader :format
7
+
8
+ def coerce_value
9
+ @format = options[:format] || '%FT%T'
10
+
11
+ if value.is_a?(::Time)
12
+ @value = value.strftime(format)
13
+ end
14
+
15
+ coerced = ::Time.strptime(value, format)
16
+
17
+ if coerced.strftime(format) == value
18
+ return coerced
19
+ end
20
+
21
+ build_error_result
22
+
23
+ rescue
24
+ build_error_result
25
+ end
26
+
27
+ def build_error_result
28
+ custom_error = "'#{value}' is not a parsable time with format: #{format}"
29
+
30
+ Result.new(nil, value, self.class, custom_error)
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,29 @@
1
+ module Compel
2
+ module Coercion
3
+
4
+ class Type
5
+
6
+ attr_accessor :value,
7
+ :options
8
+
9
+ def initialize(value, options = {})
10
+ @value = value
11
+ @options = options
12
+ end
13
+
14
+ def coerce
15
+ result = coerce_value
16
+
17
+ # There are some special cases that
18
+ # we need to build a custom error
19
+ if result.is_a?(Result)
20
+ return result
21
+ end
22
+
23
+ Result.new(result, value, self.class)
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+ end