parameters_schema 0.42

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: 699ec439cfd577bce7294f3718e6a45b4e9d4652
4
+ data.tar.gz: 6bc716b44313cc86c2b10ab393c6c9ad01c6dd9a
5
+ SHA512:
6
+ metadata.gz: f538951c2a1732b9cfa90412069a53d5ad5e5907667ae0b262fdd25c018082fabfbe3b409c7c660663166145eb5e9eb88254eba89f169e8337bd35ff49226aad
7
+ data.tar.gz: 2820f485ea953e8c629b6262b42db91ed6774f94509db1417eb85d5d537025c3eea1f1ccaae645d478ef3faaf86ed1746df579da52e33c4cc5e61a7bb70afad6
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'minitest'
4
+ gem 'activesupport'
data/README.md ADDED
@@ -0,0 +1,286 @@
1
+ # Parameters Schema
2
+
3
+ ## *"A strict API is the best kind of API."*
4
+
5
+ In this line of thought, **[strong_parameters](https://github.com/rails/strong_parameters)** lacks some cool validations that this gem provide.
6
+
7
+ For example, let's say you want your operation `create` to require a `Fixnum` parameter between 1 and 99. With strong_parameters, you're out of luck. With this gem, you simply write in your controller:
8
+ ``` ruby
9
+ class Api::PeopleController < Api::BaseController
10
+ def create
11
+ validated_params = validate_params params do
12
+ param :age, type: Fixnum, allow: (1..99)
13
+ end
14
+
15
+ # Do something with the validated parameters.
16
+ end
17
+ end
18
+ ```
19
+
20
+ So when you use this controller:
21
+ ``` ruby
22
+ > app.post 'api/people', age: 12 # validated_params = { age: 12 }
23
+ > app.post 'api/people', age: 100 # throws a ParameterSchema::InvalidParameters
24
+ ```
25
+
26
+ ## Why use this gem instead of strong_parameters
27
+
28
+ * You prefer a procedural approach (via a DSL) over a declarative one.
29
+ * You want more control over the parameters of your API, at the type and format level.
30
+ * You want to do things differently, you fucking hipster.
31
+
32
+ ## Installation
33
+
34
+ Add in your Gemfile:
35
+ ``` ruby
36
+ gem 'parameters_schema'
37
+ ```
38
+
39
+ Add in your project:
40
+ ``` ruby
41
+ require 'parameters_schema'
42
+ ```
43
+
44
+ ## Schema
45
+
46
+ The schema is the heart of this gem. It provides a simple DSL to express an operation's parameters.
47
+
48
+ Creating a schema:
49
+ ``` ruby
50
+ schema = ParametersSchema::Schema.new do
51
+ # Define parameters here...
52
+ # ... but an empty schema is also valid.
53
+ end
54
+ ```
55
+ Validating parameters against a schema:
56
+ ``` ruby
57
+ params = { potatoe: 'Eramosa' }
58
+ schema.validate!(params)
59
+ ```
60
+
61
+ The minimal representation of a parameter is:
62
+ ``` ruby
63
+ param :potatoe
64
+ ```
65
+ This represents a `required` parameter of type `String` accepting any characters and which doesn't allow nil or empty values.
66
+
67
+ The valid options for a parameter are:
68
+ ``` ruby
69
+ * required # Whether the parameter is required. Default: true.
70
+ * type # The type of the parameter. Default: String.
71
+ * allow # The allowed values of the parameter. Default: :any.
72
+ * deny # The denied values of the parameter. Default: :none.
73
+ * array # Whether the parameter is an array. Default: false.
74
+ ```
75
+
76
+ ### Parameter types
77
+
78
+ The available types are:
79
+ ``` ruby
80
+ * String
81
+ * Symbol
82
+ * Fixnum
83
+ * Float
84
+ * Date
85
+ * DateTime
86
+ * Array # An array of :any types.
87
+ * Hash # An object which members are not validated further.
88
+ * :boolean # See options for accepted values.
89
+ * :any # Accepts any value type.
90
+ ```
91
+
92
+ To accept more than one type, you can do:
93
+ ``` ruby
94
+ param :potatoe, type: [Boolean, String] # Accepts a boolean or string value.
95
+ ```
96
+
97
+ To accept an array of a specific type, you can do:
98
+ ``` ruby
99
+ param :potatoes, type: { Array => String } # Accepts an array of strings.
100
+ ```
101
+
102
+ To deeper refine the schema of an object, you pass a block to the parameter:
103
+ ```
104
+ param :potatoe do # Implicitly of type Hash
105
+ param :variety
106
+ param :origin
107
+ end
108
+ ```
109
+
110
+ As you have seen above, a parameter can be of type `Array` but can also have the option `array`. Confusing, right? This option was introduced to simplify the `type` syntax. For example:
111
+ ```
112
+ param :potatoes, type: String, array: true # This is equivalent...
113
+ param :potatoes, type: { Array => String } # ... to this.
114
+ ```
115
+
116
+ But this parameter truly shine with an array of objects:
117
+ ```
118
+ param :potatoes, array: true do
119
+ param :variety
120
+ param :origin
121
+ end
122
+
123
+ # This syntax is also valid but less sexy:
124
+ param :potatoes, type: { Array => Hash } do
125
+ param :variety
126
+ param :origin
127
+ end
128
+ ```
129
+
130
+ #### Gotchas
131
+ * A `Float` value can be passed to a `Fixnum` parameter but will loose its precision.
132
+ * Some types accepts more than one representation. Example: `Symbol` accepts any type that respond to `:to_sym`.
133
+ * If you define multiple types (ex: `[Symbol, String]`), values are interpreted in this order. So the value `'a'` will be cast to `:a`.
134
+ * Defining the type `{ Fixnum => Date }` doesn't make sense so it falls back to `Fixnum` (the key).
135
+ * `{ Array => Array }` is accepted. It means a 2D array of `:any`.
136
+ * `{ Array => Array => ... }` is not yet supported. Did I hear pull request?
137
+
138
+ ### The `allow` and `deny` options
139
+
140
+ By default, the value of a parameter can be any one in the spectrum of a type, with the exception of nil and empty. The `allow` and `deny` options can be used to further refine the accepted values.
141
+
142
+ To accept nil or empty values:
143
+ ``` ruby
144
+ param :potatoe, allow: :nil
145
+ # => accepts nil, 'Kennebec' but not ''.
146
+
147
+ param :potatoe, allow: :empty
148
+ # => accepts '', 'Kennebec' but not nil.
149
+
150
+ param :potatoe, allow: [:nil, :empty]
151
+ # => accepts nil, '' and 'Kennebec'
152
+ ```
153
+ Of course, this *nil* or *empty* restriction doesn't make sense for all the types so it will only be applied when it does.
154
+
155
+ To accept predefined values:
156
+ ``` ruby
157
+ param :potatoe, allow: ['Superior', 'Ac Belmont', 'Eramosa'] # this is case-sensitive.
158
+
159
+ # Gotcha: this will allow empty values even if you wanted to accept the value 'empty'. You can redefine keywords in the options.
160
+ param :potatoe, type: Symbol, allow: [:superior, :ac_belmont, :empty]
161
+ ```
162
+
163
+ To accept a value matching a regex:
164
+ ``` ruby
165
+ param :potatoe, allow: /^[a-zA-Z]*$/
166
+
167
+ # Gotcha: even though the regex above allows empty values, it must be explicitly stated:
168
+ param :potatoe, allow: [:empty, /^[a-zA-Z]*$/]
169
+ ```
170
+
171
+ To accept a value in a range:
172
+ ``` ruby
173
+ param :potatoe, type: Fixnum, allow: (1..3)
174
+ # => accepts 1, 2, 3 but will fail on any other value.
175
+ ```
176
+
177
+ The `deny` option is conceptually identical to `allow` but a value will fail the validation if a match is found:
178
+ ``` ruby
179
+ param :potatoe, type: Fixnum, deny: (1..3)
180
+ # => accepts any value except 1, 2, 3.
181
+ ```
182
+
183
+ The options `allow` and `deny` are validated independently. So beware to not define `allow` and `deny` options that encompass all the possible values of the parameter!
184
+
185
+ ## Exceptions
186
+
187
+ When the validation fails, an instance of `ParametersSchema::InvalidParameters` is raised. This exception contains the attribute `errors` which is an hash of `{ key: error_code }` that you can work with.
188
+
189
+ Simple case:
190
+ ``` ruby
191
+ ParametersSchema::Schema.new do
192
+ param :potatoe
193
+ end.validate!({})
194
+
195
+ # => ParametersSchema::InvalidParameters
196
+ # @errors = { potatoe: :missing }
197
+ ```
198
+
199
+ The validation process tries to accumulate as many errors as possible before raising the exception, so you can have a precise picture of what went wrong:
200
+ ``` ruby
201
+ ParametersSchema::Schema.new do
202
+ param :potatoe do
203
+ param :name
204
+ param :type, allow: ['Atlantic']
205
+ end
206
+ end.validate!(potatoe: { type: 'Conestoga' })
207
+
208
+ # => ParametersSchema::InvalidParameters
209
+ # @errors = { potatoe: { name: :missing, type: :disallowed } }
210
+ ```
211
+
212
+ The possible error codes are (in the order the are validated):
213
+ ``` ruby
214
+ * :unknown # The parameter is provided but not defined in the schema.
215
+ * :missing # The parameter is required but is missing.
216
+ * :nil # The value cannot be nil but is nil.
217
+ * :empty # The value cannot be empty but is empty.
218
+ * :disallowed # The value has an invalid format (type/allow) other than nil/empty.
219
+ ```
220
+
221
+ ## Integrate with Rails
222
+
223
+ This gem can be used outside of Rails but was created with Rails in mind. For example, the parameters `controller, action, format` are skipped by default (see Options section to override this behavior) and the parameters are defined in a `Hash`. However, this gem doesn't insinuate itself in your project so you must manually add it in your controllers or anywhere else that make sense to you. Here is a little recipe to add validation in your API pipeline:
224
+
225
+ In the base controller of your API, add this **helper**:
226
+ ``` ruby
227
+ # Validate the parameters of an action, using a schema.
228
+ # Returns the validated parameters and throw exceptions on invalid input.
229
+ def validate_params(&parameters_schema)
230
+ schema = ParametersSchema::Schema.new(parameters_schema)
231
+ schema.validate!(params)
232
+ end
233
+ ```
234
+ In the base controller of your API, add this **exception handler**:
235
+ ``` ruby
236
+ # Handle errors related to invalid parameters.
237
+ rescue_from ParametersSchema::InvalidParameters do |e|
238
+ # Do something with the exception (ex: log it).
239
+
240
+ # Render the response.
241
+ render json: ..., status: :bad_request
242
+ end
243
+ ```
244
+
245
+ Now in any controller where you want to validate the parameters, you can do:
246
+ ``` ruby
247
+ def operation
248
+ validated_params = validate_parameters do
249
+ # ...
250
+ end
251
+ # ...
252
+ end
253
+ ```
254
+
255
+ ## Options
256
+
257
+ Options can be specified on the module `ParametersSchema::Options`. Example:
258
+
259
+ ``` ruby
260
+ ParametersSchema::Options.skip_parameters = [:internal_stuff]
261
+ ```
262
+
263
+ Available options:
264
+ * `skip_parameters` an array of first-level parameters to skip. Default: `[:controller, :action, :format]`.
265
+ * `empty_keyword` the keyword used to represent an empty value. Default: `:empty`.
266
+ * `any_keyword` the keyword used to represent any value. Default: `:any`.
267
+ * `none_keyword` the keyword used to represent no value. Default: `:none`.
268
+ * `boolean_keyword` the keyword used to represent a boolean value. Default: `:boolean`.
269
+ * `nil_keyword` the keyword used to represent a nil value. Default: `:nil`.
270
+ * `boolean_true_values` the accepted boolean true values. Not case-sensitive. Default: `true`, `'t'`, `'true'`, `'1'`, `1`, `1.0`.
271
+ * `boolean_false_values` the accepted boolean false values. Not case-sensitive. Default: `false`, `'f'`, `'false'`, `'0'`, `0`, `0.0`.
272
+
273
+ ## Contribute
274
+
275
+ Yes, please. Bug fixes, new features, refactoring, unit tests. Send your precious pull requests.
276
+
277
+ ### Ideas
278
+
279
+ * Array of arrays of ...
280
+ * Min/Max for numeric values
281
+ * More `allow` options
282
+ * Better refine error codes
283
+
284
+ ## License
285
+
286
+ Parameters Schema is released under the [MIT License](http://www.opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << 'test'
5
+ end
6
+
7
+ desc 'Run tests'
8
+ task default: :test
@@ -0,0 +1,21 @@
1
+ class Object
2
+ #
3
+ # Check if object is numeric.
4
+ # From http://stackoverflow.com/questions/5661466/test-if-string-is-a-number-in-ruby-on-rails
5
+ #
6
+ # p "1".numeric? # => true
7
+ # p "1.2".numeric? # => true
8
+ # p "5.4e-29".numeric? # => true
9
+ # p "12e20".numeric? # => true
10
+ # p "1a".numeric? # => false
11
+ # p "1.2.3.4".numeric? # => false
12
+ #
13
+ def numeric?
14
+ return true if self.kind_of?(Numeric)
15
+ return true if self.to_s =~ /^\d+$/
16
+ Float(self)
17
+ true
18
+ rescue
19
+ false
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ module ParametersSchema
2
+ module ErrorCode
3
+ unless defined? UNKNOWN # Make sure we don't redefine the constants twice.
4
+ UNKNOWN = :unknown
5
+ MISSING = :missing
6
+ NIL = :nil
7
+ EMPTY = :empty
8
+ DISALLOWED = :disallowed
9
+ end
10
+ end
11
+
12
+ class InvalidParameters < StandardError
13
+ attr_reader :errors
14
+
15
+ def initialize(errors)
16
+ @errors = errors
17
+ end
18
+
19
+ def message
20
+ @errors.to_s
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,80 @@
1
+ module ParametersSchema
2
+ module Options
3
+ def self.reset_defaults
4
+ @@skip_parameters = [:controller, :action, :format]
5
+ @@empty_keyword = :empty
6
+ @@any_keyword = :any
7
+ @@none_keyword = :none
8
+ @@boolean_keyword = :boolean
9
+ @@nil_keyword = :nil
10
+ @@boolean_true_values = [true, 't', 'true', '1', 1, 1.0]
11
+ @@boolean_false_values = [false, 'f', 'false', '0', 0, 0.0]
12
+ end
13
+
14
+ def self.skip_parameters
15
+ @@skip_parameters
16
+ end
17
+
18
+ def self.skip_parameters=(new_value)
19
+ @@skip_parameters = new_value
20
+ end
21
+
22
+ def self.empty_keyword
23
+ @@empty_keyword
24
+ end
25
+
26
+ def self.empty_keyword=(new_value)
27
+ @@empty_keyword = new_value
28
+ end
29
+
30
+ def self.any_keyword
31
+ @@any_keyword
32
+ end
33
+
34
+ def self.any_keyword=(new_value)
35
+ @@any_keyword = new_value
36
+ end
37
+
38
+ def self.none_keyword
39
+ @@none_keyword
40
+ end
41
+
42
+ def self.none_keyword=(new_value)
43
+ @@none_keyword = new_value
44
+ end
45
+
46
+ def self.boolean_keyword
47
+ @@boolean_keyword
48
+ end
49
+
50
+ def self.boolean_keyword=(new_value)
51
+ @@boolean_keyword = new_value
52
+ end
53
+
54
+ def self.nil_keyword
55
+ @@nil_keyword
56
+ end
57
+
58
+ def self.nil_keyword=(new_value)
59
+ @@nil_keyword = new_value
60
+ end
61
+
62
+ def self.boolean_true_values
63
+ @@boolean_true_values
64
+ end
65
+
66
+ def self.boolean_true_values=(new_value)
67
+ @@boolean_true_values = new_value
68
+ end
69
+
70
+ def self.boolean_false_values
71
+ @@boolean_false_values
72
+ end
73
+
74
+ def self.boolean_false_values=(new_value)
75
+ @@boolean_false_values = new_value
76
+ end
77
+
78
+ self.reset_defaults
79
+ end
80
+ end
@@ -0,0 +1,296 @@
1
+ module ParametersSchema
2
+ class Schema
3
+ def initialize(&schema)
4
+ @schema = schema
5
+ end
6
+
7
+ def validate!(params)
8
+ # Make sure we have params we can work with.
9
+ @params = __prepare_params(params)
10
+
11
+ # Parse and validate each param.
12
+ @sanitized_params = []
13
+ instance_eval(&@schema)
14
+ # Serve the params if valid, otherwise throw exception.
15
+ __handle_errors
16
+ __serve
17
+ end
18
+
19
+ private
20
+
21
+ def param(name, options = {}, &inner_params)
22
+ options[:required] = !options.has_key?(:required) || options[:required].present?
23
+
24
+ options[:type] = [options[:type] || String].flatten
25
+ options[:allow] = [options[:allow].present? ? options[:allow] : ParametersSchema::Options.any_keyword].flatten
26
+ options[:deny] = [options[:deny].present? ? options[:deny] : ParametersSchema::Options.none_keyword].flatten
27
+
28
+ [ParametersSchema::Options.any_keyword, ParametersSchema::Options.none_keyword].each do |dominant_value|
29
+ [:allow, :deny, :type].each do |key|
30
+ options[key] = [dominant_value] if options[key].include?(dominant_value)
31
+ end
32
+ end
33
+
34
+ options[:array] = options[:array] || false
35
+ options[:parent] = options[:parent] || @params
36
+
37
+ options[:type].map! do |type|
38
+ # Limit to { key => value }
39
+ if type.kind_of?(Hash) && type.count > 1
40
+ type = { type.first[0] => type.first[1] }
41
+ end
42
+
43
+ # Limit to { Array => value }
44
+ if type.kind_of?(Hash) && type.first[0] != Array
45
+ type = type.first[0]
46
+ end
47
+
48
+ # Apply :array keyword if not already in the format { Array => value }
49
+ if options.delete(:array) && !type.kind_of?(Hash)
50
+ type = { Array => type }
51
+ end
52
+
53
+ # The format...
54
+ #
55
+ # param :potatoe do
56
+ # ...
57
+ # end
58
+ #
59
+ # ... is always an Hash.
60
+ if type.kind_of?(Hash) && inner_params.present?
61
+ type = { Array => Hash }
62
+ elsif inner_params.present?
63
+ type = Hash
64
+ end
65
+
66
+ type
67
+ end
68
+
69
+ @sanitized_params.push(__validate_param(name, options, inner_params))
70
+ end
71
+
72
+ def __prepare_params(params)
73
+ params ||= {}
74
+ params = {} unless params.kind_of?(Hash)
75
+ params = params.clone.with_indifferent_access
76
+ ParametersSchema::Options.skip_parameters.each{ |param| params.delete(param) }
77
+ params
78
+ end
79
+
80
+ def __validate_param(name, options, inner_params)
81
+ # Validate the presence of the parameter.
82
+ value, error = __validate_param_presence(name, options)
83
+ return __stop_validation(name, value, error, options) if error || (!options[:required] && value.nil?)
84
+
85
+ # Validate nil value.
86
+ value, error = __validate_param_value_nil(value, options)
87
+ return __stop_validation(name, value, error, options) if error || value.nil?
88
+
89
+ # Validate empty value (except hash).
90
+ value, error = __validate_param_value_empty(value, options)
91
+ return __stop_validation(name, value, error, options) if error || value.nil?
92
+
93
+ # Validate the type of the parameter.
94
+ [options[:type]].flatten.each do |type|
95
+ value, error = __validate_type_and_cast(value, type, options, inner_params)
96
+ break if error.blank?
97
+ end
98
+ return __stop_validation(name, value, error, options) if error || value.nil?
99
+
100
+ # Validate the allowed and denied values of the parameter
101
+ unless value.kind_of?(Array) || value.kind_of?(Hash)
102
+ [:allow, :deny].each do |allow_or_deny|
103
+ value, error = __validate_param_value_format(value, options, allow_or_deny)
104
+ return __stop_validation(name, value, error, options) if error || value.nil?
105
+ end
106
+ end
107
+
108
+ # Validate empty value for hash.
109
+ # This is done at this point to let the validation emit errors when inner parameters are missing.
110
+ # It is preferable that { key: {} } emit { key: { name: :missing } } than { key: :empty }.
111
+ value, error = __validate_param_value_hash_empty(value, options)
112
+ return __stop_validation(name, value, error, options) if error || value.nil?
113
+
114
+ __stop_validation(name, value, error, options)
115
+ end
116
+
117
+ def __validate_param_presence(name, options)
118
+ error = nil
119
+
120
+ if options[:required] && !options[:parent].has_key?(name)
121
+ error = ParametersSchema::ErrorCode::MISSING
122
+ elsif options[:parent].has_key?(name)
123
+ value = options[:parent][name]
124
+ end
125
+
126
+ [value, error]
127
+ end
128
+
129
+ def __validate_param_value_nil(value, options)
130
+ error = nil
131
+
132
+ if !options[:allow].include?(ParametersSchema::Options.nil_keyword) && value.nil?
133
+ error = ParametersSchema::ErrorCode::NIL
134
+ end
135
+
136
+ [value, error]
137
+ end
138
+
139
+ def __validate_param_value_empty(value, options)
140
+ error = nil
141
+
142
+ if !options[:allow].include?(ParametersSchema::Options.empty_keyword) && !value.kind_of?(Hash) && value.respond_to?(:empty?) && value.empty?
143
+ error = ParametersSchema::ErrorCode::EMPTY
144
+ end
145
+
146
+ [value, error]
147
+ end
148
+
149
+ def __validate_param_value_hash_empty(value, options)
150
+ error = nil
151
+
152
+ if !options[:allow].include?(ParametersSchema::Options.empty_keyword) && value.kind_of?(Hash) && value.empty?
153
+ error = ParametersSchema::ErrorCode::EMPTY
154
+ end
155
+
156
+ [value, error]
157
+ end
158
+
159
+ def __validate_param_value_format(value, options, allow_or_deny)
160
+ conditions = options[allow_or_deny] - [ParametersSchema::Options.empty_keyword, ParametersSchema::Options.nil_keyword]
161
+ inverse = allow_or_deny == :deny
162
+ accept_all_keyword = inverse ? ParametersSchema::Options.none_keyword : ParametersSchema::Options.any_keyword
163
+ refuse_all_keyword = inverse ? ParametersSchema::Options.any_keyword : ParametersSchema::Options.none_keyword
164
+
165
+ return [value, nil] if conditions.include?(accept_all_keyword)
166
+ return [value, ParametersSchema::ErrorCode::DISALLOWED] if conditions.include?(refuse_all_keyword)
167
+
168
+ error = nil
169
+
170
+ conditions.each do |condition|
171
+ error = nil
172
+
173
+ if condition.kind_of?(Range)
174
+ condition_passed = condition.include?(value)
175
+ condition_passed = !condition_passed if inverse
176
+ error = ParametersSchema::ErrorCode::DISALLOWED unless condition_passed
177
+ elsif condition.kind_of?(Regexp) && !value.kind_of?(String)
178
+ error = ParametersSchema::ErrorCode::DISALLOWED
179
+ elsif condition.kind_of?(Regexp) && value.kind_of?(String)
180
+ condition_passed = (condition =~ value).present?
181
+ condition_passed = !condition_passed if inverse
182
+ error = ParametersSchema::ErrorCode::DISALLOWED unless condition_passed
183
+ else
184
+ condition_passed = condition == value
185
+ condition_passed = !condition_passed if inverse
186
+ error = ParametersSchema::ErrorCode::DISALLOWED unless condition_passed
187
+ end
188
+
189
+ break if inverse ? error.present? : error.blank?
190
+ end
191
+
192
+ [value, error]
193
+ end
194
+
195
+ def __validate_type_and_cast(value, type, options, inner_params)
196
+ if type.kind_of?(Hash)
197
+ error = ParametersSchema::ErrorCode::DISALLOWED if !value.kind_of?(Array)
198
+ value, error = __validate_array(value, type.values.first, options, inner_params) unless error
199
+ elsif inner_params.present?
200
+ begin
201
+ inner_schema = ParametersSchema::Schema.new(&inner_params)
202
+ value = inner_schema.validate!(value)
203
+ rescue ParametersSchema::InvalidParameters => e
204
+ error = e.errors
205
+ end
206
+ elsif type == ParametersSchema::Options.boolean_keyword
207
+ value = true if ParametersSchema::Options.boolean_true_values.include?(value.kind_of?(String) ? value.downcase : value)
208
+ value = false if ParametersSchema::Options.boolean_false_values.include?(value.kind_of?(String) ? value.downcase : value)
209
+ error = ParametersSchema::ErrorCode::DISALLOWED if !value.kind_of?(TrueClass) && !value.kind_of?(FalseClass)
210
+ elsif type == Fixnum
211
+ error = ParametersSchema::ErrorCode::DISALLOWED if !value.numeric?
212
+ value = value.to_i if error.blank? # cast to right type.
213
+ elsif type == Float
214
+ error = ParametersSchema::ErrorCode::DISALLOWED if !value.numeric?
215
+ value = value.to_f if error.blank? # cast to right type.
216
+ elsif type == Regexp
217
+ error = ParametersSchema::ErrorCode::DISALLOWED unless value =~ options[:regex]
218
+ elsif type == ParametersSchema::Options.any_keyword
219
+ # No validation required.
220
+ elsif type == ParametersSchema::Options.none_keyword
221
+ # Always fail. Why would you want to do that?
222
+ error = ParametersSchema::ErrorCode::DISALLOWED
223
+ elsif type == String
224
+ error = ParametersSchema::ErrorCode::DISALLOWED unless value.kind_of?(String) || value.kind_of?(Symbol)
225
+ value = value.to_s if error.blank? # cast to right type.
226
+ elsif type == Symbol
227
+ error = ParametersSchema::ErrorCode::DISALLOWED unless value.respond_to?(:to_sym)
228
+ value = value.to_sym if error.blank? # cast to right type.
229
+ elsif type == Date
230
+ begin
231
+ value = value.kind_of?(String) ? Date.parse(value) : value.to_date
232
+ rescue
233
+ error = ParametersSchema::ErrorCode::DISALLOWED
234
+ end
235
+ elsif type == DateTime
236
+ begin
237
+ value = value.kind_of?(String) ? DateTime.parse(value) : value.to_datetime
238
+ rescue
239
+ error = ParametersSchema::ErrorCode::DISALLOWED
240
+ end
241
+ else
242
+ error = ParametersSchema::ErrorCode::DISALLOWED if !value.kind_of?(type)
243
+ end
244
+
245
+ [value, error]
246
+ end
247
+
248
+ def __validate_array(value, type, options, inner_params)
249
+ if !value.kind_of?(Array)
250
+ return [value, ParametersSchema::ErrorCode::DISALLOWED]
251
+ end
252
+
253
+ value_opts = {
254
+ required: true,
255
+ type: type,
256
+ parent: { value: nil },
257
+ allow: options[:allow],
258
+ deny: options[:deny]
259
+ }
260
+
261
+ value.map! do |v|
262
+ value_opts[:parent][:value] = v
263
+ __validate_param(:value, value_opts, inner_params)
264
+ end
265
+
266
+ # For now, take the first error.
267
+ [value.map{ |v| v[:value] }, value.find{ |v| v[:error].present? }.try(:[], :error)]
268
+ end
269
+
270
+ def __stop_validation(name, value, error, options)
271
+ { param: name, error: error, value: value, keep_if_nil: options[:allow].include?(ParametersSchema::Options.nil_keyword) }
272
+ end
273
+
274
+ def __handle_errors
275
+ errors = @sanitized_params
276
+ .select{ |p| p[:error].present? }
277
+ .each_with_object({}.with_indifferent_access) do |p, h|
278
+ h[p[:param]] = p[:error] == :nested_errors ? p[:value] : p[:error]
279
+ end
280
+
281
+ (@params.keys.map(&:to_sym) - @sanitized_params.map{ |p| p[:param] }).each do |extra_param|
282
+ errors[extra_param] = ParametersSchema::ErrorCode::UNKNOWN
283
+ end
284
+
285
+ raise ParametersSchema::InvalidParameters.new(errors) if errors.any?
286
+ end
287
+
288
+ def __serve
289
+ @sanitized_params
290
+ .reject{ |p| p[:value].nil? && !p[:keep_if_nil] }
291
+ .each_with_object({}.with_indifferent_access) do |p, h|
292
+ h[p[:param]] = p[:value]
293
+ end
294
+ end
295
+ end
296
+ end