weasel_diesel 1.0.4 → 1.1.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.
- 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:
|