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.
- checksums.yaml +4 -4
- data/Gemfile +7 -0
- data/README.md +65 -54
- data/lib/compel/builder/boolean.rb +13 -0
- data/lib/compel/builder/common.rb +24 -0
- data/lib/compel/builder/common_value.rb +34 -0
- data/lib/compel/builder/date.rb +23 -0
- data/lib/compel/builder/datetime.rb +23 -0
- data/lib/compel/builder/float.rb +15 -0
- data/lib/compel/builder/hash.rb +22 -0
- data/lib/compel/builder/integer.rb +15 -0
- data/lib/compel/builder/json.rb +13 -0
- data/lib/compel/builder/methods.rb +60 -0
- data/lib/compel/builder/schema.rb +27 -0
- data/lib/compel/builder/string.rb +30 -0
- data/lib/compel/builder/time.rb +23 -0
- data/lib/compel/coercion/boolean.rb +1 -1
- data/lib/compel/coercion/date.rb +19 -2
- data/lib/compel/coercion/datetime.rb +19 -2
- data/lib/compel/coercion/float.rb +1 -1
- data/lib/compel/coercion/hash.rb +1 -1
- data/lib/compel/coercion/integer.rb +1 -1
- data/lib/compel/coercion/json.rb +1 -1
- data/lib/compel/coercion/regexp.rb +17 -0
- data/lib/compel/coercion/string.rb +1 -1
- data/lib/compel/coercion/time.rb +19 -2
- data/lib/compel/coercion/type.rb +0 -13
- data/lib/compel/coercion.rb +8 -10
- data/lib/compel/contract.rb +18 -62
- data/lib/compel/exceptions/invalid_hash_error.rb +9 -0
- data/lib/compel/exceptions/type_error.rb +7 -0
- data/lib/compel/exceptions/validation_error.rb +7 -0
- data/lib/compel/validation.rb +36 -50
- data/lib/compel/validators/base.rb +19 -0
- data/lib/compel/validators/hash_validator.rb +51 -0
- data/lib/compel/validators/type_validator.rb +30 -0
- data/lib/compel/version.rb +1 -1
- data/lib/compel.rb +16 -11
- data/spec/compel/builder_spec.rb +226 -0
- data/spec/compel/coercion_spec.rb +85 -18
- data/spec/compel/compel_spec.rb +368 -160
- data/spec/compel/sinatra_integration_spec.rb +73 -0
- data/spec/compel/validation_spec.rb +122 -8
- data/spec/spec_helper.rb +19 -0
- data/spec/support/sinatra_app.rb +43 -0
- metadata +28 -10
- data/lib/compel/invalid_params_error.rb +0 -9
- data/lib/compel/param.rb +0 -46
- data/lib/compel/param_type_error.rb +0 -7
- data/lib/compel/param_validation_error.rb +0 -7
- data/spec/compel/contract_spec.rb +0 -36
- data/spec/compel/param_spec.rb +0 -25
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4ca24b865ca3552e7fb66b3b5fa3cb54522c69f6
|
4
|
+
data.tar.gz: 06342b5d60518ad7f05fc4fcd380ad6dce9b1017
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eadd3a9cf227017eb8ed57431ba6b6b687ef2e76cee95de5dc89c9691201ff8cb2ce6afb96e7061314f07b2bc3fda5d0660bb3345e2784086f9e28d31ad90ad8
|
7
|
+
data.tar.gz: d2adaa09f03406c98f87bdc0b816b30a3983a65bf306bab7ef8fc7cd6873302d024173d3cf3ca3122c0595d903962fa154a763e66e31c8f5e7c434927e214b08
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
-
Compel
|
1
|
+
Compel
|
2
2
|
==========================
|
3
3
|

|
4
4
|
[](https://codeclimate.com/github/joaquimadraz/compel)
|
5
|
+
[](https://codeclimate.com/github/joaquimadraz/compel)
|
5
6
|
|
6
7
|
Ruby Hash Coercion and Validation
|
7
8
|
|
@@ -9,32 +10,34 @@ This is a straight forward way to validate a Ruby Hash: just give an object and
|
|
9
10
|
|
10
11
|
The motivation was to create an integration for [RestMyCase](https://github.com/goncalvesjoao/rest_my_case) and have validations before any business logic execution.
|
11
12
|
|
12
|
-
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.
|
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).
|
13
14
|
|
14
15
|
### Example
|
15
16
|
|
16
17
|
```ruby
|
17
|
-
|
18
|
+
object = {
|
18
19
|
first_name: 'Joaquim',
|
19
20
|
birth_date: '1989-0',
|
20
21
|
address: {
|
21
22
|
line_one: 'Lisboa',
|
22
23
|
post_code: '1100',
|
23
|
-
|
24
|
+
country_code: 'PT'
|
24
25
|
}
|
25
26
|
}
|
26
27
|
|
27
|
-
Compel.
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
28
|
+
schema = Compel.hash.keys({
|
29
|
+
first_name: Compel.string.required,
|
30
|
+
last_name: Compel.string.required,
|
31
|
+
birth_date: Compel.datetime,
|
32
|
+
address: Compel.hash.keys({
|
33
|
+
line_one: Compel.string.required,
|
34
|
+
line_two: Compel.string.default('-'),
|
35
|
+
post_code: Compel.string.format(/^\d{4}-\d{3}$/).required,
|
36
|
+
country_code: Compel.string.in(['PT', 'GB']).default('PT')
|
37
|
+
})
|
38
|
+
})
|
39
|
+
|
40
|
+
Compel.run(object, schema)
|
38
41
|
```
|
39
42
|
|
40
43
|
Will return an [Hashie::Mash](https://github.com/intridea/hashie) object:
|
@@ -42,17 +45,18 @@ Will return an [Hashie::Mash](https://github.com/intridea/hashie) object:
|
|
42
45
|
```ruby
|
43
46
|
{
|
44
47
|
"first_name" => "Joaquim",
|
48
|
+
"birth_date" => "1989-0",
|
45
49
|
"address" => {
|
46
|
-
"line_one" => "Lisboa",
|
47
|
-
"line_two" => "-",
|
48
|
-
"
|
49
|
-
"country_code"=> "PT"
|
50
|
+
"line_one" => "Lisboa",
|
51
|
+
"line_two" => "-",
|
52
|
+
"post_code" => "1100",
|
53
|
+
"country_code" => "PT"
|
50
54
|
},
|
51
55
|
"errors" => {
|
52
56
|
"last_name" => ["is required"],
|
53
|
-
"birth_date" => ["'1989-0' is not a
|
57
|
+
"birth_date" => ["'1989-0' is not a parsable date with format: %Y-%m-%d"],
|
54
58
|
"address" => {
|
55
|
-
"
|
59
|
+
"post_code" => ["must match format ^\d{4}-\d{3}$"]
|
56
60
|
}
|
57
61
|
}
|
58
62
|
}
|
@@ -63,60 +67,67 @@ There are 3 ways to run validations:
|
|
63
67
|
Method | Behaviour
|
64
68
|
------------- | -------------
|
65
69
|
`#run` | Validates and returns an Hash with coerced params plus a `:errors` key with a _Rails like_ Hash of errors if any.
|
66
|
-
`#run!` | Validates and raises `Compel::
|
70
|
+
`#run!` | Validates and raises `Compel::InvalidHashError` exception with the coerced params and errors.
|
67
71
|
`#run?` | Validates and returns true or false.
|
68
72
|
|
69
73
|
### Types
|
70
74
|
|
71
|
-
- `
|
72
|
-
- `
|
73
|
-
- `
|
74
|
-
- `
|
75
|
+
- `#integer`
|
76
|
+
- `#float`
|
77
|
+
- `#string`
|
78
|
+
- `#json`
|
75
79
|
- ex: `"{\"a\":1,\"b\":2,\"c\":3}"`
|
76
|
-
- `
|
80
|
+
- `#hash`
|
77
81
|
- ex: `{ a: 1, b: 2, c: 3 }`
|
78
|
-
- `
|
79
|
-
- `
|
80
|
-
- `
|
81
|
-
-
|
82
|
+
- `#date`
|
83
|
+
- `#time`
|
84
|
+
- `#datetime`
|
85
|
+
- `#boolean`,
|
82
86
|
- ex: `1`/`0`, `true`/`false`, `t`/`f`, `yes`/`no`, `y`/`n`
|
83
87
|
|
84
88
|
### Sinatra Integration
|
85
89
|
|
86
|
-
If you want to use with `Sinatra`,
|
90
|
+
If you want to use with `Sinatra`, here's an example:
|
87
91
|
|
88
92
|
```ruby
|
89
93
|
class App < Sinatra::Base
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
94
|
+
|
95
|
+
set :show_exceptions, false
|
96
|
+
set :raise_errors, true
|
97
|
+
|
98
|
+
before do
|
99
|
+
content_type :json
|
95
100
|
end
|
96
101
|
|
97
|
-
|
102
|
+
helpers do
|
103
|
+
|
104
|
+
def compel(schema)
|
105
|
+
params.merge! Compel.run!(params, Compel.hash.keys(schema))
|
106
|
+
end
|
107
|
+
|
108
|
+
end
|
109
|
+
|
110
|
+
error Compel::InvalidHashError do |exception|
|
98
111
|
status 400
|
99
|
-
|
112
|
+
{ errors: exception.errors }.to_json
|
100
113
|
end
|
101
|
-
|
114
|
+
|
102
115
|
configure :development do
|
103
116
|
set :show_exceptions, false
|
104
117
|
set :raise_errors, true
|
105
118
|
end
|
106
|
-
|
107
|
-
...
|
108
|
-
|
119
|
+
|
109
120
|
post '/api/posts' do
|
110
|
-
compel
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
121
|
+
compel({
|
122
|
+
post: Compel.hash.keys({
|
123
|
+
title: Compel.string.required,
|
124
|
+
body: Compel.string,
|
125
|
+
published: Compel.boolean.default(false)
|
126
|
+
}).required
|
127
|
+
})
|
128
|
+
|
129
|
+
params.to_json
|
117
130
|
end
|
118
|
-
|
119
|
-
...
|
120
131
|
|
121
132
|
end
|
122
133
|
```
|
@@ -124,12 +135,12 @@ end
|
|
124
135
|
|
125
136
|
Add this line to your application's Gemfile:
|
126
137
|
|
127
|
-
gem 'compel'
|
138
|
+
gem 'compel', '~> 0.2.0'
|
128
139
|
|
129
140
|
And then execute:
|
130
141
|
|
131
142
|
$ bundle
|
132
|
-
|
143
|
+
|
133
144
|
### TODO
|
134
145
|
|
135
146
|
- Write more Documentation (check specs for now ;)
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Compel
|
2
|
+
module Builder
|
3
|
+
|
4
|
+
module Common
|
5
|
+
|
6
|
+
def is(value)
|
7
|
+
options[:is] = value
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
def required
|
12
|
+
options[:required] = true
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def default(value)
|
17
|
+
options[:default] = value
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Compel
|
2
|
+
module Builder
|
3
|
+
|
4
|
+
module CommonValue
|
5
|
+
|
6
|
+
def in(value)
|
7
|
+
options[:in] = value
|
8
|
+
self
|
9
|
+
end
|
10
|
+
|
11
|
+
def length(value)
|
12
|
+
options[:length] = Coercion.coerce!(value, ::Integer)
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def range(value)
|
17
|
+
options[:range] = value
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def min(value)
|
22
|
+
options[:min] = value
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def max(value)
|
27
|
+
options[:max] = value
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Compel
|
2
|
+
module Builder
|
3
|
+
|
4
|
+
class Date < Schema
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
super(Coercion::Date)
|
8
|
+
end
|
9
|
+
|
10
|
+
def format(value)
|
11
|
+
options[:format] = value
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def iso8601
|
16
|
+
options[:format] = '%Y-%m-%d'
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Compel
|
2
|
+
module Builder
|
3
|
+
|
4
|
+
class DateTime < Schema
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
super(Coercion::DateTime)
|
8
|
+
end
|
9
|
+
|
10
|
+
def format(value)
|
11
|
+
options[:format] = value
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def iso8601
|
16
|
+
options[:format] = '%FT%T'
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Compel
|
2
|
+
module Builder
|
3
|
+
|
4
|
+
class Hash < Schema
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
super(Coercion::Hash)
|
8
|
+
end
|
9
|
+
|
10
|
+
def keys(hash)
|
11
|
+
options[:keys] = hash
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def validate(object)
|
16
|
+
Contract.new(object, self).validate.serialize
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'compel/builder/common'
|
2
|
+
require 'compel/builder/common_value'
|
3
|
+
require 'compel/builder/schema'
|
4
|
+
require 'compel/builder/hash'
|
5
|
+
require 'compel/builder/json'
|
6
|
+
require 'compel/builder/string'
|
7
|
+
require 'compel/builder/integer'
|
8
|
+
require 'compel/builder/float'
|
9
|
+
require 'compel/builder/datetime'
|
10
|
+
require 'compel/builder/time'
|
11
|
+
require 'compel/builder/date'
|
12
|
+
require 'compel/builder/boolean'
|
13
|
+
|
14
|
+
module Compel
|
15
|
+
module Builder
|
16
|
+
|
17
|
+
module Methods
|
18
|
+
|
19
|
+
def hash
|
20
|
+
Builder::Hash.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def json
|
24
|
+
Builder::JSON.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def string
|
28
|
+
Builder::String.new
|
29
|
+
end
|
30
|
+
|
31
|
+
def integer
|
32
|
+
Builder::Integer.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def float
|
36
|
+
Builder::Float.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def datetime
|
40
|
+
Builder::DateTime.new
|
41
|
+
end
|
42
|
+
|
43
|
+
def time
|
44
|
+
Builder::Time.new
|
45
|
+
end
|
46
|
+
|
47
|
+
def date
|
48
|
+
Builder::Date.new
|
49
|
+
end
|
50
|
+
|
51
|
+
def boolean
|
52
|
+
Builder::Boolean.new
|
53
|
+
end
|
54
|
+
|
55
|
+
extend self
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Compel
|
2
|
+
module Builder
|
3
|
+
|
4
|
+
class Schema
|
5
|
+
|
6
|
+
include Builder::Common
|
7
|
+
|
8
|
+
attr_reader :type,
|
9
|
+
:options
|
10
|
+
|
11
|
+
def initialize(type)
|
12
|
+
@type = type
|
13
|
+
@options = Hashie::Mash.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def required?
|
17
|
+
!!options[:required]
|
18
|
+
end
|
19
|
+
|
20
|
+
def default_value
|
21
|
+
options[:default]
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Compel
|
2
|
+
module Builder
|
3
|
+
|
4
|
+
class String < Schema
|
5
|
+
|
6
|
+
include CommonValue
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
super(Coercion::String)
|
10
|
+
end
|
11
|
+
|
12
|
+
def format(regex)
|
13
|
+
options[:format] = Coercion.coerce!(regex, ::Regexp)
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def min_length(value)
|
18
|
+
options[:min_length] = Coercion.coerce!(value, ::Integer)
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def max_length(value)
|
23
|
+
options[:max_length] = Coercion.coerce!(value, ::Integer)
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Compel
|
2
|
+
module Builder
|
3
|
+
|
4
|
+
class Time < Schema
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
super(Coercion::Time)
|
8
|
+
end
|
9
|
+
|
10
|
+
def format(value)
|
11
|
+
options[:format] = value
|
12
|
+
self
|
13
|
+
end
|
14
|
+
|
15
|
+
def iso8601
|
16
|
+
options[:format] = '%FT%T'
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
data/lib/compel/coercion/date.rb
CHANGED
@@ -3,8 +3,25 @@ module Compel
|
|
3
3
|
|
4
4
|
class Date < Type
|
5
5
|
|
6
|
-
def coerce
|
7
|
-
|
6
|
+
def coerce!
|
7
|
+
format = options[:format] || '%Y-%m-%d'
|
8
|
+
|
9
|
+
if value.is_a?(::Date)
|
10
|
+
@value = value.strftime(format)
|
11
|
+
end
|
12
|
+
|
13
|
+
coerced = ::Date.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 date with format: #{format}"
|
8
25
|
end
|
9
26
|
|
10
27
|
end
|
@@ -3,8 +3,25 @@ module Compel
|
|
3
3
|
|
4
4
|
class DateTime < Type
|
5
5
|
|
6
|
-
def coerce
|
7
|
-
|
6
|
+
def coerce!
|
7
|
+
format = options[:format] || '%FT%T'
|
8
|
+
|
9
|
+
if value.is_a?(::DateTime)
|
10
|
+
@value = value.strftime(format)
|
11
|
+
end
|
12
|
+
|
13
|
+
coerced = ::DateTime.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 datetime with format: #{format}"
|
8
25
|
end
|
9
26
|
|
10
27
|
end
|
data/lib/compel/coercion/hash.rb
CHANGED
data/lib/compel/coercion/json.rb
CHANGED