playerconnect-wsdsl 0.2.2

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