weasel_diesel 1.0.4 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +136 -11
- data/lib/params.rb +10 -4
- data/lib/params_verification.rb +151 -78
- data/lib/weasel_diesel/version.rb +1 -1
- data/spec/params_verification_spec.rb +76 -17
- data/spec/test_services.rb +5 -3
- data/spec/wd_params_spec.rb +1 -1
- metadata +85 -122
data/README.md
CHANGED
@@ -108,7 +108,131 @@ Or a more complex example using XML:
|
|
108
108
|
end
|
109
109
|
```
|
110
110
|
|
111
|
-
##
|
111
|
+
## INPUT DSL
|
112
|
+
|
113
|
+
As shown in the two examples above, input parameters can be:
|
114
|
+
* optional or required
|
115
|
+
* namespaced
|
116
|
+
* typed
|
117
|
+
* marked as not being null if passed
|
118
|
+
* set to have a value defined in a list
|
119
|
+
* set to have a min value
|
120
|
+
* set to have a min length
|
121
|
+
* set to have a max value
|
122
|
+
* set to have a max length
|
123
|
+
* documented
|
124
|
+
|
125
|
+
Most of these settings are used to verify the input requests.
|
126
|
+
|
127
|
+
### Supported defined types:
|
128
|
+
|
129
|
+
* integer
|
130
|
+
* float, decimal
|
131
|
+
* string
|
132
|
+
* boolean
|
133
|
+
* array (comma delimited string)
|
134
|
+
* binary, file
|
135
|
+
|
136
|
+
#### Note regarding required vs optional params.
|
137
|
+
|
138
|
+
You can't set a required param to be `:null => true`, if you do so, the
|
139
|
+
setting will be ignored since all required params have to be present.
|
140
|
+
|
141
|
+
If you set an optional param to be `:null => false`, the verification
|
142
|
+
will only fail if the param was present in the request but the passed
|
143
|
+
value is nil. You might want to use that setting if you have an optional
|
144
|
+
param that, by definition isn't required but, if passed has to not be
|
145
|
+
null.
|
146
|
+
|
147
|
+
|
148
|
+
### Validation and other param options
|
149
|
+
|
150
|
+
You can set many rules to define an input parameter.
|
151
|
+
Here is a quick overview of the available param options, check the specs for more examples.
|
152
|
+
Options can be combined.
|
153
|
+
|
154
|
+
* `required` by default the defined optional input parameters are
|
155
|
+
optional. However their presence can be required by using this flag.
|
156
|
+
(Setting `:null => true` will be ignored if the paramter is required)
|
157
|
+
Example: `service.param.string :id, :required => true`
|
158
|
+
* `in` or `options` limits the range of the possible values being
|
159
|
+
passed. Example: `service.param.string :skills, :options %w{ruby scala clojure}`
|
160
|
+
* `default` sets a value for your in case you don't pass one. Example:
|
161
|
+
`service.param.datetime :timestamp, :default => Time.now`
|
162
|
+
* `min_value` forces the param value to be equal or greater than the
|
163
|
+
option's value. Example: `service.param.integer :age, :min_value => 21
|
164
|
+
* `max_value` forces the param value to be equal or less than the
|
165
|
+
options's value. Example: `service.param.integer :votes, :max_value => 7
|
166
|
+
* `min_length` forces the length of the param value to be equal or
|
167
|
+
greater than the option's value. Example: `service.param.string :name, :min_length => 2`
|
168
|
+
* `max_length` forces the length of the param value to be equal or
|
169
|
+
lesser than the options's value. Example: `service.param.string :name, :max_length => 251`
|
170
|
+
* `null` in the case of an optional parameter, if the parameter is being
|
171
|
+
passed, the value can't be nil or empty.
|
172
|
+
* `doc` document the param.
|
173
|
+
|
174
|
+
### Namespaced/nested object
|
175
|
+
|
176
|
+
Input parameters can be defined nested/namespaced.
|
177
|
+
This is particuliarly frequent when using Rails for instance.
|
178
|
+
|
179
|
+
```ruby
|
180
|
+
service.params do |param|
|
181
|
+
param.string :framework,
|
182
|
+
:in => ['RSpec', 'Bacon'],
|
183
|
+
:required => true,
|
184
|
+
:doc => "The test framework used, could be one of the two following: #{WeaselDieselSpecOptions.join(", ")}."
|
185
|
+
|
186
|
+
param.datetime :timestamp, :default => Time.now
|
187
|
+
param.string :alpha, :in => ['a', 'b', 'c']
|
188
|
+
param.string :version, :null => false, :doc => "The version of the framework to use."
|
189
|
+
param.integer :num, :min_value => 42, :max_value => 1000, :doc => "The number to test"
|
190
|
+
param.string :name, :min_length => 5, :max_length => 25
|
191
|
+
end
|
192
|
+
|
193
|
+
service.params.namespace :user do |user|
|
194
|
+
user.integer :id, :required => :true
|
195
|
+
user.string :sex, :in => %Q{female, male}
|
196
|
+
user.boolean :mailing_list, :default => true, :doc => "is the user subscribed to the ML?"
|
197
|
+
user.array :skills, :in => %w{ruby js cooking}
|
198
|
+
end
|
199
|
+
```
|
200
|
+
|
201
|
+
|
202
|
+
|
203
|
+
Here is the same type of input but this time using a JSON jargon,
|
204
|
+
`namespace` and `object` are aliases and can therefore can be used based
|
205
|
+
on how the input type.
|
206
|
+
|
207
|
+
```ruby
|
208
|
+
# INPUT using 1.9 hash syntax
|
209
|
+
service.params do |param|
|
210
|
+
param.integer :playlist_id,
|
211
|
+
doc: "The ID of the playlist to which the track belongs.",
|
212
|
+
required: true
|
213
|
+
param.object :track do |track|
|
214
|
+
track.string :title,
|
215
|
+
doc: "The title of the track.",
|
216
|
+
required: true
|
217
|
+
track.string :album_title,
|
218
|
+
doc: "The title of the album to which the track belongs.",
|
219
|
+
required: true
|
220
|
+
track.string :artist_name,
|
221
|
+
doc: "The name of the track's artist.",
|
222
|
+
required: true
|
223
|
+
track.string :rdio_id,
|
224
|
+
doc: "The Rdio ID of the track.",
|
225
|
+
required: true
|
226
|
+
end
|
227
|
+
end
|
228
|
+
```
|
229
|
+
|
230
|
+
|
231
|
+
|
232
|
+
## OUTPUT DSL
|
233
|
+
|
234
|
+
|
235
|
+
### JSON API example
|
112
236
|
|
113
237
|
Consider the following JSON response:
|
114
238
|
|
@@ -164,28 +288,31 @@ JSON response validation can be done using an optional module as shown in
|
|
164
288
|
The goal of this module is to help automate API testing by
|
165
289
|
validating the data structure of the returned object.
|
166
290
|
|
291
|
+
Another simple examples:
|
167
292
|
|
168
|
-
|
169
|
-
|
293
|
+
Actual output:
|
170
294
|
```
|
171
295
|
{"organization": {"name": "Example"}}
|
172
296
|
```
|
173
297
|
|
298
|
+
Output DSL:
|
174
299
|
``` Ruby
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
end
|
300
|
+
describe_service "example" do |service|
|
301
|
+
service.formats :json
|
302
|
+
service.response do |response|
|
303
|
+
response.object :organization do |node|
|
304
|
+
node.string :name
|
181
305
|
end
|
182
306
|
end
|
307
|
+
end
|
183
308
|
```
|
184
309
|
|
310
|
+
Actual output:
|
185
311
|
```
|
186
312
|
{"name": "Example"}
|
187
313
|
```
|
188
314
|
|
315
|
+
Output DSL:
|
189
316
|
``` Ruby
|
190
317
|
describe_service "example" do |service|
|
191
318
|
service.formats :json
|
@@ -198,8 +325,6 @@ end
|
|
198
325
|
```
|
199
326
|
|
200
327
|
|
201
|
-
|
202
|
-
|
203
328
|
## Test Suite & Dependencies
|
204
329
|
|
205
330
|
The test suite requires Ruby 1.9.* along with `RSpec`, `Rack`, and `Sinatra` gems.
|
data/lib/params.rb
CHANGED
@@ -22,8 +22,11 @@ class WeaselDiesel
|
|
22
22
|
# @option options [Symbol] :in A list of acceptable values.
|
23
23
|
# @option options [Symbol] :options A list of acceptable values.
|
24
24
|
# @option options [Symbol] :default The default value of the param.
|
25
|
-
# @option options [Symbol] :
|
26
|
-
# @option options [Symbol] :
|
25
|
+
# @option options [Symbol] :min_value The minimum acceptable value.
|
26
|
+
# @option options [Symbol] :max_value The maximum acceptable value.
|
27
|
+
# @option options [Symbol] :min_length The minimum acceptable string length.
|
28
|
+
# @option options [Symbol] :max_length The maximum acceptable string length.
|
29
|
+
# @option options [Boolean] :null Can this value be null?
|
27
30
|
# @option options [Symbol] :doc Documentation for the param.
|
28
31
|
# @api public
|
29
32
|
attr_reader :options
|
@@ -34,8 +37,11 @@ class WeaselDiesel
|
|
34
37
|
# @option opts [Symbol] :in A list of acceptable values.
|
35
38
|
# @option opts [Symbol] :options A list of acceptable values.
|
36
39
|
# @option opts [Symbol] :default The default value of the param.
|
37
|
-
# @option
|
38
|
-
# @option
|
40
|
+
# @option options [Symbol] :min_value The minimum acceptable value.
|
41
|
+
# @option options [Symbol] :max_value The maximum acceptable value.
|
42
|
+
# @option options [Symbol] :min_length The minimum acceptable string length.
|
43
|
+
# @option options [Symbol] :max_length The maximum acceptable string length.
|
44
|
+
# @option options [Boolean] :null Can this value be null?
|
39
45
|
# @option opts [Symbol] :doc Documentation for the param.
|
40
46
|
# @api public
|
41
47
|
def initialize(name, opts = {})
|
data/lib/params_verification.rb
CHANGED
@@ -61,7 +61,7 @@ module ParamsVerification
|
|
61
61
|
|
62
62
|
# Set optional defaults if any optional
|
63
63
|
service_params.list_optional.each do |rule|
|
64
|
-
updated_params =
|
64
|
+
updated_params = validate_optional_rule(rule, updated_params)
|
65
65
|
end
|
66
66
|
|
67
67
|
# check the namespaced params
|
@@ -70,9 +70,8 @@ module ParamsVerification
|
|
70
70
|
updated_params = validate_required_rule(rule, updated_params, param.space_name.to_s)
|
71
71
|
end
|
72
72
|
param.list_optional.each do |rule|
|
73
|
-
updated_params =
|
73
|
+
updated_params = validate_optional_rule(rule, updated_params, param.space_name.to_s)
|
74
74
|
end
|
75
|
-
|
76
75
|
end
|
77
76
|
|
78
77
|
# verify nested params, only 1 level deep tho
|
@@ -90,7 +89,7 @@ module ParamsVerification
|
|
90
89
|
|
91
90
|
private
|
92
91
|
|
93
|
-
#
|
92
|
+
# Validates a required rule against a list of params passed.
|
94
93
|
#
|
95
94
|
#
|
96
95
|
# @param [WeaselDiesel::Params::Rule] rule The required rule to check against.
|
@@ -103,56 +102,145 @@ module ParamsVerification
|
|
103
102
|
# @api private
|
104
103
|
def self.validate_required_rule(rule, params, namespace=nil)
|
105
104
|
param_name = rule.name.to_s
|
106
|
-
|
107
105
|
param_value, namespaced_params = extract_param_values(params, param_name, namespace)
|
108
|
-
# puts "verify #{param_name} params, current value: #{param_value}"
|
109
|
-
|
110
|
-
#This is disabled since required params shouldn't have a default, otherwise, why are they required?
|
111
|
-
#if param_value.nil? && rule.options && rule.options[:default]
|
112
|
-
#param_value = rule.options[:default]
|
113
|
-
#end
|
114
106
|
|
115
107
|
# Checks presence
|
116
108
|
if !(namespaced_params || params).keys.include?(param_name)
|
117
109
|
raise MissingParam, "'#{rule.name}' is missing - passed params: #{params.inspect}."
|
118
|
-
# checks null
|
119
|
-
elsif param_value.nil? && !rule.options[:null]
|
120
|
-
raise InvalidParamValue, "Value for parameter '#{param_name}' is missing - passed params: #{params.inspect}."
|
121
|
-
# checks type
|
122
|
-
elsif rule.options[:type]
|
123
|
-
verify_cast(param_name, param_value, rule.options[:type])
|
124
110
|
end
|
125
111
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
112
|
+
updated_param_value, updated_params = validate_and_cast_type(param_value, param_name, rule.options[:type], params, namespace)
|
113
|
+
|
114
|
+
# check for nulls in params that don't allow them
|
115
|
+
if !valid_null_param?(param_name, updated_param_value, rule)
|
116
|
+
raise InvalidParamValue, "Value for parameter '#{param_name}' cannot be null - passed params: #{updated_params.inspect}."
|
117
|
+
elsif updated_param_value
|
118
|
+
value_errors = validate_ruled_param_value(param_name, updated_param_value, rule)
|
119
|
+
raise InvalidParamValue, value_errors.join(', ') if value_errors
|
120
|
+
end
|
121
|
+
|
122
|
+
updated_params
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
# Validates that an optional rule is respected.
|
127
|
+
# If the rule contains default values, the params might be updated.
|
128
|
+
#
|
129
|
+
# @param [#WeaselDiesel::Params::Rule] rule The optional rule
|
130
|
+
# @param [Hash] params The request params
|
131
|
+
# @param [String] namespace An optional namespace
|
132
|
+
#
|
133
|
+
# @return [Hash] The potentially modified params
|
134
|
+
#
|
135
|
+
# @api private
|
136
|
+
def self.validate_optional_rule(rule, params, namespace=nil)
|
137
|
+
param_name = rule.name.to_s
|
138
|
+
param_value, namespaced_params = extract_param_values(params, param_name, namespace)
|
139
|
+
|
140
|
+
if param_value && !valid_null_param?(param_name, param_value, rule)
|
141
|
+
raise InvalidParamValue, "Value for parameter '#{param_name}' cannot be null if passed - passed params: #{params.inspect}."
|
142
|
+
end
|
143
|
+
|
144
|
+
# Use a default value if one is available and the submitted param value is nil
|
145
|
+
if param_value.nil? && rule.options[:default]
|
146
|
+
param_value = rule.options[:default]
|
147
|
+
if namespace
|
148
|
+
params[namespace] ||= {}
|
149
|
+
params[namespace][param_name] = param_value
|
150
|
+
else
|
151
|
+
params[param_name] = param_value
|
131
152
|
end
|
132
|
-
raise InvalidParamValue, "Value for parameter '#{param_name}' (#{param_value}) is not in the allowed set of values." unless choices.include?(param_value)
|
133
|
-
# You can have a "in" rule that also applies a min value since they are mutually exclusive
|
134
|
-
elsif rule.options[:minvalue]
|
135
|
-
min = rule.options[:minvalue]
|
136
|
-
raise InvalidParamValue, "Value for parameter '#{param_name}' is lower than the min accepted value (#{min})." if param_value.to_i < min
|
137
153
|
end
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
154
|
+
|
155
|
+
updated_param_value, updated_params = validate_and_cast_type(param_value, param_name, rule.options[:type], params, namespace)
|
156
|
+
value_errors = validate_ruled_param_value(param_name, updated_param_value, rule) if updated_param_value
|
157
|
+
raise InvalidParamValue, value_errors.join(', ') if value_errors
|
158
|
+
|
159
|
+
updated_params
|
160
|
+
end
|
161
|
+
|
162
|
+
|
163
|
+
# Validates the param value against the rule and cast the param in the appropriate type.
|
164
|
+
# The modified params containing the cast value is returned along the cast param value.
|
165
|
+
#
|
166
|
+
# @param [Object] param_value The value to validate and cast.
|
167
|
+
# @param [String] param_name The name of the param we are validating.
|
168
|
+
# @param [Symbol] type The expected object type.
|
169
|
+
# @param [Hash] params The params that might need to be updated.
|
170
|
+
# @param [String, Symbol] namespace The optional namespace used to access the `param_value`
|
171
|
+
#
|
172
|
+
# @return [Array<Object, Hash>] An array containing the param value and
|
173
|
+
# a hash representing the potentially modified params after going through the filter.
|
174
|
+
#
|
175
|
+
def self.validate_and_cast_type(param_value, param_name, rule_type, params, namespace=nil)
|
176
|
+
# checks type & modifies params if needed
|
177
|
+
if rule_type
|
178
|
+
verify_cast(param_name, param_value, rule_type)
|
179
|
+
param_value = type_cast_value(rule_type, param_value)
|
180
|
+
# update the params hash with the type cast value
|
181
|
+
if namespace
|
182
|
+
params[namespace] ||= {}
|
183
|
+
params[namespace][param_name] = param_value
|
184
|
+
else
|
185
|
+
params[param_name] = param_value
|
186
|
+
end
|
144
187
|
end
|
145
|
-
params
|
188
|
+
[param_value, params]
|
146
189
|
end
|
147
190
|
|
148
191
|
|
149
|
-
#
|
192
|
+
# Validates a value against a few rule options.
|
193
|
+
#
|
194
|
+
# @return [NilClass, Array<String>] Returns an array of error messages if an option didn't validate.
|
195
|
+
def self.validate_ruled_param_value(param_name, param_value, rule)
|
196
|
+
|
197
|
+
# checks the value against a whitelist style 'in'/'options' list
|
198
|
+
if rule.options[:options] || rule.options[:in]
|
199
|
+
choices = rule.options[:options] || rule.options[:in]
|
200
|
+
unless param_value.is_a?(Array) ? (param_value & choices == param_value) : choices.include?(param_value)
|
201
|
+
errors ||= []
|
202
|
+
errors << "Value for parameter '#{param_name}' (#{param_value}) is not in the allowed set of values."
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# enforces a minimum numeric value
|
207
|
+
if rule.options[:min_value]
|
208
|
+
min = rule.options[:min_value]
|
209
|
+
errors ||= []
|
210
|
+
errors << "Value for parameter '#{param_name}' is lower than the min accepted value (#{min})." if param_value.to_i < min
|
211
|
+
end
|
212
|
+
|
213
|
+
# enforces a maximum numeric value
|
214
|
+
if rule.options[:max_value]
|
215
|
+
max = rule.options[:max_value]
|
216
|
+
errors ||= []
|
217
|
+
errors << "Value for parameter '#{param_name}' is higher than the max accepted value (#{max})." if param_value.to_i > max
|
218
|
+
end
|
219
|
+
|
220
|
+
# enforces a minimum string length
|
221
|
+
if rule.options[:min_length]
|
222
|
+
min = rule.options[:min_length]
|
223
|
+
errors ||= []
|
224
|
+
errors << "Length of parameter '#{param_name}' is shorter than the min accepted value (#{min})." if param_value.to_s.length < min
|
225
|
+
end
|
226
|
+
|
227
|
+
# enforces a maximum string length
|
228
|
+
if rule.options[:max_length]
|
229
|
+
max = rule.options[:max_length]
|
230
|
+
errors ||= []
|
231
|
+
errors << "Length of parameter '#{param_name}' is longer than the max accepted value (#{max})." if param_value.to_s.length > max
|
232
|
+
end
|
233
|
+
|
234
|
+
errors
|
235
|
+
end
|
236
|
+
|
237
|
+
# Extract the param value and the namespaced params
|
150
238
|
# based on a passed namespace and params
|
151
239
|
#
|
152
240
|
# @param [Hash] params The passed params to extract info from.
|
153
241
|
# @param [String] param_name The param name to find the value.
|
154
242
|
# @param [NilClass, String] namespace the params' namespace.
|
155
|
-
# @return [
|
243
|
+
# @return [Array<Object, String>]
|
156
244
|
#
|
157
245
|
# @api private
|
158
246
|
def self.extract_param_values(params, param_name, namespace=nil)
|
@@ -170,41 +258,6 @@ module ParamsVerification
|
|
170
258
|
end
|
171
259
|
end
|
172
260
|
|
173
|
-
# @param [#WeaselDiesel::Params::Rule] rule The optional rule
|
174
|
-
# @param [Hash] params The request params
|
175
|
-
# @param [String] namespace An optional namespace
|
176
|
-
# @return [Hash] The potentially modified params
|
177
|
-
#
|
178
|
-
# @api private
|
179
|
-
def self.run_optional_rule(rule, params, namespace=nil)
|
180
|
-
param_name = rule.name.to_s
|
181
|
-
|
182
|
-
param_value, namespaced_params = extract_param_values(params, param_name, namespace)
|
183
|
-
|
184
|
-
if param_value.nil? && rule.options[:default]
|
185
|
-
if namespace
|
186
|
-
params[namespace][param_name] = param_value = rule.options[:default]
|
187
|
-
else
|
188
|
-
params[param_name] = param_value = rule.options[:default]
|
189
|
-
end
|
190
|
-
end
|
191
|
-
|
192
|
-
# cast the type if a type is defined and if a range of options isn't defined since the casting should have been done already
|
193
|
-
if rule.options[:type] && !param_value.nil?
|
194
|
-
if namespace
|
195
|
-
params[namespace][param_name] = param_value = type_cast_value(rule.options[:type], param_value)
|
196
|
-
else
|
197
|
-
params[param_name] = param_value = type_cast_value(rule.options[:type], param_value)
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
choices = rule.options[:options] || rule.options[:in]
|
202
|
-
if choices && param_value && !choices.include?(param_value)
|
203
|
-
raise InvalidParamValue, "Value for parameter '#{param_name}' (#{param_value}) is not in the allowed set of values."
|
204
|
-
end
|
205
|
-
|
206
|
-
params
|
207
|
-
end
|
208
261
|
|
209
262
|
def self.unexpected_params?(params, param_names)
|
210
263
|
# Raise an exception unless no unexpected params were found
|
@@ -216,6 +269,7 @@ module ParamsVerification
|
|
216
269
|
|
217
270
|
|
218
271
|
def self.type_cast_value(type, value)
|
272
|
+
return value if value == nil
|
219
273
|
case type
|
220
274
|
when :integer
|
221
275
|
value.to_i
|
@@ -241,30 +295,49 @@ module ParamsVerification
|
|
241
295
|
# An array type is a comma delimited string, we need to cast the passed strings.
|
242
296
|
when :array
|
243
297
|
value.respond_to?(:split) ? value.split(',') : value
|
244
|
-
when :binary, :
|
298
|
+
when :binary, :file
|
245
299
|
value
|
246
300
|
else
|
247
301
|
value
|
248
302
|
end
|
249
303
|
end
|
250
304
|
|
251
|
-
# Checks that the value's type matches the expected type for a given param
|
305
|
+
# Checks that the value's type matches the expected type for a given param. If a nil value is passed
|
306
|
+
# the verification is skipped.
|
252
307
|
#
|
253
308
|
# @param [Symbol, String] Param name used if the verification fails and that an error is raised.
|
254
|
-
# @param [#to_s] The value to validate.
|
309
|
+
# @param [NilClass, #to_s] The value to validate.
|
255
310
|
# @param [Symbol] The expected type, such as :boolean, :integer etc...
|
256
311
|
# @raise [InvalidParamType] Custom exception raised when the validation isn't found or the value doesn't match.
|
257
312
|
#
|
258
|
-
# @return [
|
313
|
+
# @return [NilClass]
|
259
314
|
# @api public
|
260
|
-
# TODO raising an exception really isn't a good idea since it forces the stack to unwind.
|
261
|
-
# More than likely developers are using exceptions to control the code flow and a different approach should be used.
|
262
|
-
# Catch/throw is a bit more efficient but is still the wrong approach for this specific problem.
|
263
315
|
def self.verify_cast(name, value, expected_type)
|
316
|
+
return if value == nil
|
264
317
|
validation = ParamsVerification.type_validations[expected_type.to_sym]
|
265
318
|
unless validation.nil? || value.to_s =~ validation
|
266
319
|
raise InvalidParamType, "Value for parameter '#{name}' (#{value}) is of the wrong type (expected #{expected_type})"
|
267
320
|
end
|
268
321
|
end
|
322
|
+
|
323
|
+
# Checks that a param explicitly set to not be null is present.
|
324
|
+
# if 'null' is found in the ruleset and set to 'false' (default is 'true' to allow null),
|
325
|
+
# then confirm that the submitted value isn't nil or empty
|
326
|
+
# @param [String] param_name The name of the param to verify.
|
327
|
+
# @param [NilClass, String, Integer, TrueClass, FalseClass] param_value The value to check.
|
328
|
+
# @param [WeaselDiesel::Params::Rule] rule The rule to check against.
|
329
|
+
#
|
330
|
+
# @return [Boolean] true if the param is valid, false otherwise
|
331
|
+
def self.valid_null_param?(param_name, param_value, rule)
|
332
|
+
if rule.options.has_key?(:null) && rule.options[:null] == false
|
333
|
+
if rule.options[:type] && rule.options[:type] == :array
|
334
|
+
return false if param_value.nil? || (param_value.respond_to?(:split) && param_value.split(',').empty?)
|
335
|
+
else
|
336
|
+
return false if param_value.nil? || param_value == ''
|
337
|
+
end
|
338
|
+
end
|
339
|
+
true
|
340
|
+
end
|
341
|
+
|
269
342
|
|
270
343
|
end
|
@@ -5,37 +5,67 @@ describe ParamsVerification do
|
|
5
5
|
before :all do
|
6
6
|
@service = WSList.all.find{|s| s.url == 'services/test.xml'}
|
7
7
|
@service.should_not be_nil
|
8
|
-
@valid_params = {'framework' => 'RSpec', 'version' => '1.02', 'user' => {'id' => '123'}}
|
8
|
+
@valid_params = {'framework' => 'RSpec', 'version' => '1.02', 'user' => {'id' => '123', 'groups' => 'manager,developer', 'skills' => 'java,ruby'}}
|
9
|
+
end
|
10
|
+
|
11
|
+
def copy(params)
|
12
|
+
Marshal.load( Marshal.dump(params) )
|
9
13
|
end
|
10
14
|
|
11
15
|
it "should validate valid params" do
|
12
|
-
params = @valid_params
|
16
|
+
params = copy(@valid_params)
|
13
17
|
lambda{ ParamsVerification.validate!(params, @service.defined_params) }.should_not raise_exception
|
14
18
|
end
|
15
19
|
|
16
20
|
it "should return the params" do
|
17
|
-
params = @valid_params
|
21
|
+
params = copy(@valid_params)
|
18
22
|
returned_params = ParamsVerification.validate!(params, @service.defined_params)
|
19
23
|
returned_params.should be_an_instance_of(Hash)
|
20
24
|
returned_params.keys.size.should >= 3
|
21
25
|
end
|
22
26
|
|
27
|
+
it "should return array in the params" do
|
28
|
+
params = copy(@valid_params)
|
29
|
+
returned_params = ParamsVerification.validate!(params, @service.defined_params)
|
30
|
+
returned_params['user']['groups'].should be == @valid_params['user']['groups'].split(",")
|
31
|
+
returned_params['user']['skills'].should be == @valid_params['user']['skills'].split(",")
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should not duplicate params in the root level" do
|
35
|
+
params = copy(@valid_params)
|
36
|
+
returned_params = ParamsVerification.validate!(params, @service.defined_params)
|
37
|
+
returned_params['groups'].should be_nil
|
38
|
+
returned_params['skills'].should be_nil
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should raise an exception when values of required param are not in the allowed list" do
|
42
|
+
params = copy(@valid_params)
|
43
|
+
params['user']['groups'] = 'admin,root,manager'
|
44
|
+
lambda { ParamsVerification.validate!(params, @service.defined_params) }.should raise_error(ParamsVerification::InvalidParamValue)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should raise an exception when values of optional param are not in the allowed list" do
|
48
|
+
params = copy(@valid_params)
|
49
|
+
params['user']['skills'] = 'ruby,java,php'
|
50
|
+
lambda { ParamsVerification.validate!(params, @service.defined_params) }.should raise_error(ParamsVerification::InvalidParamValue)
|
51
|
+
end
|
52
|
+
|
23
53
|
it "should set the default value for an optional param" do
|
24
|
-
params = @valid_params
|
54
|
+
params = copy(@valid_params)
|
25
55
|
params['timestamp'].should be_nil
|
26
56
|
returned_params = ParamsVerification.validate!(params, @service.defined_params)
|
27
57
|
returned_params['timestamp'].should_not be_nil
|
28
58
|
end
|
29
59
|
|
30
60
|
it "should set the default value for a namespace optional param" do
|
31
|
-
params =
|
61
|
+
params = copy(@valid_params)
|
32
62
|
params['user']['mailing_list'].should be_nil
|
33
63
|
returned_params = ParamsVerification.validate!(params, @service.defined_params)
|
34
64
|
returned_params['user']['mailing_list'].should be_true
|
35
65
|
end
|
36
66
|
|
37
67
|
it "should raise an exception when a required param is missing" do
|
38
|
-
params = @valid_params
|
68
|
+
params = copy(@valid_params)
|
39
69
|
params.delete('framework')
|
40
70
|
lambda{ ParamsVerification.validate!(params, @service.defined_params) }.should raise_exception(ParamsVerification::MissingParam)
|
41
71
|
end
|
@@ -55,27 +85,47 @@ describe ParamsVerification do
|
|
55
85
|
end
|
56
86
|
|
57
87
|
it "should raise an exception when a param is of the wrong type" do
|
58
|
-
params = @valid_params
|
88
|
+
params = copy(@valid_params)
|
59
89
|
params['user']['id'] = 'abc'
|
60
90
|
lambda{ ParamsVerification.validate!(params, @service.defined_params) }.should raise_exception(ParamsVerification::InvalidParamType)
|
61
91
|
end
|
62
92
|
|
63
|
-
it "should raise an exception when a param is under the
|
64
|
-
params = @valid_params
|
93
|
+
it "should raise an exception when a param is under the min_value" do
|
94
|
+
params = copy(@valid_params)
|
95
|
+
params['num'] = '1'
|
96
|
+
lambda{ ParamsVerification.validate!(params, @service.defined_params) }.should raise_exception(ParamsVerification::InvalidParamValue)
|
65
97
|
params['num'] = 1
|
66
|
-
lambda{ ParamsVerification.validate!(params, @service.defined_params) }.should raise_exception(ParamsVerification::
|
98
|
+
lambda{ ParamsVerification.validate!(params, @service.defined_params) }.should raise_exception(ParamsVerification::InvalidParamValue)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should raise an exception when a param is over the max_value" do
|
102
|
+
params = copy(@valid_params)
|
103
|
+
params['num'] = 10_000
|
104
|
+
lambda{ ParamsVerification.validate!(params, @service.defined_params) }.should raise_exception(ParamsVerification::InvalidParamValue)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should raise an exception when a param is under the min_length" do
|
108
|
+
params = copy(@valid_params)
|
109
|
+
params['name'] ='bob'
|
110
|
+
lambda{ ParamsVerification.validate!(params, @service.defined_params) }.should raise_exception(ParamsVerification::InvalidParamValue)
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should raise an exception when a param is over the max_length" do
|
114
|
+
params = copy(@valid_params)
|
115
|
+
params['name'] = "Whether 'tis nobler in the mind to suffer The slings and arrows of outrageous fortune"
|
116
|
+
lambda{ ParamsVerification.validate!(params, @service.defined_params) }.should raise_exception(ParamsVerification::InvalidParamValue)
|
67
117
|
end
|
68
118
|
|
69
119
|
it "should raise an exception when a param isn't in the param option list" do
|
70
|
-
params = @valid_params
|
120
|
+
params = copy(@valid_params)
|
71
121
|
params['alpha'] = 'z'
|
72
122
|
lambda{ ParamsVerification.validate!(params, @service.defined_params) }.should raise_exception(ParamsVerification::InvalidParamValue)
|
73
123
|
end
|
74
124
|
|
75
125
|
it "should raise an exception when a nested optional param isn't in the param option list" do
|
76
|
-
params = @valid_params
|
126
|
+
params = copy(@valid_params)
|
77
127
|
params['user']['sex'] = 'large'
|
78
|
-
lambda{ ParamsVerification.validate!(params, @service.defined_params) }.should raise_exception(ParamsVerification::
|
128
|
+
lambda{ ParamsVerification.validate!(params, @service.defined_params) }.should raise_exception(ParamsVerification::InvalidParamValue)
|
79
129
|
# other service
|
80
130
|
params = {'preference' => {'region_code' => 'us', 'language_code' => 'de'}}
|
81
131
|
service = WSList.all.find{|s| s.url == 'preferences.xml'}
|
@@ -91,25 +141,34 @@ describe ParamsVerification do
|
|
91
141
|
lambda{ ParamsVerification.validate!(params, service.defined_params) }.should raise_exception(ParamsVerification::InvalidParamValue)
|
92
142
|
end
|
93
143
|
|
94
|
-
|
95
144
|
it "should validate that no params are passed when accept_no_params! is set on a service" do
|
96
145
|
service = WSList.all.find{|s| s.url == "services/test_no_params.xml"}
|
97
146
|
service.should_not be_nil
|
98
|
-
params = @valid_params
|
147
|
+
params = copy(@valid_params)
|
99
148
|
lambda{ ParamsVerification.validate!(params, service.defined_params) }.should raise_exception
|
100
149
|
end
|
101
150
|
|
102
151
|
it "should raise an exception when an unexpected param is found" do
|
103
|
-
params = @valid_params
|
152
|
+
params = copy(@valid_params)
|
104
153
|
params['attack'] = true
|
105
154
|
lambda{ ParamsVerification.validate!(params, @service.defined_params) }.should raise_exception(ParamsVerification::UnexpectedParam)
|
106
155
|
end
|
107
156
|
|
108
157
|
it "should prevent XSS attack on unexpected param name being listed in the exception message" do
|
109
|
-
params = @valid_params
|
158
|
+
params = copy(@valid_params)
|
110
159
|
params["7e22c<script>alert('xss vulnerability')</script>e88ff3f0952"] = 1
|
111
160
|
escaped_error_message = /7e22c<script>alert\('xss vulnerability'\)<\/script>e88ff3f0952/
|
112
161
|
lambda{ ParamsVerification.validate!(params, @service.defined_params) }.should raise_exception(ParamsVerification::UnexpectedParam, escaped_error_message)
|
113
162
|
end
|
163
|
+
|
164
|
+
it "should make sure that optional params marked as not false are being set" do
|
165
|
+
params = copy(@valid_params)
|
166
|
+
ParamsVerification.validate!(params, @service.defined_params).should be_true
|
167
|
+
params.delete('version')
|
168
|
+
# if omitted, the param should not raise an exception
|
169
|
+
ParamsVerification.validate!(params, @service.defined_params).should be_true
|
170
|
+
params['version'] = ''
|
171
|
+
lambda{ ParamsVerification.validate!(params, @service.defined_params) }.should raise_exception(ParamsVerification::InvalidParamValue)
|
172
|
+
end
|
114
173
|
|
115
174
|
end
|
data/spec/test_services.rb
CHANGED
@@ -16,8 +16,8 @@ describe_service "services/test.xml" do |service|
|
|
16
16
|
p.datetime :timestamp, :default => Time.now
|
17
17
|
p.string :alpha, :in => ['a', 'b', 'c']
|
18
18
|
p.string :version, :null => false, :doc => "The version of the framework to use."
|
19
|
-
p.integer :num, :
|
20
|
-
|
19
|
+
p.integer :num, :min_value => 42, :max_value => 1000, :doc => "The number to test"
|
20
|
+
p.string :name, :min_length => 5, :max_length => 25
|
21
21
|
end
|
22
22
|
|
23
23
|
# service.param :delta, :optional => true, :type => 'float'
|
@@ -26,8 +26,10 @@ describe_service "services/test.xml" do |service|
|
|
26
26
|
|
27
27
|
service.params.namespace :user do |user|
|
28
28
|
user.integer :id, :required => :true
|
29
|
-
user.string
|
29
|
+
user.string :sex, :in => %Q{female, male}
|
30
30
|
user.boolean :mailing_list, :default => true, :doc => "is the user subscribed to the ML?"
|
31
|
+
user.array :groups, :required => true, :in => %w{developer admin manager}
|
32
|
+
user.array :skills, :in => %w{ruby java networking}
|
31
33
|
end
|
32
34
|
|
33
35
|
# the response contains a list of player creation ratings each object in the list
|
data/spec/wd_params_spec.rb
CHANGED
@@ -21,7 +21,7 @@ describe WeaselDiesel::Params do
|
|
21
21
|
|
22
22
|
it "should have a list of optional param rules" do
|
23
23
|
@sparams.list_optional.should be_an_instance_of(Array)
|
24
|
-
@sparams.list_optional.length.should ==
|
24
|
+
@sparams.list_optional.length.should == 5
|
25
25
|
end
|
26
26
|
|
27
27
|
it "should have a list of namespaced param rules" do
|
metadata
CHANGED
@@ -1,131 +1,103 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: weasel_diesel
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.0
|
5
5
|
prerelease:
|
6
|
-
segments:
|
7
|
-
- 1
|
8
|
-
- 0
|
9
|
-
- 4
|
10
|
-
version: 1.0.4
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- Matt Aimonetti
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
dependencies:
|
21
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2012-07-18 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
22
15
|
name: rspec
|
23
|
-
|
24
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
25
17
|
none: false
|
26
|
-
requirements:
|
27
|
-
- -
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
|
30
|
-
segments:
|
31
|
-
- 0
|
32
|
-
version: "0"
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
33
22
|
type: :development
|
34
|
-
version_requirements: *id001
|
35
|
-
- !ruby/object:Gem::Dependency
|
36
|
-
name: rack-test
|
37
23
|
prerelease: false
|
38
|
-
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
25
|
none: false
|
40
|
-
requirements:
|
41
|
-
- -
|
42
|
-
- !ruby/object:Gem::Version
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
type: :development
|
48
|
-
version_requirements: *id002
|
49
|
-
- !ruby/object:Gem::Dependency
|
50
|
-
name: yard
|
51
|
-
prerelease: false
|
52
|
-
requirement: &id003 !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rack-test
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
53
33
|
none: false
|
54
|
-
requirements:
|
55
|
-
- -
|
56
|
-
- !ruby/object:Gem::Version
|
57
|
-
|
58
|
-
segments:
|
59
|
-
- 0
|
60
|
-
version: "0"
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
61
38
|
type: :development
|
62
|
-
version_requirements: *id003
|
63
|
-
- !ruby/object:Gem::Dependency
|
64
|
-
name: sinatra
|
65
39
|
prerelease: false
|
66
|
-
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
67
41
|
none: false
|
68
|
-
requirements:
|
69
|
-
- -
|
70
|
-
- !ruby/object:Gem::Version
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: yard
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
75
54
|
type: :development
|
76
|
-
version_requirements: *id004
|
77
|
-
- !ruby/object:Gem::Dependency
|
78
|
-
name: rake
|
79
55
|
prerelease: false
|
80
|
-
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: sinatra
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
81
65
|
none: false
|
82
|
-
requirements:
|
83
|
-
- -
|
84
|
-
- !ruby/object:Gem::Version
|
85
|
-
|
86
|
-
segments:
|
87
|
-
- 0
|
88
|
-
version: "0"
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
89
70
|
type: :development
|
90
|
-
version_requirements: *id005
|
91
|
-
- !ruby/object:Gem::Dependency
|
92
|
-
name: backports
|
93
71
|
prerelease: false
|
94
|
-
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
95
73
|
none: false
|
96
|
-
requirements:
|
97
|
-
- -
|
98
|
-
- !ruby/object:Gem::Version
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
- !
|
106
|
-
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rake
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
107
87
|
prerelease: false
|
108
|
-
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
109
89
|
none: false
|
110
|
-
requirements:
|
111
|
-
- -
|
112
|
-
- !ruby/object:Gem::Version
|
113
|
-
|
114
|
-
segments:
|
115
|
-
- 0
|
116
|
-
version: "0"
|
117
|
-
type: :runtime
|
118
|
-
version_requirements: *id007
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
119
94
|
description: Ruby DSL describing Web Services without implementation details.
|
120
|
-
email:
|
95
|
+
email:
|
121
96
|
- mattaimonetti@gmail.com
|
122
97
|
executables: []
|
123
|
-
|
124
98
|
extensions: []
|
125
|
-
|
126
99
|
extra_rdoc_files: []
|
127
|
-
|
128
|
-
files:
|
100
|
+
files:
|
129
101
|
- .gitignore
|
130
102
|
- .travis.yml
|
131
103
|
- Gemfile
|
@@ -158,41 +130,31 @@ files:
|
|
158
130
|
- spec/ws_list_spec.rb
|
159
131
|
- spec/wsdsl_sinatra_ext_spec.rb
|
160
132
|
- weasel_diesel.gemspec
|
161
|
-
has_rdoc: true
|
162
133
|
homepage: https://github.com/mattetti/Weasel-Diesel
|
163
134
|
licenses: []
|
164
|
-
|
165
135
|
post_install_message:
|
166
136
|
rdoc_options: []
|
167
|
-
|
168
|
-
require_paths:
|
137
|
+
require_paths:
|
169
138
|
- lib
|
170
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
139
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
171
140
|
none: false
|
172
|
-
requirements:
|
173
|
-
- -
|
174
|
-
- !ruby/object:Gem::Version
|
175
|
-
|
176
|
-
|
177
|
-
- 0
|
178
|
-
version: "0"
|
179
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ! '>='
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '0'
|
145
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
180
146
|
none: false
|
181
|
-
requirements:
|
182
|
-
- -
|
183
|
-
- !ruby/object:Gem::Version
|
184
|
-
|
185
|
-
segments:
|
186
|
-
- 0
|
187
|
-
version: "0"
|
147
|
+
requirements:
|
148
|
+
- - ! '>='
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '0'
|
188
151
|
requirements: []
|
189
|
-
|
190
152
|
rubyforge_project: wsdsl
|
191
|
-
rubygems_version: 1.
|
153
|
+
rubygems_version: 1.8.24
|
192
154
|
signing_key:
|
193
155
|
specification_version: 3
|
194
156
|
summary: Web Service DSL
|
195
|
-
test_files:
|
157
|
+
test_files:
|
196
158
|
- spec/hello_world_controller.rb
|
197
159
|
- spec/hello_world_service.rb
|
198
160
|
- spec/json_response_description_spec.rb
|
@@ -207,3 +169,4 @@ test_files:
|
|
207
169
|
- spec/wd_spec.rb
|
208
170
|
- spec/ws_list_spec.rb
|
209
171
|
- spec/wsdsl_sinatra_ext_spec.rb
|
172
|
+
has_rdoc:
|