weasel_diesel 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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