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.
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