safrano 0.5.5 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/lib/core_ext/Date/format.rb +47 -0
  3. data/lib/core_ext/DateTime/format.rb +54 -0
  4. data/lib/core_ext/Dir/iter.rb +7 -5
  5. data/lib/core_ext/Hash/transform.rb +14 -6
  6. data/lib/core_ext/Numeric/convert.rb +1 -1
  7. data/lib/core_ext/Time/format.rb +71 -0
  8. data/lib/core_ext/date.rb +5 -0
  9. data/lib/core_ext/datetime.rb +5 -0
  10. data/lib/core_ext/time.rb +5 -0
  11. data/lib/odata/attribute.rb +8 -6
  12. data/lib/odata/batch.rb +3 -3
  13. data/lib/odata/collection.rb +5 -5
  14. data/lib/odata/collection_filter.rb +3 -1
  15. data/lib/odata/collection_media.rb +4 -27
  16. data/lib/odata/collection_order.rb +1 -1
  17. data/lib/odata/common_logger.rb +5 -27
  18. data/lib/odata/complex_type.rb +19 -21
  19. data/lib/odata/edm/primitive_types.rb +14 -19
  20. data/lib/odata/entity.rb +12 -12
  21. data/lib/odata/error.rb +7 -7
  22. data/lib/odata/expand.rb +2 -2
  23. data/lib/odata/filter/base.rb +10 -1
  24. data/lib/odata/filter/error.rb +2 -2
  25. data/lib/odata/filter/parse.rb +16 -2
  26. data/lib/odata/filter/sequel.rb +31 -4
  27. data/lib/odata/filter/sequel_datetime_adapter.rb +21 -0
  28. data/lib/odata/filter/token.rb +18 -5
  29. data/lib/odata/filter/tree.rb +83 -9
  30. data/lib/odata/function_import.rb +11 -9
  31. data/lib/odata/model_ext.rb +26 -29
  32. data/lib/odata/request/json.rb +171 -0
  33. data/lib/odata/transition.rb +2 -2
  34. data/lib/odata/url_parameters.rb +3 -3
  35. data/lib/odata/walker.rb +1 -1
  36. data/lib/safrano/multipart.rb +1 -3
  37. data/lib/safrano/rack_app.rb +2 -14
  38. data/lib/safrano/rack_builder.rb +0 -15
  39. data/lib/safrano/request.rb +3 -3
  40. data/lib/safrano/response.rb +3 -3
  41. data/lib/safrano/service.rb +13 -5
  42. data/lib/safrano/type_mapping.rb +4 -4
  43. data/lib/safrano/version.rb +1 -2
  44. data/lib/safrano.rb +3 -0
  45. metadata +51 -15
data/lib/odata/entity.rb CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  require 'json'
4
4
  require 'rexml/document'
5
- require 'safrano.rb'
6
- require 'odata/model_ext.rb' # required for self.class.entity_type_name ??
5
+ require 'safrano'
6
+ require 'odata/model_ext' # required for self.class.entity_type_name ??
7
7
  require_relative 'navigation_attribute'
8
8
 
9
9
  module Safrano
@@ -86,9 +86,9 @@ module Safrano
86
86
  "#{self.class.uri}#{@odata_pk}"
87
87
  end
88
88
 
89
- D = 'd'.freeze
90
- DJ_OPEN = '{"d":'.freeze
91
- DJ_CLOSE = '}'.freeze
89
+ D = 'd'
90
+ DJ_OPEN = '{"d":'
91
+ DJ_CLOSE = '}'
92
92
 
93
93
  # Json formatter for a single entity (probably OData V1/V2 like)
94
94
  def to_odata_json(request:)
@@ -176,7 +176,7 @@ module Safrano
176
176
  destroy(transaction: false)
177
177
  end
178
178
  rescue StandardError => e
179
- raise SequelAdapterError.new(e)
179
+ raise SequelAdapterError, e
180
180
  end
181
181
 
182
182
  # TODO: differentiate between POST/PUT/PATCH/MERGE
@@ -203,7 +203,7 @@ module Safrano
203
203
  if req.walker.media_value
204
204
  odata_media_value_put(req)
205
205
  elsif req.accept?(APPJSON)
206
- data = JSON.parse(req.body.read)
206
+ data = Safrano::OData::JSON.parse_one(req.body.read, self.class)
207
207
  data.delete('__metadata')
208
208
 
209
209
  if req.in_changeset
@@ -220,7 +220,7 @@ module Safrano
220
220
  end
221
221
 
222
222
  def odata_patch(req)
223
- req.with_parsed_data do |data|
223
+ req.with_parsed_data(self.class) do |data|
224
224
  data.delete('__metadata')
225
225
 
226
226
  # validate payload column names
@@ -401,9 +401,9 @@ module Safrano
401
401
  include Entity
402
402
  def pk_uri
403
403
  pku = +''
404
- self.class.odata_upk_parts.each_with_index { |upart, i|
404
+ self.class.odata_upk_parts.each_with_index do |upart, i|
405
405
  pku = "#{pku}#{upart}#{pk[i]}"
406
- }
406
+ end
407
407
  pku
408
408
  end
409
409
 
@@ -419,7 +419,7 @@ module Safrano
419
419
  module EntityCreateStandardOutput
420
420
  # Json formatter for a create entity POST call / Standard version; return as json object
421
421
  def to_odata_create_json(request:)
422
- # TODO Perf: reduce method call overhead
422
+ # TODO: Perf: reduce method call overhead
423
423
  # we added this redirection for readability and flexibility
424
424
  to_odata_json(request: request)
425
425
  end
@@ -428,7 +428,7 @@ module Safrano
428
428
  module EntityCreateArrayOutput
429
429
  # Json formatter for a create entity POST call Array version
430
430
  def to_odata_create_json(request:)
431
- # TODO Perf: reduce method call overhead
431
+ # TODO: Perf: reduce method call overhead
432
432
  # we added this redirection for readability and flexibility
433
433
  to_odata_array_json(request: request)
434
434
  end
data/lib/odata/error.rb CHANGED
@@ -59,12 +59,12 @@ module Safrano
59
59
  message = (m = @msg.to_s).empty? ? to_s : m
60
60
  if req.accept?(APPJSON)
61
61
  # json is default content type so we dont need to specify it here again
62
- [self.http_code, EMPTY_HASH,
63
- { 'odata.error' => { 'code' => "#{http_code}",
62
+ [http_code, EMPTY_HASH,
63
+ { 'odata.error' => { 'code' => http_code.to_s,
64
64
  'type' => to_s,
65
65
  'message' => message } }.to_json]
66
66
  else
67
- [self.http_code, CT_TEXT, message]
67
+ [http_code, CT_TEXT, message]
68
68
  end
69
69
  end
70
70
  end
@@ -82,8 +82,8 @@ module Safrano
82
82
  if req.accept?(APPJSON)
83
83
  # json is default content type so we dont need to specify it here again
84
84
  [self.class.http_code, EMPTY_HASH,
85
- { 'odata.error' => { 'code' => "#{self.class.http_code}",
86
- 'type' => "#{self.class}",
85
+ { 'odata.error' => { 'code' => self.class.http_code.to_s,
86
+ 'type' => self.class.to_s,
87
87
  'message' => message } }.to_json]
88
88
  else
89
89
  [self.class.http_code, CT_TEXT, message]
@@ -184,7 +184,7 @@ module Safrano
184
184
  class BadRequestSelectInvalidProps < BadRequestError
185
185
  include ErrorInstance
186
186
  def initialize(model, iprops)
187
- @msg = ((iprops.size > 1) ? "Bad Request: the $select properties #{iprops.to_a.join(', ')} are invalid for entityset #{model.entity_set_name}" : "Bad Request: the $select property #{iprops.first} is invalid for entityset #{model.entity_set_name}")
187
+ @msg = (iprops.size > 1 ? "Bad Request: the $select properties #{iprops.to_a.join(', ')} are invalid for entityset #{model.entity_set_name}" : "Bad Request: the $select property #{iprops.first} is invalid for entityset #{model.entity_set_name}")
188
188
  end
189
189
  end
190
190
 
@@ -232,7 +232,7 @@ module Safrano
232
232
  @msg = 'The requested OData version is not yet supported'
233
233
  end
234
234
  # batch not implemented (Safrano specific)
235
- class BatchNotImplementedError < NotImplementedError
235
+ class BatchNotImplementedError < RuntimeError
236
236
  @msg = 'Not implemented: OData batch'
237
237
  end
238
238
 
data/lib/odata/expand.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'odata/error.rb'
3
+ require 'odata/error'
4
4
 
5
5
  # all dataset expanding related classes in our OData module
6
6
  # ie do eager loading
@@ -52,7 +52,7 @@ module Safrano
52
52
  # [1] --> { 1 => {} }
53
53
  DEEPH_1 = ->(inp) { inp.size > 1 ? { inp[0] => DEEPH_1.call(inp[1..-1]) } : { inp[0] => {} } }
54
54
 
55
- NODESEP = '/'.freeze
55
+ NODESEP = '/'
56
56
 
57
57
  def initialize(exstr)
58
58
  exstr.strip!
@@ -49,10 +49,13 @@ module Safrano
49
49
  class ArgTree < Tree
50
50
  end
51
51
 
52
- # Numbers (floating point, ints, dec)
52
+ # Numbers (floating point, ints)
53
53
  class FPNumber < Leave
54
54
  end
55
55
 
56
+ class DecimalLit < Leave
57
+ end
58
+
56
59
  # Literals are unquoted words without /
57
60
  class Literal < Leave
58
61
  end
@@ -70,5 +73,11 @@ module Safrano
70
73
  # Quoted Strings
71
74
  class QString < Leave
72
75
  end
76
+
77
+ # DateTime Literals
78
+ class DateTimeLit < Leave
79
+ end
80
+ class DateTimeOffsetLit < Leave
81
+ end
73
82
  end
74
83
  end
@@ -16,7 +16,7 @@ module Safrano
16
16
  # Parser errors
17
17
 
18
18
  class Error
19
- def Error.http_code
19
+ def self.http_code
20
20
  const_get(:HTTP_CODE)
21
21
  end
22
22
  HTTP_CODE = 400
@@ -70,7 +70,7 @@ module Safrano
70
70
  include ::Safrano::ErrorInstance
71
71
  def initialize(tok, typ, cur)
72
72
  super
73
- @msg = "Bad Request: wrong number of parameters for function #{cur.parent.value.to_s} in $filter"
73
+ @msg = "Bad Request: wrong number of parameters for function #{cur.parent.value} in $filter"
74
74
  end
75
75
  end
76
76
  # Invalid separator in this context (missing parenthesis?)
@@ -174,7 +174,21 @@ module Safrano
174
174
  @cursor.update_state(tok, typ)
175
175
  grow_at_cursor(FPNumber.new(tok))
176
176
  end
177
-
177
+ when :DecimalLit
178
+ with_accepted(tok, typ) do
179
+ @cursor.update_state(tok, typ)
180
+ grow_at_cursor(DecimalLit.new(tok))
181
+ end
182
+ when :DateTimeLit
183
+ with_accepted(tok, typ) do
184
+ @cursor.update_state(tok, typ)
185
+ grow_at_cursor(DateTimeLit.new(tok))
186
+ end
187
+ when :DateTimeOffsetLit
188
+ with_accepted(tok, typ) do
189
+ @cursor.update_state(tok, typ)
190
+ grow_at_cursor(DateTimeOffsetLit.new(tok))
191
+ end
178
192
  when :unmatchedQuote
179
193
  break unmatched_quote_error(tok, typ)
180
194
 
@@ -188,7 +202,7 @@ module Safrano
188
202
  break(@error) if @error
189
203
  end
190
204
  (@error = @tree.check_types) unless @error
191
- @error ? @error : Contract.valid(@tree)
205
+ @error || Contract.valid(@tree)
192
206
  end
193
207
  end
194
208
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative './base'
4
4
  require_relative './sequel_function_adapter'
5
-
5
+ require_relative './sequel_datetime_adapter'
6
6
  module Safrano
7
7
  module Filter
8
8
  # Base class for Leaves, Trees, RootTrees etc
@@ -184,9 +184,10 @@ module Safrano
184
184
  Contract.collect_result!(@children[0].leuqes(jh),
185
185
  @children[1].leuqes(jh)) do |c0, c1|
186
186
  if c1 == NullLiteral::LEUQES
187
- if @value == :eq
187
+ case @value
188
+ when :eq
188
189
  leuqes_op = :IS
189
- elsif @value == :ne
190
+ when :ne
190
191
  leuqes_op = :'IS NOT'
191
192
  end
192
193
  end
@@ -242,6 +243,12 @@ module Safrano
242
243
  end
243
244
  end
244
245
 
246
+ class DecimalLit
247
+ def leuqes(_jh)
248
+ success Sequel.lit(@value)
249
+ end
250
+ end
251
+
245
252
  # Literals are unquoted words
246
253
  class Literal
247
254
  def leuqes(jh)
@@ -271,7 +278,7 @@ module Safrano
271
278
 
272
279
  # Null
273
280
  class NullLiteral
274
- def leuqes(jh)
281
+ def leuqes(_jh)
275
282
  # Sequel's representation of NULL
276
283
  success LEUQES
277
284
  end
@@ -304,5 +311,25 @@ module Safrano
304
311
  success "%#{@value}%"
305
312
  end
306
313
  end
314
+
315
+ # DateTime literals datetime'2017-04-15T00:00:00'
316
+ class DateTimeLit
317
+ # datetime method is defined dynamically by adapter-specific include on startup
318
+ # --> sequel_datetime_adapter.rb
319
+ def leuqes(_jh)
320
+ # success Sequel.function(:datetime, @value)
321
+ success datetime(@value)
322
+ end
323
+ end
324
+
325
+ # DateTimeOffset literals datetimeoffset'2017-04-15T00:00:00+02:00'
326
+ class DateTimeOffsetLit
327
+ # datetime method is defined dynamically by adapter-specific include on startup
328
+ # --> sequel_datetime_adapter.rb
329
+ def leuqes(_jh)
330
+ # success Sequel.function(:datetime, @value)
331
+ success datetime(@value)
332
+ end
333
+ end
307
334
  end
308
335
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './tree'
4
+ require_relative './sequel'
5
+
6
+ module Safrano
7
+ module Filter
8
+ # sqlite adapter specific DateTime handler
9
+ module DateTimeSqlite
10
+ def datetime(_val)
11
+ Sequel.function(:datetime, @value)
12
+ end
13
+ end
14
+ # non-sqlite adapter specific DateTime handler
15
+ module DateTimeDefault
16
+ def datetime(_val)
17
+ Sequel.lit("'#{@value}'")
18
+ end
19
+ end
20
+ end
21
+ end
@@ -15,9 +15,16 @@ module Safrano
15
15
  BINOBOOL = '[eE][qQ]|[LlgGNn][eETt]|[aA][nN][dD]|[oO][rR]'
16
16
  BINOARITHM = '[aA][dD][dD]|[sS][uU][bB]|[mM][uU][lL]|[dD][iI][vV]|[mM][oO][dD]'
17
17
  NOTRGX = 'not|NOT|Not'
18
- FPRGX = '\d+(?:\.\d+)?(?:e[+-]?\d+)?'
18
+ FPRGX = '\d+(?:\.\d+)?(?:e[+-]?\d+)?[df]?'
19
+ DECIMALRGX = '\d+(?:\.\d+)[mM]'
19
20
  QUALITRGX = '\w+(?:\/\w+)+'
20
- RGX = /(#{FUNCRGX})|(#{NULLRGX})|([\(\),])|(#{BINOBOOL})\s+|(#{BINOARITHM})|(#{NOTRGX})|#{QSTRINGRGX}|(#{FPRGX})|(#{QUALITRGX})|(\w+)|(')/.freeze
21
+ # datetime'yyyy-mm-ddThh:mm[:ss[.fffffff]]' NOTE: Spaces are not allowed between datetime and quoted portion.
22
+ # datetime is case-insensitive
23
+ DATIRGX = '\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?:\:\d{2})?(?:\.\d{1,7})?'
24
+ DATETIMERGX = /datetime'#{DATIRGX}[zZ]?'/i.freeze
25
+ DATIOFFRGX = /datetimeoffset'#{DATIRGX}(?:[zZ]|[+-]\d{2}:\d{2})'/.freeze
26
+
27
+ RGX = /(#{FUNCRGX})|(#{NULLRGX})|([(),])|(#{BINOBOOL})\s+|(#{BINOARITHM})|(#{NOTRGX})|#{QSTRINGRGX}|(#{DECIMALRGX})|(#{FPRGX})|(#{QUALITRGX})|(#{DATETIMERGX})|(#{DATIOFFRGX})|(\w+)|(')/.freeze
21
28
 
22
29
  def each_typed_token(inp)
23
30
  typ = nil
@@ -52,12 +59,18 @@ module Safrano
52
59
  when 6
53
60
  :QString
54
61
  when 7
55
- :FPNumber
62
+ :DecimalLit
56
63
  when 8
57
- :Qualit
64
+ :FPNumber
58
65
  when 9
59
- :Literal
66
+ :Qualit
60
67
  when 10
68
+ :DateTimeLit
69
+ when 11
70
+ :DateTimeOffsetLit
71
+ when 12
72
+ :Literal
73
+ when 13
61
74
  :unmatchedQuote
62
75
  end
63
76
  yield found, typ
@@ -45,6 +45,11 @@ module Safrano
45
45
  super(val, &block)
46
46
  end
47
47
 
48
+ # shortcut used for testing
49
+ def first_child_value
50
+ @children.first.value
51
+ end
52
+
48
53
  def attach(child)
49
54
  child.parent = self
50
55
  @children << child
@@ -66,7 +71,7 @@ module Safrano
66
71
  def accept?(tok, typ)
67
72
  case typ
68
73
  when :Literal, :NullLiteral, :Qualit, :QString, :FuncTree, :ArgTree,
69
- :UnopTree, :FPNumber
74
+ :UnopTree, :FPNumber, :DecimalLit, :DateTimeLit, :DateTimeOffsetLit
70
75
  nil
71
76
  when :Delimiter
72
77
  if tok == '('
@@ -232,7 +237,8 @@ module Safrano
232
237
 
233
238
  def update_state(_tok, typ)
234
239
  case typ
235
- when :Literal, :NullLiteral, :Qualit, :QString, :FuncTree, :BinopBool, :BinopArithm, :UnopTree, :FPNumber
240
+ when :Literal, :NullLiteral, :Qualit, :QString, :FuncTree, :BinopBool, :BinopArithm,
241
+ :UnopTree, :FPNumber, :DecimalLit, :DateTimeLit, :DateTimeOffsetLit
236
242
  @state = :closed
237
243
  end
238
244
  end
@@ -297,7 +303,8 @@ module Safrano
297
303
  @state = :closed
298
304
  when :Separator
299
305
  @state = :sep
300
- when :Literal, :NullLiteral, :Qualit, :QString, :FuncTree, :FPNumber
306
+ when :Literal, :NullLiteral, :Qualit, :QString, :FuncTree, :FPNumber, :DecimalLit,
307
+ :DateTimeLit, :DateTimeOffsetLit
301
308
  @state = :val
302
309
  end
303
310
  end
@@ -307,8 +314,8 @@ module Safrano
307
314
  case typ
308
315
  when :Delimiter
309
316
  if @value == '(' && tok == ')' && @state != :closed
310
- if (@parent.class == IdentityFuncTree) or
311
- (@parent.arity_full?(@children.size))
317
+ if (@parent.class == IdentityFuncTree) ||
318
+ @parent.arity_full?(@children.size)
312
319
 
313
320
  nil
314
321
  else
@@ -327,11 +334,10 @@ module Safrano
327
334
  elsif @state == :sep
328
335
  Parser::ErrorInvalidToken.new(tok, typ, self)
329
336
  end
330
- when :Literal, :NullLiteral, :Qualit, :QString, :FuncTree, :FPNumber
337
+ when :Literal, :NullLiteral, :Qualit, :QString, :FuncTree, :FPNumber, :DecimalLit,
338
+ :DateTimeLit, :DateTimeOffsetLit
331
339
  if (@state == :open) || (@state == :sep)
332
- if @parent.arity_full?(@children.size)
333
- Parser::ErrorInvalidArity.new(tok, typ, self)
334
- end
340
+ Parser::ErrorInvalidArity.new(tok, typ, self) if @parent.arity_full?(@children.size)
335
341
  else
336
342
  Parser::ErrorInvalidToken.new(tok, typ, self)
337
343
  end
@@ -349,6 +355,13 @@ module Safrano
349
355
 
350
356
  # Numbers (floating point, ints, dec)
351
357
  class FPNumber
358
+ def initialize(val)
359
+ # 1.53f --> value 1.53
360
+ # 1.53d --> value 1.53
361
+ # 1.53 --> value 1.53
362
+ val[-1] =~ /[fd]/i ? super(val[0..-2]) : super(val)
363
+ end
364
+
352
365
  def accept?(tok, typ)
353
366
  case typ
354
367
  when :Delimiter, :Separator, :BinopBool, :BinopArithm
@@ -363,7 +376,28 @@ module Safrano
363
376
  :number
364
377
  end
365
378
  end
379
+ class DecimalLit
380
+ def initialize(val)
381
+ # 1.53m --> value 1.53
382
+ # Warning, this assumes that the m|M part in the input is really not optional
383
+ # cf. DECIMALRGX in token.rb
384
+
385
+ super(val[0..-2])
386
+ end
387
+
388
+ def accept?(tok, typ)
389
+ case typ
390
+ when :Delimiter, :Separator, :BinopBool, :BinopArithm
391
+ nil
392
+ else
393
+ Parser::ErrorInvalidToken.new(tok, typ, self)
394
+ end
395
+ end
366
396
 
397
+ def edm_type
398
+ :decimal
399
+ end
400
+ end
367
401
  # Literals are unquoted words without /
368
402
  class Literal
369
403
  def accept?(tok, typ)
@@ -409,6 +443,46 @@ module Safrano
409
443
  end
410
444
  end
411
445
 
446
+ # DateTimeLit
447
+ class DateTimeLit
448
+ def initialize(val)
449
+ # datetime'2000-12-12T12:00:53' --> value 2000-12-12T12:00:53
450
+ super(val[9..-2])
451
+ end
452
+
453
+ def accept?(tok, typ)
454
+ case typ
455
+ when :Delimiter, :Separator, :BinopBool, :BinopArithm
456
+ nil
457
+ else
458
+ Parser::ErrorInvalidToken.new(tok, typ, self)
459
+ end
460
+ end
461
+
462
+ def edm_type
463
+ :datetime
464
+ end
465
+ end
466
+ # DateTimeOffsetLit
467
+ class DateTimeOffsetLit
468
+ def initialize(val)
469
+ # datetimeoffset'2000-12-12T12:00:53+02:00' --> value 2000-12-12T12:00:53+02:00
470
+ super(val[15..-2])
471
+ end
472
+
473
+ def accept?(tok, typ)
474
+ case typ
475
+ when :Delimiter, :Separator, :BinopBool, :BinopArithm
476
+ nil
477
+ else
478
+ Parser::ErrorInvalidToken.new(tok, typ, self)
479
+ end
480
+ end
481
+
482
+ def edm_type
483
+ :datetimeoffset
484
+ end
485
+ end
412
486
  # Quoted Strings
413
487
  class QString
414
488
  DBL_QO = "''"
@@ -84,14 +84,14 @@ module Safrano
84
84
  def check_missing_params
85
85
  # do we have all parameters provided ? use Set difference to check
86
86
  pkeys = @params.keys.map(&:to_sym).to_set
87
- unless (idiff = @input.keys.to_set - pkeys).empty?
87
+ if (idiff = @input.keys.to_set - pkeys).empty?
88
+ Contract::OK
89
+ else
88
90
 
89
91
  Safrano::ServiceOperationParameterMissing.new(
90
92
  missing: idiff.to_a,
91
93
  sopname: @name
92
94
  )
93
- else
94
- Contract::OK
95
95
  end
96
96
  end
97
97
 
@@ -134,12 +134,14 @@ module Safrano
134
134
  # EntitySet= @entity_set ,
135
135
  'ReturnType' => @returning.type_metadata,
136
136
  'm:HttpMethod' => @http_method)
137
- @input.each do |iname, type|
138
- funky.add_element('Parameter',
139
- 'Name' => iname.to_s,
140
- 'Type' => type.type_name,
141
- 'Mode' => 'In')
142
- end if @input
137
+ if @input
138
+ @input.each do |iname, type|
139
+ funky.add_element('Parameter',
140
+ 'Name' => iname.to_s,
141
+ 'Type' => type.type_name,
142
+ 'Mode' => 'In')
143
+ end
144
+ end
143
145
  funky
144
146
  end
145
147