wsdsl 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/lib/params.rb ADDED
@@ -0,0 +1,366 @@
1
+ class WSDSL
2
+ # Service params class letting you define param rules.
3
+ # Usually not initialized directly but accessed via the service methods.
4
+ #
5
+ # @see WSDSL#params
6
+ #
7
+ # @api public
8
+ class Params
9
+
10
+ # Params usually have a few rules used to validate requests.
11
+ # Rules are not usually initialized directly but instead via
12
+ # the service's #params accessor.
13
+ #
14
+ # @api public
15
+ class Rule
16
+
17
+ # @return [Symbol, String] name The name of the param the rule applies to.
18
+ # @api public
19
+ attr_reader :name
20
+
21
+ # @return [Hash] options The rule options.
22
+ # @option options [Symbol] :in A list of acceptable values.
23
+ # @option options [Symbol] :options A list of acceptable values.
24
+ # @option options [Symbol] :default The default value of the param.
25
+ # @option options [Symbol] :minvalue The minimum acceptable value.
26
+ # @option options [Symbol] :maxvalue The maximim acceptable value.
27
+ # @api public
28
+ attr_reader :options
29
+
30
+
31
+ # @param [Symbol, String] name
32
+ # The param's name
33
+ # @param [Hash] opts The rule options
34
+ # @option opts [Symbol] :in A list of acceptable values.
35
+ # @option opts [Symbol] :options A list of acceptable values.
36
+ # @option opts [Symbol] :default The default value of the param.
37
+ # @option opts [Symbol] :minvalue The minimum acceptable value.
38
+ # @option opts [Symbol] :maxvalue The maximim acceptable value.
39
+ # @api public
40
+ def initialize(name, opts = {})
41
+ @name = name
42
+ @options = opts
43
+ end
44
+
45
+ # The namespace used if any
46
+ #
47
+ # @return [NilClass, String]
48
+ # @api public
49
+ def namespace
50
+ @options[:space_name]
51
+ end
52
+
53
+ end # of Rule
54
+
55
+ # The namespace used if any
56
+ #
57
+ # @return [String]
58
+ # @api public
59
+ attr_reader :space_name
60
+
61
+ # @param [Hash] opts The params options
62
+ # @option opts [:symbol] :space_name Optional namespace.
63
+ # @api public
64
+ def initialize(opts={})
65
+ @space_name = opts[:space_name]
66
+ end
67
+
68
+ # Defines a new param and add it to the optional or required list based
69
+ # the passed options.
70
+ #
71
+ # @param [Symbol, String] name
72
+ # The name of the param
73
+ # @param [Symbol] type
74
+ # The type of param
75
+ # @param [Hash] options
76
+ # A hash representing the param settings
77
+ #
78
+ # @example Declaring an integer service param called id
79
+ # service.param(:id, :integer, :default => 9999, :in => [0, 9999])
80
+ #
81
+ # @return [Array] the typed list of params (required or optional)
82
+ # @api public]
83
+ def param(name, type, options={})
84
+ options[:type] = type
85
+ options[:space_name] = options[:space_name] || space_name
86
+ if options.delete(:required)
87
+ list_required << Rule.new(name, options)
88
+ else
89
+ list_optional << Rule.new(name, options)
90
+ end
91
+ end
92
+
93
+ # @group Params defintition DSL (accept_param style)
94
+
95
+ # Defines a new string param and add it to the required or optional list
96
+ #
97
+ # @param [String] name
98
+ # The name of the param
99
+ # @param [Hash] options
100
+ # A hash representing the param settings
101
+ #
102
+ # @example Defining a string service param named type which has various options.
103
+ # service.param.string :type, :in => LeaderboardType.names, :default => LeaderboardType::LIFETIME
104
+ #
105
+ # @api public
106
+ # @return [Arrays<WSDSL::Params::Rule>]
107
+ # List of optional or required param rules depending on the new param rule type
108
+ def string(name, options={})
109
+ param(name, :string, options)
110
+ end
111
+
112
+ # Defines a new integer param and add it to the required or optional list
113
+ #
114
+ # @param [String] name
115
+ # The name of the param
116
+ # @param [Hash] options
117
+ # A hash representing the param settings
118
+ #
119
+ # @example Defining a string service param named type which has various options.
120
+ # service.param.string :type, :in => LeaderboardType.names, :default => LeaderboardType::LIFETIME
121
+ #
122
+ # @api public
123
+ # @return [Arrays<WSDSL::Params::Rule>]
124
+ # List of optional or required param rules depending on the new param rule type
125
+ def integer(name, options={})
126
+ param(name, :integer, options)
127
+ end
128
+
129
+ # Defines a new float param and add it to the required or optional list
130
+ #
131
+ # @param [String] name
132
+ # The name of the param
133
+ # @param [Hash] options
134
+ # A hash representing the param settings
135
+ #
136
+ # @example Defining a string service param named type which has various options.
137
+ # service.param.string :type, :in => LeaderboardType.names, :default => LeaderboardType::LIFETIME
138
+ #
139
+ # @api public
140
+ # @return [Arrays<WSDSL::Params::Rule>]
141
+ # List of optional or required param rules depending on the new param rule type
142
+ def float(name, options={})
143
+ param(name, :float, options)
144
+ end
145
+
146
+ # Defines a new decimal param and add it to the required or optional list
147
+ #
148
+ # @param [String] name
149
+ # The name of the param
150
+ # @param [Hash] options
151
+ # A hash representing the param settings
152
+ #
153
+ # @example Defining a string service param named type which has various options.
154
+ # service.param.string :type, :in => LeaderboardType.names, :default => LeaderboardType::LIFETIME
155
+ #
156
+ # @api public
157
+ # @return [Arrays<WSDSL::Params::Rule>]
158
+ # List of optional or required param rules depending on the new param rule type
159
+ def decimal(name, options={})
160
+ param(name, :decimal, options)
161
+ end
162
+
163
+ # Defines a new boolean param and add it to the required or optional list
164
+ #
165
+ # @param [String] name
166
+ # The name of the param
167
+ # @param [Hash] options
168
+ # A hash representing the param settings
169
+ #
170
+ # @example Defining a string service param named type which has various options.
171
+ # service.param.string :type, :in => LeaderboardType.names, :default => LeaderboardType::LIFETIME
172
+ #
173
+ # @api public
174
+ # @return [Arrays<WSDSL::Params::Rule>]
175
+ # List of optional or required param rules depending on the new param rule type
176
+ def boolean(name, options={})
177
+ param(name, :boolean, options)
178
+ end
179
+
180
+ # Defines a new datetime param and add it to the required or optional list
181
+ #
182
+ # @param [String] name
183
+ # The name of the param
184
+ # @param [Hash] options
185
+ # A hash representing the param settings
186
+ #
187
+ # @example Defining a string service param named type which has various options.
188
+ # service.param.string :type, :in => LeaderboardType.names, :default => LeaderboardType::LIFETIME
189
+ #
190
+ # @api public
191
+ # @return [Arrays<WSDSL::Params::Rule>]
192
+ # List of optional or required param rules depending on the new param rule type
193
+ def datetime(name, options={})
194
+ param(name, :datetime, options)
195
+ end
196
+
197
+ # Defines a new text param and add it to the required or optional list
198
+ #
199
+ # @param [String] name
200
+ # The name of the param
201
+ # @param [Hash] options
202
+ # A hash representing the param settings
203
+ #
204
+ # @example Defining a string service param named type which has various options.
205
+ # service.param.string :type, :in => LeaderboardType.names, :default => LeaderboardType::LIFETIME
206
+ #
207
+ # @api public
208
+ # @return [Arrays<WSDSL::Params::Rule>]
209
+ # List of optional or required param rules depending on the new param rule type
210
+ def text(name, options={})
211
+ param(name, :text, options)
212
+ end
213
+
214
+ # Defines a new binary param and add it to the required or optional list
215
+ #
216
+ # @param [String] name
217
+ # The name of the param
218
+ # @param [Hash] options
219
+ # A hash representing the param settings
220
+ #
221
+ # @example Defining a string service param named type which has various options.
222
+ # service.param.string :type, :in => LeaderboardType.names, :default => LeaderboardType::LIFETIME
223
+ #
224
+ # @api public
225
+ # @return [Arrays<WSDSL::Params::Rule>]
226
+ # List of optional or required param rules depending on the new param rule type
227
+ def binary(name, options={})
228
+ param(name, :binary, options)
229
+ end
230
+
231
+ # Defines a new array param and add it to the required or optional list
232
+ #
233
+ # @param [String] name
234
+ # The name of the param
235
+ # @param [Hash] options
236
+ # A hash representing the param settings
237
+ #
238
+ # @example Defining a string service param named type which has various options.
239
+ # service.param.string :type, :in => LeaderboardType.names, :default => LeaderboardType::LIFETIME
240
+ #
241
+ # @api public
242
+ # @return [Array<WSDSL::Params::Rule>]
243
+ # List of optional or required param rules depending on the new param rule type
244
+ def array(name, options={})
245
+ param(name, :array, options)
246
+ end
247
+
248
+ # Defines a new file param and add it to the required or optional list
249
+ #
250
+ # @param [String] name
251
+ # The name of the param
252
+ # @param [Hash] options
253
+ # A hash representing the param settings
254
+ #
255
+ # @example Defining a string service param named type which has various options.
256
+ # service.param.string :type, :in => LeaderboardType.names, :default => LeaderboardType::LIFETIME
257
+ #
258
+ # @api public
259
+ # @return [Arrays<WSDSL::Params::Rule>]
260
+ # List of optional or required param rules depending on the new param rule type
261
+ def file(name, options={})
262
+ param(name, :file, options)
263
+ end
264
+
265
+ # @group param setters based on the state (required or optional)
266
+
267
+ # Defines a new required param
268
+ #
269
+ # @param [Symbol, String] param_name
270
+ # The name of the param to define
271
+ # @param [Hash] opts
272
+ # A hash representing the required param, the key being the param name name
273
+ # and the value being a hash of options.
274
+ #
275
+ # @example Defining a required service param called 'id' of `Integer` type
276
+ # service.params.required :id, :type => 'integer', :default => 9999
277
+ #
278
+ # @return [Array<WSDSL::Params::Rule>] The list of required rules
279
+ #
280
+ # @api public
281
+ def required(param_name, opts={})
282
+ # # support for when a required param doesn't have any options
283
+ # unless opts.respond_to?(:each_pair)
284
+ # opts = {opts => nil}
285
+ # end
286
+ # # recursive rule creation
287
+ # if opts.size > 1
288
+ # opts.each_pair{|k,v| requires({k => v})}
289
+ # else
290
+ list_required << Rule.new(param_name, opts)
291
+ # end
292
+ end
293
+
294
+ # Defines a new optional param rule
295
+ #
296
+ # @param [Symbol, String] param_name
297
+ # The name of the param to define
298
+ # @param [Hash] opts
299
+ # A hash representing the required param, the key being the param name name
300
+ # and the value being a hash of options.
301
+ #
302
+ # @example Defining an optional service param called 'id' of `Integer` type
303
+ # service.params.optional :id, :type => 'integer', :default => 9999
304
+ #
305
+ # @return [Array<WSDSL::Params::Rule>] The list of optional rules
306
+ # @api public
307
+ def optional(param_name, opts={})
308
+ # # recursive rule creation
309
+ # if opts.size > 1
310
+ # opts.each_pair{|k,v| optional({k => v})}
311
+ # else
312
+ list_optional << Rule.new(param_name, opts)
313
+ # end
314
+ end
315
+
316
+ # @group params accessors per status (required or optional)
317
+
318
+ # Returns an array of all the required params
319
+ #
320
+ # @return [Array<WSDSL::Params::Rule>] The list of required rules
321
+ # @api public
322
+ def list_required
323
+ @required ||= []
324
+ end
325
+
326
+ # Returns an array of all the optional params
327
+ #
328
+ # @return [Array<WSDSL::Params::Rule>] all the optional params
329
+ # @api public
330
+ def list_optional
331
+ @optional ||= []
332
+ end
333
+
334
+ # @endgroup
335
+
336
+ # Defines a namespaced param
337
+ #
338
+ # @yield [Params] the newly created namespaced param
339
+ # @return [Array<WSDSL::Params>] the list of all the namespaced params
340
+ # @api public
341
+ def namespace(name)
342
+ params = Params.new(:space_name => name)
343
+ yield(params) if block_given?
344
+ namespaced_params << params unless namespaced_params.include?(params)
345
+ end
346
+
347
+ # Returns the namespaced params
348
+ #
349
+ # @return [Array<WSDSL::Params>] the list of all the namespaced params
350
+ # @api public
351
+ def namespaced_params
352
+ @namespaced_params ||= []
353
+ end
354
+
355
+ # Returns the names of the first level expected params
356
+ #
357
+ # @return [Array<WSDSL::Params>]
358
+ # @api public
359
+ def param_names
360
+ first_level_expected_params = (list_required + list_optional).map{|rule| rule.name.to_s}
361
+ first_level_expected_params += namespaced_params.map{|r| r.space_name.to_s}
362
+ first_level_expected_params
363
+ end
364
+
365
+ end # of Params
366
+ end
@@ -0,0 +1,222 @@
1
+ # ParamsVerification module.
2
+ # Written to verify a service params without creating new objects.
3
+ # This module is used on all requests requiring validation and therefore performance
4
+ # security and maintainability are critical.
5
+ #
6
+ # @api public
7
+ module ParamsVerification
8
+
9
+ class ParamError < StandardError; end #:nodoc
10
+ class NoParamsDefined < ParamError; end #:nodoc
11
+ class MissingParam < ParamError; end #:nodoc
12
+ class UnexpectedParam < ParamError; end #:nodoc
13
+ class InvalidParamType < ParamError; end #:nodoc
14
+ class InvalidParamValue < ParamError; end #:nodoc
15
+
16
+ # An array of validation regular expressions.
17
+ # The array gets cached but can be accessed via the symbol key.
18
+ #
19
+ # @return [Hash] An array with all the validation types as keys and regexps as values.
20
+ # @api public
21
+ def self.type_validations
22
+ @type_validations ||= { :integer => /^-?\d+$/,
23
+ :float => /^-?(\d*\.\d+|\d+)$/,
24
+ :decimal => /^-?(\d*\.\d+|\d+)$/,
25
+ :datetime => /^[-\d:T\s]+$/, # "T" is for ISO date format
26
+ :boolean => /^(1|true|TRUE|T|Y|0|false|FALSE|F|N)$/
27
+ }
28
+ end
29
+
30
+ # Validation against each required WSDSL::Params::Rule
31
+ # and returns the potentially modified params (with default values)
32
+ #
33
+ # @param [Hash] params The params to verify (incoming request params)
34
+ # @param [WSDSL::Params] service_params A Playco service param compatible object listing required and optional params
35
+ # @param [Boolean] ignore_unexpected Flag letting the validation know if unexpected params should be ignored
36
+ #
37
+ # @return [Hash]
38
+ # The passed params potentially modified by the default rules defined in the service.
39
+ #
40
+ # @example Validate request params against a service's defined param rules
41
+ # ParamsVerification.validate!(request.params, @service.defined_params)
42
+ #
43
+ # @api public
44
+ def self.validate!(params, service_params, ignore_unexpected=false)
45
+
46
+ # Verify that no garbage params are passed, if they are, an exception is raised.
47
+ # only the first level is checked at this point
48
+ unless ignore_unexpected
49
+ unexpected_params?(params, service_params.param_names)
50
+ end
51
+
52
+ # dupe the params so we don't modify the passed value
53
+ updated_params = params.dup
54
+ # Required param verification
55
+ service_params.list_required.each do |rule|
56
+ updated_params = validate_required_rule(rule, updated_params)
57
+ end
58
+
59
+ # Set optional defaults if any optional
60
+ service_params.list_optional.each do |rule|
61
+ updated_params = run_optional_rule(rule, updated_params)
62
+ end
63
+
64
+ # check the namespaced params
65
+ service_params.namespaced_params.each do |param|
66
+ param.list_required.each do |rule|
67
+ updated_params = validate_required_rule(rule, updated_params, param.space_name.to_s)
68
+ end
69
+ # TODO add verification for namespaced optional rules
70
+ end
71
+
72
+ # verify nested params, only 1 level deep tho
73
+ params.each_pair do |key, value|
74
+ if value.is_a?(Hash)
75
+ namespaced = service_params.namespaced_params.find{|np| np.space_name.to_s == key.to_s}
76
+ raise UnexpectedParam, "Request included unexpected parameter: #{key}" if namespaced.nil?
77
+ unexpected_params?(params[key], namespaced.param_names)
78
+ end
79
+ end
80
+
81
+ updated_params
82
+ end
83
+
84
+
85
+ private
86
+
87
+ # Validate a required rule against a list of params passed.
88
+ #
89
+ #
90
+ # @param [WSDSL::Params::Rule] rule The required rule to check against.
91
+ # @param [Hash] params The request params.
92
+ # @param [String] namespace Optional param namespace to check the rule against.
93
+ #
94
+ # @return [Hash]
95
+ # A hash representing the potentially modified params after going through the filter.
96
+ #
97
+ # @api private
98
+ def self.validate_required_rule(rule, params, namespace=nil)
99
+ param_name = rule.name.to_s
100
+
101
+ # Namespace check
102
+ if namespace == '' || namespace.nil?
103
+ param_value = params[param_name]
104
+ else
105
+ # puts "namespace: #{namespace} - params #{params[namespace].inspect}"
106
+ namespaced_params = params[namespace]
107
+ param_value = namespaced_params ? namespaced_params[param_name] : nil
108
+ end
109
+ # puts "verify #{param_name} params, current value: #{param_value}"
110
+
111
+ # Checks default
112
+ if param_value.nil? && rule.options && rule.options[:default]
113
+ param_ref = namespace.nil? ? params[param_name] : params[namespace][param_name]
114
+ param_ref = rule.options[:default]
115
+ # Checks presence
116
+ elsif !(namespaced_params || params).keys.include?(param_name)
117
+ 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
+ elsif rule.options[:options] || rule.options[:in]
125
+ choices = rule.options[:options] || rule.options[:in]
126
+ if rule.options[:type]
127
+ # Force the cast so we can compare properly
128
+ param_value = params[param_name] = type_cast_value(rule.options[:type], param_value)
129
+ end
130
+ raise InvalidParamValue, "Value for parameter '#{param_name}' (#{param_value}) is not in the allowed set of values." unless choices.include?(param_value)
131
+ elsif rule.options[:minvalue]
132
+ min = rule.options[:minvalue]
133
+ raise InvalidParamValue, "Value for parameter '#{param_name}' is lower than the min accepted value (#{min})." if param_value.to_i >= min
134
+ end
135
+ # Returns the updated params
136
+
137
+ # 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
138
+ if rule.options[:type] && !(rule.options[:options] || rule.options[:in])
139
+ # puts "casting #{param_value} into type: #{rule.options[:type]}"
140
+ params[param_name] = type_cast_value(rule.options[:type], param_value)
141
+ end
142
+
143
+ params
144
+ end
145
+
146
+ # @todo add support for namespaces
147
+ # @param [#WSDSL::Params::Rule] rule The optional rule
148
+ # @param [Hash] params The request params
149
+ # @param [String] namespace An optional namespace
150
+ # @return [Hash] The potentially modified params
151
+ # @api private
152
+ def self.run_optional_rule(rule, params, namespace=nil)
153
+ param_name = rule.name.to_s
154
+ param_value = params[param_name]
155
+ if param_value.nil? && rule.options[:default]
156
+ params[param_name] = rule.options[:default]
157
+ end
158
+
159
+ # 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
160
+ if rule.options[:type] && !param_value.nil?
161
+ params[param_name] = type_cast_value(rule.options[:type], param_value)
162
+ end
163
+
164
+ params
165
+ end
166
+
167
+ def self.unexpected_params?(params, param_names)
168
+ # Raise an exception unless no unexpected params were found
169
+ unexpected_keys = (params.keys(&:to_s) - param_names)
170
+ unless unexpected_keys.empty?
171
+ raise UnexpectedParam, "Request included unexpected parameter(s): #{unexpected_keys.join(', ')}"
172
+ end
173
+ end
174
+
175
+
176
+ def self.type_cast_value(type, value)
177
+ case type
178
+ when :integer
179
+ value.to_i
180
+ when :float, :decimal
181
+ value.to_f
182
+ when :string
183
+ value.to_s
184
+ when :boolean
185
+ if value.is_a? TrueClass
186
+ true
187
+ elsif value.is_a? FalseClass
188
+ false
189
+ else
190
+ case value.to_s
191
+ when /^(1|true|TRUE|T|Y)$/
192
+ true
193
+ when /^(0|false|FALSE|F|N)$/
194
+ false
195
+ else
196
+ raise InvalidParamValue, "Could not typecast boolean to appropriate value"
197
+ end
198
+ end
199
+ when :binary, :array, :file
200
+ value
201
+ else
202
+ value
203
+ end
204
+ end
205
+
206
+ # Checks that the value's type matches the expected type for a given param
207
+ #
208
+ # @param [Symbol, String] Param name used if the verification fails and that an error is raised.
209
+ # @param [#to_s] The value to validate.
210
+ # @param [Symbol] The expected type, such as :boolean, :integer etc...
211
+ # @raise [InvalidParamType] Custom exception raised when the validation isn't found or the value doesn't match.
212
+ #
213
+ # @return [Nil]
214
+ # @api public
215
+ def self.verify_cast(name, value, expected_type)
216
+ validation = ParamsVerification.type_validations[expected_type.to_sym]
217
+ unless validation.nil? || value.to_s =~ validation
218
+ raise InvalidParamType, "Value for parameter '#{name}' (#{value}) is of the wrong type (expected #{expected_type})"
219
+ end
220
+ end
221
+
222
+ end