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.
- checksums.yaml +4 -4
- data/README.md +88 -20
- data/lib/compel/builder/array.rb +31 -0
- data/lib/compel/builder/boolean.rb +7 -0
- data/lib/compel/builder/common.rb +2 -2
- data/lib/compel/builder/common_value.rb +4 -4
- data/lib/compel/builder/hash.rb +3 -1
- data/lib/compel/builder/methods.rb +5 -0
- data/lib/compel/builder/schema.rb +4 -0
- data/lib/compel/coercion/coercion.rb +47 -0
- data/lib/compel/coercion/nil_result.rb +13 -0
- data/lib/compel/coercion/result.rb +37 -0
- data/lib/compel/coercion/types/array.rb +15 -0
- data/lib/compel/coercion/{boolean.rb → types/boolean.rb} +1 -3
- data/lib/compel/coercion/types/date.rb +36 -0
- data/lib/compel/coercion/types/datetime.rb +36 -0
- data/lib/compel/coercion/{float.rb → types/float.rb} +2 -2
- data/lib/compel/coercion/{hash.rb → types/hash.rb} +2 -2
- data/lib/compel/coercion/{integer.rb → types/integer.rb} +2 -2
- data/lib/compel/coercion/{json.rb → types/json.rb} +2 -2
- data/lib/compel/coercion/{regexp.rb → types/regexp.rb} +2 -4
- data/lib/compel/coercion/{string.rb → types/string.rb} +3 -5
- data/lib/compel/coercion/types/time.rb +36 -0
- data/lib/compel/coercion/types/type.rb +29 -0
- data/lib/compel/contract.rb +14 -42
- data/lib/compel/errors.rb +13 -17
- data/lib/compel/exceptions/invalid_object_error.rb +9 -0
- data/lib/compel/result.rb +30 -0
- data/lib/compel/validation.rb +6 -4
- data/lib/compel/validators/array_validator.rb +107 -0
- data/lib/compel/validators/base.rb +11 -1
- data/lib/compel/validators/hash_validator.rb +77 -19
- data/lib/compel/validators/type_validator.rb +42 -11
- data/lib/compel/version.rb +1 -1
- data/lib/compel.rb +6 -15
- data/spec/compel/builder_spec.rb +354 -144
- data/spec/compel/coercion_spec.rb +16 -1
- data/spec/compel/compel_spec.rb +315 -302
- data/spec/compel/validation_spec.rb +97 -115
- data/spec/support/sinatra_app.rb +2 -2
- metadata +21 -15
- data/lib/compel/coercion/date.rb +0 -30
- data/lib/compel/coercion/datetime.rb +0 -30
- data/lib/compel/coercion/time.rb +0 -30
- data/lib/compel/coercion/type.rb +0 -17
- data/lib/compel/coercion.rb +0 -31
- 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:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 258250ae836758b145cc384b581c628a696260b8
|
4
|
+
data.tar.gz: 1298e32dee4358430f051fcb378785e487b4123a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 69e0028f7dc235d8a8cf5e8a2bd3af74c59c11c7c37cf933a041447360444774e0590d38bad735ee8cf8463f602f48d38981394c7f109519ef604effc0da9d60
|
7
|
+
data.tar.gz: a580c0ff252daecc215142f323ca35f4d874cd62893da793ff5175f7f4c1d1491ba1272608043681dbed2ee87c9db17077407718c78b7556ba076ef5223404c9
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@ Compel
|
|
2
2
|
==========================
|
3
3
|

|
4
4
|
[](https://codeclimate.com/github/joaquimadraz/compel)
|
5
|
-
[](https://codeclimate.com/github/joaquimadraz/compel)
|
5
|
+
[](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
|
-
###
|
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::
|
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
|
-
|
74
|
-
|
75
|
-
|
76
|
-
-
|
77
|
-
-
|
78
|
-
- `#
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
-
|
84
|
-
- `#
|
85
|
-
|
86
|
-
|
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::
|
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
|
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
|
@@ -3,13 +3,13 @@ module Compel
|
|
3
3
|
|
4
4
|
module CommonValue
|
5
5
|
|
6
|
-
def
|
7
|
-
options[:
|
6
|
+
def is(value)
|
7
|
+
options[:is] = value
|
8
8
|
self
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
12
|
-
options[:
|
11
|
+
def in(value)
|
12
|
+
options[:in] = value
|
13
13
|
self
|
14
14
|
end
|
15
15
|
|
data/lib/compel/builder/hash.rb
CHANGED
@@ -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
|
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
|
@@ -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,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
|
@@ -3,7 +3,7 @@ module Compel
|
|
3
3
|
|
4
4
|
class Boolean < Type
|
5
5
|
|
6
|
-
def
|
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
|
@@ -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
|