weasel_diesel 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,268 @@
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 WeaselDiesel::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 [WeaselDiesel::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 [WeaselDiesel::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
+ end
123
+
124
+ if 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
+ # You can have a "in" rule that also applies a min value since they are mutually exclusive
132
+ elsif rule.options[:minvalue]
133
+ min = rule.options[:minvalue]
134
+ raise InvalidParamValue, "Value for parameter '#{param_name}' is lower than the min accepted value (#{min})." if param_value.to_i < min
135
+ end
136
+ # Returns the updated params
137
+
138
+ # 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
139
+ if rule.options[:type] && !(rule.options[:options] || rule.options[:in])
140
+ # puts "casting #{param_value} into type: #{rule.options[:type]}"
141
+ params[param_name] = type_cast_value(rule.options[:type], param_value)
142
+ end
143
+ params
144
+ end
145
+
146
+
147
+ # Extract the param valie and the namespaced params
148
+ # based on a passed namespace and params
149
+ #
150
+ # @param [Hash] params The passed params to extract info from.
151
+ # @param [String] param_name The param name to find the value.
152
+ # @param [NilClass, String] namespace the params' namespace.
153
+ # @return [Arrays<Object, String>]
154
+ #
155
+ # @api private
156
+ def self.extract_param_values(params, param_name, namespace=nil)
157
+ # Namespace check
158
+ if namespace == '' || namespace.nil?
159
+ [params[param_name], nil]
160
+ else
161
+ # puts "namespace: #{namespace} - params #{params[namespace].inspect}"
162
+ namespaced_params = params[namespace]
163
+ if namespaced_params
164
+ [namespaced_params[param_name], namespaced_params]
165
+ else
166
+ [nil, namespaced_params]
167
+ end
168
+ end
169
+ end
170
+
171
+ # @param [#WeaselDiesel::Params::Rule] rule The optional rule
172
+ # @param [Hash] params The request params
173
+ # @param [String] namespace An optional namespace
174
+ # @return [Hash] The potentially modified params
175
+ #
176
+ # @api private
177
+ def self.run_optional_rule(rule, params, namespace=nil)
178
+ param_name = rule.name.to_s
179
+
180
+ param_value, namespaced_params = extract_param_values(params, param_name, namespace)
181
+
182
+ if param_value.nil? && rule.options[:default]
183
+ if namespace
184
+ params[namespace][param_name] = param_value = rule.options[:default]
185
+ else
186
+ params[param_name] = param_value = rule.options[:default]
187
+ end
188
+ end
189
+
190
+ # 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
191
+ if rule.options[:type] && !param_value.nil?
192
+ if namespace
193
+ params[namespace][param_name] = param_value = type_cast_value(rule.options[:type], param_value)
194
+ else
195
+ params[param_name] = param_value = type_cast_value(rule.options[:type], param_value)
196
+ end
197
+ end
198
+
199
+ choices = rule.options[:options] || rule.options[:in]
200
+ if choices && param_value && !choices.include?(param_value)
201
+ raise InvalidParamValue, "Value for parameter '#{param_name}' (#{param_value}) is not in the allowed set of values."
202
+ end
203
+
204
+ params
205
+ end
206
+
207
+ def self.unexpected_params?(params, param_names)
208
+ # Raise an exception unless no unexpected params were found
209
+ unexpected_keys = (params.keys - param_names)
210
+ unless unexpected_keys.empty?
211
+ raise UnexpectedParam, "Request included unexpected parameter(s): #{unexpected_keys.join(', ')}"
212
+ end
213
+ end
214
+
215
+
216
+ def self.type_cast_value(type, value)
217
+ case type
218
+ when :integer
219
+ value.to_i
220
+ when :float, :decimal
221
+ value.to_f
222
+ when :string
223
+ value.to_s
224
+ when :boolean
225
+ if value.is_a? TrueClass
226
+ true
227
+ elsif value.is_a? FalseClass
228
+ false
229
+ else
230
+ case value.to_s
231
+ when /^(1|true|TRUE|T|Y)$/
232
+ true
233
+ when /^(0|false|FALSE|F|N)$/
234
+ false
235
+ else
236
+ raise InvalidParamValue, "Could not typecast boolean to appropriate value"
237
+ end
238
+ end
239
+ # An array type is a comma delimited string, we need to cast the passed strings.
240
+ when :array
241
+ value.respond_to?(:split) ? value.split(',') : value
242
+ when :binary, :array, :file
243
+ value
244
+ else
245
+ value
246
+ end
247
+ end
248
+
249
+ # Checks that the value's type matches the expected type for a given param
250
+ #
251
+ # @param [Symbol, String] Param name used if the verification fails and that an error is raised.
252
+ # @param [#to_s] The value to validate.
253
+ # @param [Symbol] The expected type, such as :boolean, :integer etc...
254
+ # @raise [InvalidParamType] Custom exception raised when the validation isn't found or the value doesn't match.
255
+ #
256
+ # @return [Nil]
257
+ # @api public
258
+ # TODO raising an exception really isn't a good idea since it forces the stack to unwind.
259
+ # More than likely developers are using exceptions to control the code flow and a different approach should be used.
260
+ # Catch/throw is a bit more efficient but is still the wrong approach for this specific problem.
261
+ def self.verify_cast(name, value, expected_type)
262
+ validation = ParamsVerification.type_validations[expected_type.to_sym]
263
+ unless validation.nil? || value.to_s =~ validation
264
+ raise InvalidParamType, "Value for parameter '#{name}' (#{value}) is of the wrong type (expected #{expected_type})"
265
+ end
266
+ end
267
+
268
+ end
@@ -0,0 +1,457 @@
1
+ require 'json'
2
+
3
+ class WeaselDiesel
4
+ # Response DSL class
5
+ # @api public
6
+ class Response
7
+
8
+ # The list of all the elements inside the response
9
+ #
10
+ # @return [Array<WeaselDiesel::Response::Element>]
11
+ # @api public
12
+ attr_reader :elements
13
+
14
+ # The list of all the arays inside the response
15
+ #
16
+ # @return [Array<WeaselDiesel::Response::Array>]
17
+ attr_reader :arrays
18
+
19
+ def initialize
20
+ @elements = []
21
+ @arrays = []
22
+ end
23
+
24
+ # Lists all top level simple elements and array elements.
25
+ #
26
+ # @return [Array<WeaselDiesel::Response::Element, WeaselDiesel::Response::Array>]
27
+ def nodes
28
+ elements + arrays
29
+ end
30
+
31
+ # Shortcut to automatically create a node of array type.
32
+ # Useful when describing a JSON response.
33
+ #
34
+ # @param [String, Symbol] name the name of the element.
35
+ # @param [Hash] opts the element options.
36
+ # @see Vector#initialize
37
+ def array(name, type=nil)
38
+ vector = Vector.new(name, type)
39
+ yield(vector) if block_given?
40
+ @arrays << vector
41
+ end
42
+
43
+ # Defines a new element and yields the content of an optional block
44
+ # Each new element is then stored in the elements array.
45
+ #
46
+ # @param [Hash] opts Options used to define the element
47
+ # @option opts [String, Symbol] :name The element name
48
+ # @option opts [String, Symbol] :type The optional type
49
+ #
50
+ # @yield [WeaselDiesel::Response::Element] the newly created element
51
+ # @example create an element called 'my_stats'.
52
+ # service.response do |response|
53
+ # response.element(:name => "my_stats", :type => 'Leaderboard')
54
+ # end
55
+ #
56
+ # @return [WeaselDiesel::Response::Element]
57
+ # @api public
58
+ def element(opts={})
59
+ el = Element.new(opts[:name], opts[:type])
60
+ yield(el) if block_given?
61
+ @elements << el
62
+ el
63
+ end
64
+
65
+ # Defines an anonymous element
66
+ # Useful for JSON response description
67
+ #
68
+ # @return [WeaselDiesel::Response::Element]
69
+ def object
70
+ yield element
71
+ end
72
+
73
+ # Returns a response element object based on its name
74
+ # @param [String, Symbol] The element name we want to match
75
+ #
76
+ # @return [WeaselDiesel::Response::Element]
77
+ # @api public
78
+ def element_named(name)
79
+ @elements.find{|e| e.name.to_s == name.to_s}
80
+ end
81
+
82
+
83
+ # Converts the object into a JSON representation
84
+ # @return [String] JSON representation of the response
85
+ def to_json(*args)
86
+ if nodes.size > 1
87
+ nodes.to_json(*args)
88
+ else
89
+ nodes.first.to_json(*args)
90
+ end
91
+ end
92
+
93
+
94
+ class Params
95
+ class Rule
96
+ def to_hash
97
+ {:name => name, :options => options}
98
+ end
99
+ end
100
+ end
101
+
102
+ # The Response element class describing each element of a service response.
103
+ # Instances are usually not instiated directly but via the Response#element accessor.
104
+ #
105
+ # @see WeaselDiesel::Response#element
106
+ # @api public
107
+ class Element
108
+
109
+ # @return [String, #to_s] The name of the element
110
+ # @api public
111
+ attr_reader :name
112
+
113
+ # @api public
114
+ attr_reader :type
115
+
116
+ # The optional lookup key of an object
117
+ attr_reader :key
118
+
119
+ # @return [Array<WeaselDiesel::Response::Element::Attribute>] An array of attributes
120
+ # @api public
121
+ attr_reader :attributes
122
+
123
+ # @return [Array<WeaselDiesel::Response::Element::MetaAttribute>] An array of meta attributes
124
+ # @api public
125
+ attr_reader :meta_attributes
126
+
127
+ # @return [Array] An array of vectors/arrays
128
+ # @api public
129
+ attr_reader :vectors
130
+
131
+ # @return [WeaselDiesel::Documentation::ElementDoc] Response element documentation
132
+ # @api public
133
+ attr_reader :doc
134
+
135
+ # @return [NilClass, Array<WeaselDiesel::Response::Element>] The optional nested elements
136
+ attr_reader :elements
137
+
138
+ # Alias to use a JSON/JS jargon instead of XML.
139
+ alias :properties :attributes
140
+
141
+ # Alias to use a JSON/JS jargon instead of XML.
142
+ alias :objects :elements
143
+
144
+ # param [String, Symbol] name The name of the element
145
+ # param [String, Symbol] type The optional type of the element
146
+ # @api public
147
+ def initialize(name, type=nil)
148
+ # sets a documentation placeholder since the response doc is defined at the same time
149
+ # the response is defined.
150
+ @doc = Documentation::ElementDoc.new(name)
151
+ @name = name
152
+ @type = type
153
+ @attributes = []
154
+ @meta_attributes = []
155
+ @vectors = []
156
+ @key = nil
157
+ # we don't need to initialize the nested elements, by default they should be nil
158
+ end
159
+
160
+ # sets a new attribute and returns the entire list of attributes
161
+ #
162
+ # @param [Hash] opts An element's attribute options
163
+ # @option opts [String, Symbol] attribute_name The name of the attribute, the value being the type
164
+ # @option opts [String, Symbol] :doc The attribute documentation
165
+ # @option opts [String, Symbol] :mock An optional mock value used by service related tools
166
+ #
167
+ # @example Creation of a response attribute called 'best_lap_time'
168
+ # service.response do |response|
169
+ # response.element(:name => "my_stats", :type => 'Leaderboard') do |e|
170
+ # e.attribute "best_lap_time" => :float, :doc => "Best lap time in seconds."
171
+ # end
172
+ # end
173
+ #
174
+ # @return [Array<WeaselDiesel::Response::Attribute>]
175
+ # @api public
176
+ def attribute(opts, extra_opts={})
177
+ raise ArgumentError unless opts.is_a?(Hash) && extra_opts.is_a?(Hash)
178
+ new_attribute = Attribute.new(opts, extra_opts)
179
+ @attributes << new_attribute
180
+ # document the attribute if description available
181
+ # we might want to have a placeholder message when a response attribute isn't defined
182
+ if opts.merge!(extra_opts).has_key?(:doc)
183
+ @doc.attribute(new_attribute.name, opts[:doc])
184
+ end
185
+ @attributes
186
+ end
187
+
188
+ # sets a new meta attribute and returns the entire list of meta attributes
189
+ #
190
+ # @param [Hash] opts An element's attribute options
191
+ # @option opts [String, Symbol] attribute_name The name of the attribute, the value being the type
192
+ # @option opts [String, Symbol] :mock An optional mock value used by service related tools
193
+ #
194
+ # @example Creation of a response attribute called 'best_lap_time'
195
+ # service.response do |response|
196
+ # response.element(:name => "my_stats", :type => 'Leaderboard') do |e|
197
+ # e.meta_attribute "id" => :key
198
+ # end
199
+ # end
200
+ #
201
+ # @return [Array<WeaselDiesel::Response::MetaAttribute>]
202
+ # @api public
203
+ def meta_attribute(opts)
204
+ raise ArgumentError unless opts.is_a?(Hash)
205
+ # extract the documentation part and add it where it belongs
206
+ new_attribute = MetaAttribute.new(opts)
207
+ @meta_attributes << new_attribute
208
+ @meta_attributes
209
+ end
210
+
211
+ # Defines an array aka vector of elements.
212
+ #
213
+ # @param [String, Symbol] name The name of the array element.
214
+ # @param [String, Symbol] type Optional type information, useful to store the represented
215
+ # object types for instance.
216
+ #
217
+ # @param [Proc] &block
218
+ # A block to execute against the newly created array.
219
+ #
220
+ # @example Defining an element array called 'player_creation_rating'
221
+ # element.array 'player_creation_rating', 'PlayerCreationRating' do |a|
222
+ # a.attribute :comments => :string
223
+ # a.attribute :player_id => :integer
224
+ # a.attribute :rating => :integer
225
+ # a.attribute :username => :string
226
+ # end
227
+ # @yield [Vector] the newly created array/vector instance
228
+ # @see Element#initialize
229
+ #
230
+ # @return [Array<WeaselDiesel::Response::Vector>]
231
+ # @api public
232
+ def array(name, type=nil)
233
+ vector = Vector.new(name, type)
234
+ yield(vector) if block_given?
235
+ @vectors << vector
236
+ end
237
+
238
+ # Returns the arrays/vectors contained in the response.
239
+ # This is an alias to access @vectors
240
+ # @see @vectors
241
+ #
242
+ # @return [Array<WeaselDiesel::Response::Vector>]
243
+ # @api public
244
+ def arrays
245
+ @vectors
246
+ end
247
+
248
+ # Defines a new element and yields the content of an optional block
249
+ # Each new element is then stored in the elements array.
250
+ #
251
+ # @param [Hash] opts Options used to define the element
252
+ # @option opts [String, Symbol] :name The element name
253
+ # @option opts [String, Symbol] :type The optional type
254
+ #
255
+ # @yield [WeaselDiesel::Response::Element] the newly created element
256
+ # @example create an element called 'my_stats'.
257
+ # service.response do |response|
258
+ # response.element(:name => "my_stats", :type => 'Leaderboard')
259
+ # end
260
+ #
261
+ # @return [Array<WeaselDiesel::Response::Element>]
262
+ # @api public
263
+ def element(opts={})
264
+ el = Element.new(opts[:name], opts[:type])
265
+ yield(el) if block_given?
266
+ @elements ||= []
267
+ @elements << el
268
+ el
269
+ end
270
+
271
+ # Shortcut to create a new element.
272
+ #
273
+ # @param [Symbol, String] name the name of the element.
274
+ # @param [Hash] opts the options for the newly created element.
275
+ def object(name, opts={}, &block)
276
+ element(opts.merge(:name => name), &block)
277
+ end
278
+
279
+ # Getter/setter for the key meta attribute.
280
+ # A key name can be used to lookup an object by a primary key for instance.
281
+ #
282
+ # @param [Symbol, String] name the name of the key attribute.
283
+ # @param [Hash] opts the options attached with the key.
284
+ def key(name=nil, opts={})
285
+ meta_attribute_getter_setter(:key, name, opts)
286
+ end
287
+
288
+ # Getter/setter for the type meta attribute.
289
+ #
290
+ # @param [Symbol, String] name the name of the type attribute.
291
+ # @param [Hash] opts the options attached with the key.
292
+ def type(name=nil, opts={})
293
+ meta_attribute_getter_setter(:type, name, opts)
294
+ end
295
+
296
+ # Shortcut to create a string attribute
297
+ #
298
+ # @param [Symbol, String] name the name of the attribute.
299
+ # @param [Hash] opts the attribute options.
300
+ def string(name=nil, opts={})
301
+ attribute({name => :string}, opts)
302
+ end
303
+
304
+ # Shortcut to create a string attribute
305
+ #
306
+ # @param [Symbol, String] name the name of the attribute.
307
+ # @param [Hash] opts the attribute options.
308
+ def integer(name=nil, opts={})
309
+ attribute({name => :integer}, opts)
310
+ end
311
+
312
+ # Shortcut to create a string attribute
313
+ #
314
+ # @param [Symbol, String] name the name of the attribute.
315
+ # @param [Hash] opts the attribute options.
316
+ def float(name=nil, opts={})
317
+ attribute({name => :float}, opts)
318
+ end
319
+
320
+ # Shortcut to create a string attribute
321
+ #
322
+ # @param [Symbol, String] name the name of the attribute.
323
+ # @param [Hash] opts the attribute options.
324
+ def boolean(name=nil, opts={})
325
+ attribute({name => :boolean}, opts)
326
+ end
327
+
328
+ # Shortcut to create a string attribute
329
+ #
330
+ # @param [Symbol, String] name the name of the attribute.
331
+ # @param [Hash] opts the attribute options.
332
+ def datetime(name=nil, opts={})
333
+ attribute({name => :datetime}, opts)
334
+ end
335
+
336
+ # Converts an element into a hash representation
337
+ #
338
+ # @return [Hash] the element attributes formated in a hash
339
+ def to_hash
340
+ attrs = {}
341
+ attributes.each{ |attr| attrs[attr.name] = attr.type }
342
+ elements.each{ |el| attrs[el.name] = el.to_hash } if elements
343
+ if self.class == Vector
344
+ name ? {name => [attrs]} : [attrs]
345
+ else
346
+ name ? {name => attrs} : attrs
347
+ end
348
+ end
349
+
350
+ def to_html
351
+ output = ""
352
+ if name
353
+ output << "<li>"
354
+ output << "<span class='label notice'>#{name}</span> of type <span class='label success'>#{self.is_a?(Vector) ? 'Array' : 'Object'}</span>"
355
+ end
356
+ if self.is_a? Vector
357
+ output << "<h6>Properties of each array item:</h6>"
358
+ else
359
+ output << "<h6>Properties:</h6>"
360
+ end
361
+ output << "<ul>"
362
+ properties.each do |prop|
363
+ output << "<li><span class='label notice'>#{prop.name}</span> of type <span class='label success'>#{prop.type}</span> #{'(Can be blank or missing) ' if prop.opts && prop.opts.respond_to?(:[]) && prop.opts[:null]} "
364
+ output << prop.doc unless prop.doc.blank?
365
+ output << "</li>"
366
+ end
367
+ arrays.each{ |arr| output << arr.html }
368
+ elements.each {|el| output << el.to_html } if elements
369
+ output << "</ul>"
370
+ output << "</li>" if name
371
+ output
372
+ end
373
+
374
+ private
375
+
376
+ # Create a meta element attribute
377
+ def meta_attribute_getter_setter(type, name, opts)
378
+ if name
379
+ meta_attribute({name => type}.merge(opts))
380
+ else
381
+ # with a fallback to the @type ivar
382
+ meta = meta_attributes.find{|att| att.type == type}
383
+ if meta
384
+ meta.value
385
+ else
386
+ instance_variable_get("@#{type}")
387
+ end
388
+ end
389
+ end
390
+
391
+ # Response element's attribute class
392
+ # @api public
393
+ class Attribute
394
+
395
+ # @return [String, #to_s] The attribute's name.
396
+ # @api public
397
+ attr_reader :name
398
+ alias :value :name
399
+
400
+ # @return [Symbol, String, #to_s] The attribute's type such as boolean, string etc..
401
+ # @api public
402
+ attr_reader :type
403
+
404
+ # @return [String] The documentation associated with this attribute.
405
+ # @api public
406
+ attr_reader :doc
407
+
408
+ # @see {Attribute#new}
409
+ # @return [Hash, Nil, Object] Could be a hash, nil or any object depending on how the attribute is created.
410
+ # @api public
411
+ attr_reader :opts
412
+
413
+ # Takes a Hash or an Array and extract the attribute name, type
414
+ # doc and extra options.
415
+ # If the passed objects is a Hash, the name will be extract from
416
+ # the first key and the type for the first value.
417
+ # An entry keyed by :doc will be used for the doc and the rest will go
418
+ # as extra options.
419
+ #
420
+ # If an Array is passed, the elements will be 'shifted' in this order:
421
+ # name, type, doc, type
422
+ #
423
+ # @param [Hash, Array] o_params
424
+ # @param [Hash] o_extra_params A hash with extra params passed, needed to support Ruby 1.8 :(
425
+ #
426
+ # @api public
427
+ def initialize(o_params, o_extra_params={})
428
+ params = o_params.dup
429
+ extra_params = o_extra_params.dup
430
+ if params.is_a?(Hash)
431
+ @name, @type = params.shift
432
+ @doc = params.delete(:doc) if params.has_key?(:doc)
433
+ @doc ||= extra_params.delete(:doc) if extra_params.has_key?(:doc)
434
+ @opts = params.merge!(extra_params)
435
+ elsif params.is_a?(Array)
436
+ @name = params.shift
437
+ @type = params.shift
438
+ @doc = params.shift
439
+ @opts = params
440
+ end
441
+ end
442
+ end
443
+
444
+ # Response's meta attribute meant to set some extra
445
+ # attributes which are not part of the response per se.
446
+ class MetaAttribute < Attribute
447
+ end
448
+
449
+ end # of Element
450
+
451
+ # Array of objects
452
+ # @api public
453
+ class Vector < Element
454
+ end # of Vector
455
+
456
+ end # of Response
457
+ end