wsdsl 0.0.1

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,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