compel 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +7 -0
  3. data/README.md +65 -54
  4. data/lib/compel/builder/boolean.rb +13 -0
  5. data/lib/compel/builder/common.rb +24 -0
  6. data/lib/compel/builder/common_value.rb +34 -0
  7. data/lib/compel/builder/date.rb +23 -0
  8. data/lib/compel/builder/datetime.rb +23 -0
  9. data/lib/compel/builder/float.rb +15 -0
  10. data/lib/compel/builder/hash.rb +22 -0
  11. data/lib/compel/builder/integer.rb +15 -0
  12. data/lib/compel/builder/json.rb +13 -0
  13. data/lib/compel/builder/methods.rb +60 -0
  14. data/lib/compel/builder/schema.rb +27 -0
  15. data/lib/compel/builder/string.rb +30 -0
  16. data/lib/compel/builder/time.rb +23 -0
  17. data/lib/compel/coercion/boolean.rb +1 -1
  18. data/lib/compel/coercion/date.rb +19 -2
  19. data/lib/compel/coercion/datetime.rb +19 -2
  20. data/lib/compel/coercion/float.rb +1 -1
  21. data/lib/compel/coercion/hash.rb +1 -1
  22. data/lib/compel/coercion/integer.rb +1 -1
  23. data/lib/compel/coercion/json.rb +1 -1
  24. data/lib/compel/coercion/regexp.rb +17 -0
  25. data/lib/compel/coercion/string.rb +1 -1
  26. data/lib/compel/coercion/time.rb +19 -2
  27. data/lib/compel/coercion/type.rb +0 -13
  28. data/lib/compel/coercion.rb +8 -10
  29. data/lib/compel/contract.rb +18 -62
  30. data/lib/compel/exceptions/invalid_hash_error.rb +9 -0
  31. data/lib/compel/exceptions/type_error.rb +7 -0
  32. data/lib/compel/exceptions/validation_error.rb +7 -0
  33. data/lib/compel/validation.rb +36 -50
  34. data/lib/compel/validators/base.rb +19 -0
  35. data/lib/compel/validators/hash_validator.rb +51 -0
  36. data/lib/compel/validators/type_validator.rb +30 -0
  37. data/lib/compel/version.rb +1 -1
  38. data/lib/compel.rb +16 -11
  39. data/spec/compel/builder_spec.rb +226 -0
  40. data/spec/compel/coercion_spec.rb +85 -18
  41. data/spec/compel/compel_spec.rb +368 -160
  42. data/spec/compel/sinatra_integration_spec.rb +73 -0
  43. data/spec/compel/validation_spec.rb +122 -8
  44. data/spec/spec_helper.rb +19 -0
  45. data/spec/support/sinatra_app.rb +43 -0
  46. metadata +28 -10
  47. data/lib/compel/invalid_params_error.rb +0 -9
  48. data/lib/compel/param.rb +0 -46
  49. data/lib/compel/param_type_error.rb +0 -7
  50. data/lib/compel/param_validation_error.rb +0 -7
  51. data/spec/compel/contract_spec.rb +0 -36
  52. data/spec/compel/param_spec.rb +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: babd567c6c66e9826606f628417eb16599a30f43
4
- data.tar.gz: 3abbd9005a7aeb99146677491cd5a9d9b59add3a
3
+ metadata.gz: 4ca24b865ca3552e7fb66b3b5fa3cb54522c69f6
4
+ data.tar.gz: 06342b5d60518ad7f05fc4fcd380ad6dce9b1017
5
5
  SHA512:
6
- metadata.gz: b326bc4046b7844f027a5cde2ed1316dfe32180a5b2a804a93b368221740f05f88cb43d1c9781080aa80ea00391845ac16f1602fc94c637d7e51bba9daee352f
7
- data.tar.gz: 77f93f863cd71d5779d7e76d0d658c41f47ca9e0766fc38fbfd8ba08a1a390c946c75bd3621549cff20708711a6c24101c4e7f2ea04b7f0c34a7a0dec09d7dc1
6
+ metadata.gz: eadd3a9cf227017eb8ed57431ba6b6b687ef2e76cee95de5dc89c9691201ff8cb2ce6afb96e7061314f07b2bc3fda5d0660bb3345e2784086f9e28d31ad90ad8
7
+ data.tar.gz: d2adaa09f03406c98f87bdc0b816b30a3983a65bf306bab7ef8fc7cd6873302d024173d3cf3ca3122c0595d903962fa154a763e66e31c8f5e7c434927e214b08
data/Gemfile CHANGED
@@ -1,3 +1,10 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
+
5
+ group :test do
6
+ gem 'codeclimate-test-reporter'
7
+ gem 'simplecov', require: false
8
+ gem 'rack-test'
9
+ gem 'sinatra', require: false
10
+ end
data/README.md CHANGED
@@ -1,7 +1,8 @@
1
- Compel
1
+ 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
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
- params= {
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
- country: 'PT'
24
+ country_code: 'PT'
24
25
  }
25
26
  }
26
27
 
27
- Compel.run(params) do
28
- param :first_name, String, required: true
29
- param :last_name, String, required: true
30
- param :birth_date, DateTime
31
- param :address, Hash do
32
- param :line_one, String, required: true
33
- param :line_two, String, default: '-'
34
- param :post_code, String, required: true, format: /^\d{4}-\d{3}$/
35
- param :country_code, String, in: ['PT', 'GB'], default: 'PT'
36
- end
37
- end
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" => "-", # default value
48
- "post_code_pfx" => 1100, # Already an Integer
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 valid DateTime"],
57
+ "birth_date" => ["'1989-0' is not a parsable date with format: %Y-%m-%d"],
54
58
  "address" => {
55
- "post_code_sfx" => ["is required"]
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::InvalidParamsError` exception with the coerced params and errors.
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
- - `Integer`
72
- - `Float`
73
- - `String`
74
- - `JSON`
75
+ - `#integer`
76
+ - `#float`
77
+ - `#string`
78
+ - `#json`
75
79
  - ex: `"{\"a\":1,\"b\":2,\"c\":3}"`
76
- - `Hash`
80
+ - `#hash`
77
81
  - ex: `{ a: 1, b: 2, c: 3 }`
78
- - `Date`
79
- - `Time`
80
- - `DateTime`
81
- - `Compel::Boolean`,
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`, just add the following code to your Sinatra app:
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
- def compel(&block)
94
- params.merge! Compel::run!(params, &block)
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
- error Compel::InvalidParamsError do |exception|
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
- json errors: exception.errors
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 do
111
- param :post, Hash, required: true do
112
- param :title, String, required: true
113
- end
114
- end
115
-
116
- puts params[:post]
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,13 @@
1
+ module Compel
2
+ module Builder
3
+
4
+ class Boolean < Schema
5
+
6
+ def initialize
7
+ super(Coercion::Boolean)
8
+ end
9
+
10
+ end
11
+
12
+ end
13
+ end
@@ -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,15 @@
1
+ module Compel
2
+ module Builder
3
+
4
+ class Float < Schema
5
+
6
+ include CommonValue
7
+
8
+ def initialize
9
+ super(Coercion::Float)
10
+ end
11
+
12
+ end
13
+
14
+ end
15
+ 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,15 @@
1
+ module Compel
2
+ module Builder
3
+
4
+ class Integer < Schema
5
+
6
+ include CommonValue
7
+
8
+ def initialize
9
+ super(Coercion::Integer)
10
+ end
11
+
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ module Compel
2
+ module Builder
3
+
4
+ class JSON < Schema
5
+
6
+ def initialize
7
+ super(Coercion::JSON)
8
+ end
9
+
10
+ end
11
+
12
+ end
13
+ 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
@@ -3,7 +3,7 @@ module Compel
3
3
 
4
4
  class Boolean < Type
5
5
 
6
- def coerce
6
+ def coerce!
7
7
  if /(false|f|no|n|0)$/i === "#{value}"
8
8
  return false
9
9
  end
@@ -3,8 +3,25 @@ module Compel
3
3
 
4
4
  class Date < Type
5
5
 
6
- def coerce
7
- ::Date.parse(value)
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
- ::DateTime.parse(value)
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
@@ -3,7 +3,7 @@ module Compel
3
3
 
4
4
  class Float < Type
5
5
 
6
- def coerce
6
+ def coerce!
7
7
  Float(value)
8
8
  end
9
9
 
@@ -3,7 +3,7 @@ module Compel
3
3
 
4
4
  class Hash < Type
5
5
 
6
- def coerce
6
+ def coerce!
7
7
  Hashie::Mash.new(value).to_hash
8
8
  end
9
9
 
@@ -3,7 +3,7 @@ module Compel
3
3
 
4
4
  class Integer < Type
5
5
 
6
- def coerce
6
+ def coerce!
7
7
  Integer(value)
8
8
  end
9
9
 
@@ -3,7 +3,7 @@ module Compel
3
3
 
4
4
  class JSON < Type
5
5
 
6
- def coerce
6
+ def coerce!
7
7
  ::JSON.parse(value)
8
8
  end
9
9
 
@@ -0,0 +1,17 @@
1
+ module Compel
2
+ module Coercion
3
+
4
+ class Regexp < Type
5
+
6
+ def coerce!
7
+ if value.is_a?(::Regexp)
8
+ return value
9
+ end
10
+
11
+ fail
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+ end