compel 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 20943c4ee705f920a1d316a9028b0ddc7e26cfc7
4
+ data.tar.gz: e9071ee624b81b8efe30b1747eacc3d00426339b
5
+ SHA512:
6
+ metadata.gz: 638f760fe8ea31a6545101cf48923ed2d835cf4f449d4f29bbf64610945eb78013d4aaedb5ea0724946846235b490741712b12d7dbd8cc09ac2c60127734aa0f
7
+ data.tar.gz: cf70bca72fc84dbc3e4c3434a16bb49569a659188e1be6049e20d058027ac33be2f0361b12f240a9d1b5e0a3b6572a7b5ae295735445cf29628f09f28558d1b9
data/.gitignore ADDED
@@ -0,0 +1,20 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .rvmrc
7
+ .ruby-version
8
+ .ruby-gemset
9
+ Gemfile.lock
10
+ InstalledFiles
11
+ _yardoc
12
+ coverage
13
+ doc/
14
+ lib/bundler/man
15
+ pkg
16
+ rdoc
17
+ spec/reports
18
+ test/tmp
19
+ test/version_tmp
20
+ tmp
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format documentation
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.3
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Joaquim Adráz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
data/README.md ADDED
@@ -0,0 +1,131 @@
1
+ Compel
2
+ ==========================
3
+ ![](https://travis-ci.org/joaquimadraz/compel.svg)
4
+ [![Code Climate](https://codeclimate.com/github/joaquimadraz/compel/badges/gpa.svg)](https://codeclimate.com/github/joaquimadraz/compel)
5
+
6
+ Ruby Hash Coercion and Validation
7
+
8
+ This is a straight forward way to validate a Ruby Hash: just give an object and the schema.
9
+
10
+ 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
+ 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
+
14
+ ###Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ gem 'compel'
19
+
20
+ And then execute:
21
+
22
+ $ bundle
23
+
24
+ ### Example
25
+
26
+ ```ruby
27
+ params= {
28
+ first_name: 'Joaquim',
29
+ birth_date: '1989-0',
30
+ address: {
31
+ line_one: 'Lisboa',
32
+ post_code: '1100',
33
+ country: 'PT'
34
+ }
35
+ }
36
+
37
+ Compel.run(params) do
38
+ param :first_name, String, required: true
39
+ param :last_name, String, required: true
40
+ param :birth_date, DateTime
41
+ param :address, Hash do
42
+ param :line_one, String, required: true
43
+ param :line_two, String, default: '-'
44
+ param :post_code, String, required: true, format: /^\d{4}-\d{3}$/
45
+ param :country_code, String, in: ['PT', 'GB'], default: 'PT'
46
+ end
47
+ end
48
+ ```
49
+
50
+ Will return an [Hashie::Mash](https://github.com/intridea/hashie) object:
51
+
52
+ ```ruby
53
+ {
54
+ "first_name" => "Joaquim",
55
+ "address" => {
56
+ "line_one" => "Lisboa",
57
+ "line_two" => "-", # default value
58
+ "post_code_pfx" => 1100, # Already an Integer
59
+ "country_code"=> "PT"
60
+ },
61
+ "errors" => {
62
+ "last_name" => ["is required"],
63
+ "birth_date" => ["'1989-0' is not a valid DateTime"],
64
+ "address" => {
65
+ "post_code_sfx" => ["is required"]
66
+ }
67
+ }
68
+ }
69
+ ```
70
+
71
+ There are 3 ways run validations:
72
+
73
+ - `#run`
74
+ - Validates and returns an Hash with coerced params plus a `:errors` key with a _Rails like_ Hash of errors if any.
75
+ - `#run!`
76
+ - Validates and raises `Compel::InvalidParamsError` exception with the coerced params and errors.
77
+ - `#run?`
78
+ - Validates and returns true or false.
79
+
80
+
81
+ ### Types
82
+
83
+ - `Integer`
84
+ - `Float`
85
+ - `String`
86
+ - `JSON`
87
+ - ex: `"{\"a\":1,\"b\":2,\"c\":3}"`
88
+ - `Hash`
89
+ - ex: `{ a: 1, b: 2, c: 3 }`
90
+ - `Date`
91
+ - `Time`
92
+ - `DateTime`
93
+ - `Compel::Boolean`,
94
+ - ex: `1`/`0`, `true`/`false`, `t`/`f`, `yes`/`no`, `y`/`n`
95
+
96
+ ### Sinatra Integration
97
+
98
+ If you want to use with `Sinatra`, just add the following code to your Sinatra app:
99
+
100
+ ```ruby
101
+ class App < Sinatra::Base
102
+
103
+ ...
104
+
105
+ def compel(&block)
106
+ Compel::run!(params, &block)
107
+ end
108
+
109
+ error Compel::InvalidParamsError do |exception|
110
+ status 400
111
+ json errors: exception.errors
112
+ end
113
+
114
+ configure :development do
115
+ set :show_exceptions, false
116
+ set :raise_errors, true
117
+ end
118
+
119
+ end
120
+ ```
121
+
122
+ ### TODO
123
+
124
+ - Write more Documentation (check specs for now ;)
125
+ - Rails integration
126
+ - [RestMyCase](https://github.com/goncalvesjoao/rest_my_case) integration
127
+
128
+
129
+ ### Get in touch
130
+ If you have any questions, write an issue or get in touch [@joaquimadraz](https://twitter.com/joaquimadraz)
131
+
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ task :default => :test
5
+ RSpec::Core::RakeTask.new(:test)
data/compel.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # encoding: utf-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'compel/version'
6
+
7
+ Gem::Specification.new do |gem|
8
+ gem.name = 'compel'
9
+ gem.version = Compel::VERSION
10
+ gem.authors = ['Joaquim Adráz']
11
+ gem.email = ['joaquim.adraz@gmail.com']
12
+ gem.description = %q{Compel}
13
+ gem.summary = %q{Ruby Hash Coercion and Validation}
14
+ gem.homepage = 'https://github.com/joaquimadraz/compel'
15
+
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
19
+ gem.require_paths = ['lib']
20
+
21
+ gem.add_runtime_dependency 'hashie', '~> 3.4'
22
+ gem.add_development_dependency 'rspec', '~> 3.2'
23
+ gem.add_development_dependency 'rake', '~> 0'
24
+ gem.add_development_dependency 'pry', '~> 0'
25
+ end
@@ -0,0 +1,21 @@
1
+ module Compel
2
+ module Coercion
3
+
4
+ class Boolean < Type
5
+
6
+ def coerce
7
+ if /(false|f|no|n|0)$/i === "#{value}"
8
+ return false
9
+ end
10
+
11
+ if /(true|t|yes|y|1)$/i === "#{value}"
12
+ return true
13
+ end
14
+
15
+ fail
16
+ end
17
+
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,13 @@
1
+ module Compel
2
+ module Coercion
3
+
4
+ class Date < Type
5
+
6
+ def coerce
7
+ ::Date.parse(value)
8
+ end
9
+
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Compel
2
+ module Coercion
3
+
4
+ class DateTime < Type
5
+
6
+ def coerce
7
+ ::DateTime.parse(value)
8
+ end
9
+
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Compel
2
+ module Coercion
3
+
4
+ class Float < Type
5
+
6
+ def coerce
7
+ Float(value)
8
+ end
9
+
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Compel
2
+ module Coercion
3
+
4
+ class Hash < Type
5
+
6
+ def coerce
7
+ Hashie::Mash.new(value).to_hash
8
+ end
9
+
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Compel
2
+ module Coercion
3
+
4
+ class Integer < Type
5
+
6
+ def coerce
7
+ Integer(value)
8
+ end
9
+
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ module Compel
2
+ module Coercion
3
+
4
+ class JSON < Type
5
+
6
+ def coerce
7
+ ::JSON.parse(value)
8
+ end
9
+
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,17 @@
1
+ module Compel
2
+ module Coercion
3
+
4
+ class String < Type
5
+
6
+ def coerce
7
+ if !value.is_a?(::String)
8
+ fail
9
+ end
10
+
11
+ value
12
+ end
13
+
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ module Compel
2
+ module Coercion
3
+
4
+ class Time < Type
5
+
6
+ def coerce
7
+ ::Time.parse(value)
8
+ end
9
+
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,30 @@
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 parse
15
+ @value = value
16
+ end
17
+
18
+ def serialize
19
+ raise '#serialize? should be implemented'
20
+ end
21
+
22
+ def coerce!
23
+ parse
24
+ coerce
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,33 @@
1
+ module Compel
2
+
3
+ module Coercion
4
+
5
+ def valid?(value, type, options = {})
6
+ !!coerce!(value, type, options) rescue false
7
+ end
8
+
9
+ def coerce!(value, type, options = {})
10
+ return nil if value.nil?
11
+
12
+ begin
13
+ klass = compel_type?(type) ? type : get_compel_type_klass(type)
14
+
15
+ return klass.new(value, options).coerce!
16
+ rescue
17
+ raise ParamTypeError, "'#{value}' is not a valid #{type}"
18
+ end
19
+ end
20
+
21
+ def compel_type?(type)
22
+ type.to_s.split('::')[0..1].join('::') == 'Compel::Coercion'
23
+ end
24
+
25
+ def get_compel_type_klass(type)
26
+ const_get("Compel::Coercion::#{type}")
27
+ end
28
+
29
+ extend self
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,101 @@
1
+ module Compel
2
+
3
+ class Contract
4
+
5
+ attr_reader :errors,
6
+ :conditions,
7
+ :coerced_params,
8
+ :serialized_errors
9
+
10
+ def initialize(params, &block)
11
+ if params.nil? || !Coercion.valid?(params, Hash)
12
+ raise ParamTypeError, 'params must be an Hash'
13
+ end
14
+
15
+ @errors = Errors.new
16
+ @params = Hashie::Mash.new(params)
17
+ @conditions = Hashie::Mash.new
18
+ @coerced_params = Hashie::Mash.new
19
+
20
+ instance_eval(&block)
21
+ end
22
+
23
+ def validate
24
+ @conditions.values.each do |param|
25
+ begin
26
+ # If it is an Hash and it was given conditions for that Hash,
27
+ # build a new Compel::Contract form inner conditions
28
+ if (param.hash? && param.conditions?)
29
+
30
+ # If this param is required, build the Compel::Contract,
31
+ # otherwise, only build it if is given a value for the param
32
+ if param.required? || !param.value.nil?
33
+ contract = Contract.new(param.value, &param.conditions).validate
34
+
35
+ @errors.add(param.name, contract.errors)
36
+
37
+ # Update the param value with coerced values to use later
38
+ # when coercing param parent
39
+ @coerced_params[param.name] = contract.coerced_params
40
+ end
41
+ end
42
+
43
+ # All values must coerce before going through validation,
44
+ # raise exception to avoid validation
45
+
46
+ # If the param value has already been coerced from digging into child Hash
47
+ # use that value instead, so we don't loose the previous coerced values
48
+
49
+ coerced_value = Coercion.coerce! \
50
+ (@coerced_params[param.name].nil? ? param.value : @coerced_params[param.name]), param.type, param.options
51
+
52
+ # Only add to coerced values if not nil
53
+ if !coerced_value.nil?
54
+ @coerced_params[param.name] = coerced_value
55
+ end
56
+
57
+ @errors.add \
58
+ param.name, Validation.validate(param.value, param.options)
59
+
60
+ rescue Compel::ParamTypeError => exception
61
+ @errors.add(param.name, exception.message)
62
+ end
63
+ end
64
+
65
+ self
66
+ end
67
+
68
+ def param(name, type, options = {}, &block)
69
+ @conditions[name] = \
70
+ Param.new(name, type, @params[name], options, &block)
71
+ end
72
+
73
+ def serialize
74
+ coerced_params.tap do |hash|
75
+ if !valid?
76
+ hash[:errors] = serialized_errors
77
+ end
78
+ end
79
+ end
80
+
81
+ def valid?
82
+ @errors.empty?
83
+ end
84
+
85
+ def serialized_errors
86
+ @errors.to_hash
87
+ end
88
+
89
+ def raise?
90
+ if !valid?
91
+ exception = InvalidParamsError.new
92
+ exception.params = coerced_params
93
+ exception.errors = serialized_errors
94
+
95
+ raise exception, 'params are invalid'
96
+ end
97
+ end
98
+
99
+ end
100
+
101
+ end
@@ -0,0 +1,53 @@
1
+ module Compel
2
+
3
+ class Errors
4
+
5
+ def initialize
6
+ @errors = Hashie::Mash.new
7
+ end
8
+
9
+ def add(key, error)
10
+ if error.is_a?(Compel::Errors)
11
+ if error.empty?
12
+ return
13
+ end
14
+
15
+ if @errors[key].nil?
16
+ @errors[key] = {}
17
+ end
18
+
19
+ @errors[key].merge!(error.to_hash)
20
+
21
+ return
22
+ end
23
+
24
+ if !error.is_a?(Array)
25
+ error = [error]
26
+ end
27
+
28
+ if error.empty?
29
+ return
30
+ end
31
+
32
+ if @errors[key].nil?
33
+ @errors[key] = []
34
+ end
35
+
36
+ @errors[key].concat(error)
37
+ end
38
+
39
+ def length
40
+ @errors.keys.length
41
+ end
42
+
43
+ def empty?
44
+ length == 0
45
+ end
46
+
47
+ def to_hash
48
+ @errors
49
+ end
50
+
51
+ end
52
+
53
+ end
@@ -0,0 +1,9 @@
1
+ module Compel
2
+
3
+ class InvalidParamsError < StandardError
4
+
5
+ attr_accessor :params, :errors
6
+
7
+ end
8
+
9
+ end
@@ -0,0 +1,46 @@
1
+ module Compel
2
+
3
+ class Param
4
+
5
+ attr_reader :name,
6
+ :type,
7
+ :options,
8
+ :conditions
9
+
10
+ def initialize(name, type, value, options = {}, &conditions)
11
+ @name = name
12
+ @type = type
13
+ @value = value
14
+ @options = options
15
+ @conditions = conditions
16
+ end
17
+
18
+ def value
19
+ default_value = if options[:default].is_a?(Proc)
20
+ options[:default].call
21
+ else
22
+ options[:default]
23
+ end
24
+
25
+ @value || default_value
26
+ end
27
+
28
+ def value=(value)
29
+ @value = value
30
+ end
31
+
32
+ def required?
33
+ !!@options[:required]
34
+ end
35
+
36
+ def hash?
37
+ @type == Hash
38
+ end
39
+
40
+ def conditions?
41
+ !!@conditions
42
+ end
43
+
44
+ end
45
+
46
+ end
@@ -0,0 +1,7 @@
1
+ module Compel
2
+
3
+ class ParamTypeError < StandardError
4
+
5
+ end
6
+
7
+ end
@@ -0,0 +1,7 @@
1
+ module Compel
2
+
3
+ class ParamValidationError < StandardError
4
+
5
+ end
6
+
7
+ end