apipie-rails 0.3.6 → 0.5.17

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +5 -5
  2. data/.travis.yml +23 -7
  3. data/CHANGELOG.md +147 -2
  4. data/Gemfile +1 -0
  5. data/Gemfile.rails41 +2 -0
  6. data/Gemfile.rails42 +10 -1
  7. data/Gemfile.rails50 +9 -0
  8. data/Gemfile.rails51 +9 -0
  9. data/Gemfile.rails60 +14 -0
  10. data/PROPOSAL_FOR_RESPONSE_DESCRIPTIONS.md +244 -0
  11. data/README.rst +570 -17
  12. data/apipie-rails.gemspec +3 -3
  13. data/app/controllers/apipie/apipies_controller.rb +48 -17
  14. data/app/views/apipie/apipies/_method_detail.erb +21 -0
  15. data/app/views/apipie/apipies/_params.html.erb +4 -2
  16. data/app/views/apipie/apipies/index.html.erb +5 -1
  17. data/app/views/apipie/apipies/resource.html.erb +3 -0
  18. data/app/views/layouts/apipie/apipie.html.erb +1 -1
  19. data/config/locales/en.yml +1 -0
  20. data/config/locales/fr.yml +31 -0
  21. data/config/locales/it.yml +31 -0
  22. data/config/locales/ja.yml +31 -0
  23. data/lib/apipie/apipie_module.rb +22 -4
  24. data/lib/apipie/application.rb +55 -28
  25. data/lib/apipie/configuration.rb +19 -3
  26. data/lib/apipie/core_ext/route.rb +9 -0
  27. data/lib/apipie/dsl_definition.rb +151 -10
  28. data/lib/apipie/error_description.rb +9 -2
  29. data/lib/apipie/errors.rb +34 -0
  30. data/lib/apipie/extractor/collector.rb +4 -0
  31. data/lib/apipie/extractor/recorder.rb +13 -12
  32. data/lib/apipie/extractor/writer.rb +83 -55
  33. data/lib/apipie/extractor.rb +10 -4
  34. data/lib/apipie/method_description.rb +51 -4
  35. data/lib/apipie/param_description.rb +56 -2
  36. data/lib/apipie/resource_description.rb +10 -3
  37. data/lib/apipie/response_description.rb +131 -0
  38. data/lib/apipie/response_description_adapter.rb +200 -0
  39. data/lib/apipie/routes_formatter.rb +1 -1
  40. data/lib/apipie/rspec/response_validation_helper.rb +194 -0
  41. data/lib/apipie/static_dispatcher.rb +3 -2
  42. data/lib/apipie/swagger_generator.rb +708 -0
  43. data/lib/apipie/tag_list_description.rb +11 -0
  44. data/lib/apipie/validator.rb +69 -8
  45. data/lib/apipie/version.rb +1 -1
  46. data/lib/apipie-rails.rb +7 -0
  47. data/lib/tasks/apipie.rake +103 -8
  48. data/spec/controllers/apipies_controller_spec.rb +52 -12
  49. data/spec/controllers/concerns_controller_spec.rb +2 -2
  50. data/spec/controllers/extended_controller_spec.rb +14 -0
  51. data/spec/controllers/memes_controller_spec.rb +10 -0
  52. data/spec/controllers/users_controller_spec.rb +115 -75
  53. data/spec/dummy/app/controllers/application_controller.rb +5 -1
  54. data/spec/dummy/app/controllers/concerns/extending_concern.rb +12 -0
  55. data/spec/dummy/app/controllers/concerns/sample_controller.rb +5 -5
  56. data/spec/dummy/app/controllers/extended_controller.rb +14 -0
  57. data/spec/dummy/app/controllers/pets_controller.rb +408 -0
  58. data/spec/dummy/app/controllers/pets_using_auto_views_controller.rb +73 -0
  59. data/spec/dummy/app/controllers/pets_using_self_describing_classes_controller.rb +95 -0
  60. data/spec/dummy/app/controllers/tagged_cats_controller.rb +32 -0
  61. data/spec/dummy/app/controllers/tagged_dogs_controller.rb +15 -0
  62. data/spec/dummy/app/controllers/twitter_example_controller.rb +5 -0
  63. data/spec/dummy/app/controllers/users_controller.rb +19 -11
  64. data/spec/dummy/components/test_engine/Gemfile +6 -0
  65. data/spec/dummy/components/test_engine/app/controllers/test_engine/application_controller.rb +4 -0
  66. data/spec/dummy/components/test_engine/app/controllers/test_engine/memes_controller.rb +37 -0
  67. data/spec/dummy/components/test_engine/config/routes.rb +3 -0
  68. data/spec/dummy/components/test_engine/db/.gitkeep +0 -0
  69. data/spec/dummy/components/test_engine/lib/test_engine.rb +7 -0
  70. data/spec/dummy/components/test_engine/test_engine.gemspec +11 -0
  71. data/spec/dummy/config/application.rb +5 -0
  72. data/spec/dummy/config/environments/development.rb +3 -0
  73. data/spec/dummy/config/environments/production.rb +3 -0
  74. data/spec/dummy/config/environments/test.rb +3 -0
  75. data/spec/dummy/config/initializers/apipie.rb +3 -1
  76. data/spec/dummy/config/routes.rb +24 -1
  77. data/spec/lib/extractor/writer_spec.rb +32 -4
  78. data/spec/lib/file_handler_spec.rb +18 -0
  79. data/spec/lib/method_description_spec.rb +34 -0
  80. data/spec/lib/swagger/openapi_2_0_schema.json +1607 -0
  81. data/spec/lib/swagger/rake_swagger_spec.rb +139 -0
  82. data/spec/lib/swagger/response_validation_spec.rb +104 -0
  83. data/spec/lib/swagger/swagger_dsl_spec.rb +658 -0
  84. data/spec/lib/validator_spec.rb +58 -0
  85. data/spec/lib/validators/array_validator_spec.rb +28 -8
  86. data/spec/spec_helper.rb +68 -0
  87. metadata +75 -23
  88. data/Gemfile +0 -7
  89. data/Gemfile.rails32 +0 -6
  90. data/Gemfile.rails40 +0 -5
  91. data/lib/apipie/client/generator.rb +0 -135
@@ -0,0 +1,708 @@
1
+ module Apipie
2
+
3
+ #--------------------------------------------------------------------------
4
+ # Configuration. Should be moved to Apipie config.
5
+ #--------------------------------------------------------------------------
6
+ class SwaggerGenerator
7
+ require 'json'
8
+ require 'ostruct'
9
+ require 'open3'
10
+ require 'zlib' if Apipie.configuration.swagger_generate_x_computed_id_field?
11
+
12
+ attr_reader :computed_interface_id
13
+
14
+ def initialize(apipie)
15
+ @apipie = apipie
16
+ @issued_warnings = []
17
+ end
18
+
19
+ def params_in_body?
20
+ Apipie.configuration.swagger_content_type_input == :json
21
+ end
22
+
23
+ def params_in_body_use_reference?
24
+ Apipie.configuration.swagger_json_input_uses_refs
25
+ end
26
+
27
+ def responses_use_reference?
28
+ Apipie.configuration.swagger_responses_use_refs?
29
+ end
30
+
31
+ def include_warning_tags?
32
+ Apipie.configuration.swagger_include_warning_tags
33
+ end
34
+
35
+
36
+ def generate_from_resources(version, resources, method_name, lang, clear_warnings=false)
37
+ init_swagger_vars(version, lang, clear_warnings)
38
+
39
+ @lang = lang
40
+ @only_method = method_name
41
+ add_resources(resources)
42
+
43
+ @swagger[:info]["x-computed-id"] = @computed_interface_id if Apipie.configuration.swagger_generate_x_computed_id_field?
44
+ return @swagger
45
+ end
46
+
47
+
48
+ #--------------------------------------------------------------------------
49
+ # Initialization
50
+ #--------------------------------------------------------------------------
51
+
52
+ def init_swagger_vars(version, lang, clear_warnings=false)
53
+
54
+ # docs = {
55
+ # :name => Apipie.configuration.app_name,
56
+ # :info => Apipie.app_info(version, lang),
57
+ # :copyright => Apipie.configuration.copyright,
58
+ # :doc_url => Apipie.full_url(url_args),
59
+ # :api_url => Apipie.api_base_url(version),
60
+ # :resources => _resources
61
+ # }
62
+
63
+
64
+ @swagger = {
65
+ swagger: '2.0',
66
+ info: {
67
+ title: "#{Apipie.configuration.app_name}",
68
+ description: "#{Apipie.app_info(version, lang)}#{Apipie.configuration.copyright}",
69
+ version: "#{version}",
70
+ "x-copyright" => Apipie.configuration.copyright,
71
+ },
72
+ basePath: Apipie.api_base_url(version),
73
+ consumes: [],
74
+ paths: {},
75
+ definitions: {},
76
+ tags: [],
77
+ }
78
+
79
+ if Apipie.configuration.swagger_api_host
80
+ @swagger[:host] = Apipie.configuration.swagger_api_host
81
+ end
82
+
83
+ if params_in_body?
84
+ @swagger[:consumes] = ['application/json']
85
+ @swagger[:info][:title] += " (params in:body)"
86
+ else
87
+ @swagger[:consumes] = ['application/x-www-form-urlencoded', 'multipart/form-data']
88
+ @swagger[:info][:title] += " (params in:formData)"
89
+ end
90
+
91
+ @paths = @swagger[:paths]
92
+ @definitions = @swagger[:definitions]
93
+ @tags = @swagger[:tags]
94
+
95
+ @issued_warnings = [] if clear_warnings || @issued_warnings.nil?
96
+ @computed_interface_id = 0
97
+
98
+ @current_lang = lang
99
+ end
100
+
101
+ #--------------------------------------------------------------------------
102
+ # Engine interface methods
103
+ #--------------------------------------------------------------------------
104
+
105
+ def add_resources(resources)
106
+ resources.each do |resource_name, resource_defs|
107
+ add_resource_description(resource_name, resource_defs)
108
+ add_resource_methods(resource_name, resource_defs)
109
+ end
110
+ end
111
+
112
+ def add_resource_methods(resource_name, resource_defs)
113
+ resource_defs._methods.each do |apipie_method_name, apipie_method_defs|
114
+ add_ruby_method(@paths, apipie_method_defs)
115
+ end
116
+ end
117
+
118
+
119
+ #--------------------------------------------------------------------------
120
+ # Logging, debugging and regression-testing utilities
121
+ #--------------------------------------------------------------------------
122
+
123
+ def ruby_name_for_method(method)
124
+ return "<no method>" if method.nil?
125
+ method.resource.controller.name + "#" + method.method
126
+ end
127
+
128
+
129
+ def warn_missing_method_summary() warn 100, "missing short description for method"; end
130
+ def warn_added_missing_slash(path) warn 101,"added missing / at beginning of path: #{path}"; end
131
+ def warn_no_return_codes_specified() warn 102,"no return codes ('errors') specified"; end
132
+ def warn_hash_without_internal_typespec(param_name) warn 103,"the parameter :#{param_name} is a generic Hash without an internal type specification"; end
133
+ def warn_optional_param_in_path(param_name) warn 104, "the parameter :#{param_name} is 'in-path'. Ignoring 'not required' in DSL"; end
134
+ def warn_optional_without_default_value(param_name) warn 105,"the parameter :#{param_name} is optional but default value is not specified (use :default_value => ...)"; end
135
+ def warn_param_ignored_in_form_data(param_name) warn 106,"ignoring param :#{param_name} -- cannot include Hash without fields in a formData specification"; end
136
+ def warn_path_parameter_not_described(name,path) warn 107,"the parameter :#{name} appears in the path #{path} but is not described"; end
137
+ def warn_inferring_boolean(name) warn 108,"the parameter [#{name}] is Enum with [true,false] values. Inferring 'boolean'"; end
138
+
139
+ def warn(warning_num, msg)
140
+ suppress = Apipie.configuration.swagger_suppress_warnings
141
+ return if suppress == true
142
+ return if suppress.is_a?(Array) && suppress.include?(warning_num)
143
+
144
+ method_id = ruby_name_for_method(@current_method)
145
+ warning_id = "#{method_id}#{warning_num}#{msg}"
146
+
147
+ if @issued_warnings.include?(warning_id)
148
+ # Already issued this warning for the current method
149
+ return
150
+ end
151
+
152
+ print "WARNING (#{warning_num}): [#{method_id}] -- #{msg}\n"
153
+ @issued_warnings.push(warning_id)
154
+ @warnings_issued = true
155
+ end
156
+
157
+ def info(msg)
158
+ print "--- INFO: [#{ruby_name_for_method(@current_method)}] -- #{msg}\n"
159
+ end
160
+
161
+
162
+ # the @computed_interface_id is a number that is uniquely derived from the list of operations
163
+ # added to the swagger definition (in an order-dependent way).
164
+ # it can be used for regression testing, allowing some differentiation between changes that
165
+ # result from changes to the input and those that result from changes to the generation
166
+ # algorithms.
167
+ # note that at the moment, this only takes operation ids into account, and ignores parameter
168
+ # definitions, so it's only partially useful.
169
+ def include_op_id_in_computed_interface_id(op_id)
170
+ @computed_interface_id = Zlib::crc32("#{@computed_interface_id} #{op_id}") if Apipie.configuration.swagger_generate_x_computed_id_field?
171
+ end
172
+
173
+ #--------------------------------------------------------------------------
174
+ # Create a tag description for a described resource
175
+ #--------------------------------------------------------------------------
176
+
177
+ def tag_name_for_resource(resource)
178
+ # resource.controller
179
+ resource._id
180
+ end
181
+
182
+ def add_resource_description(resource_name, resource)
183
+ if resource._full_description
184
+ @tags << {
185
+ name: tag_name_for_resource(resource),
186
+ description: Apipie.app.translate(resource._full_description, @current_lang)
187
+ }
188
+ end
189
+ end
190
+
191
+ #--------------------------------------------------------------------------
192
+ # Create swagger definitions for a ruby method
193
+ #--------------------------------------------------------------------------
194
+
195
+ def add_ruby_method(paths, ruby_method)
196
+
197
+ if @only_method
198
+ return unless ruby_method.method == @only_method
199
+ else
200
+ return if !ruby_method.show
201
+ end
202
+
203
+ for api in ruby_method.apis do
204
+ # controller: ruby_method.resource.controller.name,
205
+
206
+ path = swagger_path(api.path)
207
+ paths[path] ||= {}
208
+ methods = paths[path]
209
+ @current_method = ruby_method
210
+
211
+ @warnings_issued = false
212
+ responses = swagger_responses_hash_for_method(ruby_method)
213
+ if include_warning_tags?
214
+ warning_tags = @warnings_issued ? ['warnings issued'] : []
215
+ else
216
+ warning_tags = []
217
+ end
218
+
219
+ op_id = swagger_op_id_for_path(api.http_method, api.path)
220
+
221
+ include_op_id_in_computed_interface_id(op_id)
222
+
223
+ method_key = api.http_method.downcase
224
+ @current_http_method = method_key
225
+
226
+ methods[method_key] = {
227
+ tags: [tag_name_for_resource(ruby_method.resource)] + warning_tags + ruby_method.tag_list.tags,
228
+ consumes: params_in_body? ? ['application/json'] : ['application/x-www-form-urlencoded', 'multipart/form-data'],
229
+ operationId: op_id,
230
+ summary: Apipie.app.translate(api.short_description, @current_lang),
231
+ parameters: swagger_params_array_for_method(ruby_method, api.path),
232
+ responses: responses,
233
+ description: ruby_method.full_description
234
+ }
235
+
236
+ if methods[method_key][:summary].nil?
237
+ methods[method_key].delete(:summary)
238
+ warn_missing_method_summary
239
+ end
240
+ end
241
+ end
242
+
243
+ #--------------------------------------------------------------------------
244
+ # Utilities for conversion of ruby syntax to swagger syntax
245
+ #--------------------------------------------------------------------------
246
+
247
+ def swagger_path(str)
248
+ str = str.gsub(/:(\w+)/, '{\1}')
249
+ str = str.gsub(/\/$/, '')
250
+
251
+ if str[0] != '/'
252
+ warn_added_missing_slash(str)
253
+ str = '/' + str
254
+ end
255
+ str
256
+ end
257
+
258
+ def remove_colons(str)
259
+ str.gsub(":", "_")
260
+ end
261
+
262
+ def swagger_op_id_for_method(method)
263
+ remove_colons method.resource.controller.name + "::" + method.method
264
+ end
265
+
266
+ def swagger_id_for_typename(typename)
267
+ typename
268
+ end
269
+
270
+ def swagger_op_id_for_path(http_method, path)
271
+ # using lowercase http method, because the 'swagger-codegen' tool outputs
272
+ # strange method names if the http method is in uppercase
273
+ http_method.downcase + path.gsub(/\//,'_').gsub(/:(\w+)/, '\1').gsub(/_$/,'')
274
+ end
275
+
276
+ class SwaggerTypeWithFormat
277
+ attr_reader :str_format
278
+ def initialize(type, str_format)
279
+ @type = type
280
+ @str_format = str_format
281
+ end
282
+
283
+ def to_s
284
+ @type
285
+ end
286
+
287
+ def ==(other)
288
+ other.to_s == self.to_s
289
+ end
290
+ end
291
+
292
+ def lookup
293
+ @lookup ||= {
294
+ numeric: "number",
295
+ hash: "object",
296
+ array: "array",
297
+
298
+ # see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types
299
+ integer: SwaggerTypeWithFormat.new("integer", "int32"),
300
+ long: SwaggerTypeWithFormat.new("integer", "int64"),
301
+ number: SwaggerTypeWithFormat.new("number", nil), # here just for completeness
302
+ float: SwaggerTypeWithFormat.new("number", "float"),
303
+ double: SwaggerTypeWithFormat.new("number", "double"),
304
+ string: SwaggerTypeWithFormat.new("string", nil), # here just for completeness
305
+ byte: SwaggerTypeWithFormat.new("string", "byte"),
306
+ binary: SwaggerTypeWithFormat.new("string", "binary"),
307
+ boolean: SwaggerTypeWithFormat.new("boolean", nil), # here just for completeness
308
+ date: SwaggerTypeWithFormat.new("string", "date"),
309
+ dateTime: SwaggerTypeWithFormat.new("string", "date-time"),
310
+ password: SwaggerTypeWithFormat.new("string", "password"),
311
+ }
312
+ end
313
+
314
+
315
+ def swagger_param_type(param_desc)
316
+ if param_desc.nil?
317
+ raise("problem")
318
+ end
319
+
320
+ v = param_desc.validator
321
+ if v.nil?
322
+ return "string"
323
+ end
324
+
325
+ if v.class == Apipie::Validator::EnumValidator || (v.respond_to?(:is_enum?) && v.is_enum?)
326
+ if v.values - [true, false] == [] && [true, false] - v.values == []
327
+ warn_inferring_boolean(param_desc.name)
328
+ return "boolean"
329
+ else
330
+ return "enum"
331
+ end
332
+ elsif v.class == Apipie::Validator::HashValidator
333
+ # pp v
334
+ end
335
+
336
+
337
+ return lookup[v.expected_type.to_sym] || v.expected_type
338
+ end
339
+
340
+
341
+ #--------------------------------------------------------------------------
342
+ # Responses
343
+ #--------------------------------------------------------------------------
344
+
345
+ def json_schema_for_method_response(method, return_code, allow_nulls)
346
+ @definitions = {}
347
+ for response in method.returns
348
+ if response.code.to_s == return_code.to_s
349
+ schema = response_schema(response, allow_nulls) if response.code.to_s == return_code.to_s
350
+ schema[:definitions] = @definitions if @definitions != {}
351
+ return schema
352
+ end
353
+ end
354
+ nil
355
+ end
356
+
357
+ def json_schema_for_self_describing_class(cls, allow_nulls)
358
+ adapter = ResponseDescriptionAdapter.from_self_describing_class(cls)
359
+ response_schema(adapter, allow_nulls)
360
+ end
361
+
362
+ def response_schema(response, allow_nulls=false)
363
+ begin
364
+ # no need to warn about "missing default value for optional param" when processing response definitions
365
+ prev_value = @disable_default_value_warning
366
+ @disable_default_value_warning = true
367
+
368
+ if responses_use_reference? && response.typename
369
+ schema = {"$ref" => gen_referenced_block_from_params_array(swagger_id_for_typename(response.typename), response.params_ordered, allow_nulls)}
370
+ else
371
+ schema = json_schema_obj_from_params_array(response.params_ordered, allow_nulls)
372
+ end
373
+
374
+ ensure
375
+ @disable_default_value_warning = prev_value
376
+ end
377
+
378
+ if response.is_array? && schema
379
+ schema = {
380
+ type: allow_nulls ? ["array","null"] : "array",
381
+ items: schema
382
+ }
383
+ end
384
+
385
+ if response.allow_additional_properties
386
+ schema[:additionalProperties] = true
387
+ end
388
+
389
+ schema
390
+ end
391
+
392
+ def swagger_responses_hash_for_method(method)
393
+ result = {}
394
+
395
+ for error in method.errors
396
+ error_block = {description: Apipie.app.translate(error.description, @current_lang)}
397
+ result[error.code] = error_block
398
+ end
399
+
400
+ for response in method.returns
401
+ swagger_response_block = {
402
+ description: response.description
403
+ }
404
+
405
+ schema = response_schema(response)
406
+ swagger_response_block[:schema] = schema if schema
407
+
408
+ result[response.code] = swagger_response_block
409
+ end
410
+
411
+ if result.length == 0
412
+ warn_no_return_codes_specified
413
+ result[200] = {description: 'ok'}
414
+ end
415
+
416
+ result
417
+ end
418
+
419
+
420
+
421
+ #--------------------------------------------------------------------------
422
+ # Auto-insertion of parameters that are implicitly defined in the path
423
+ #--------------------------------------------------------------------------
424
+
425
+ def param_names_from_path(path)
426
+ path.scan(/:(\w+)/).map do |ar|
427
+ ar[0].to_sym
428
+ end
429
+ end
430
+
431
+ def add_missing_params(method, path)
432
+ param_names_from_method = method.params.map {|name, desc| name}
433
+ missing = param_names_from_path(path) - param_names_from_method
434
+
435
+ result = method.params
436
+
437
+ missing.each do |name|
438
+ warn_path_parameter_not_described(name, path)
439
+ result[name.to_sym] = OpenStruct.new({
440
+ required: true,
441
+ _gen_added_from_path: true,
442
+ name: name,
443
+ validator: Apipie::Validator::NumberValidator.new(nil),
444
+ options: {
445
+ in: "path"
446
+ }
447
+ })
448
+ end
449
+
450
+ result
451
+ end
452
+
453
+ #--------------------------------------------------------------------------
454
+ # The core routine for creating a swagger parameter definition block.
455
+ # The output is slightly different when the parameter is inside a schema block.
456
+ #--------------------------------------------------------------------------
457
+ def swagger_atomic_param(param_desc, in_schema, name, allow_nulls)
458
+ def save_field(entry, openapi_key, v, apipie_key=openapi_key, translate=false)
459
+ if v.key?(apipie_key)
460
+ if translate
461
+ entry[openapi_key] = Apipie.app.translate(v[apipie_key], @current_lang)
462
+ else
463
+ entry[openapi_key] = v[apipie_key]
464
+ end
465
+ end
466
+ end
467
+
468
+ swagger_def = {}
469
+ swagger_def[:name] = name if !name.nil?
470
+
471
+ swg_param_type = swagger_param_type(param_desc)
472
+ swagger_def[:type] = swg_param_type.to_s
473
+ if (swg_param_type.is_a? SwaggerTypeWithFormat) && !swg_param_type.str_format.nil?
474
+ swagger_def[:format] = swg_param_type.str_format
475
+ end
476
+
477
+ if swagger_def[:type] == "array"
478
+ swagger_def[:items] = {type: "string"}
479
+ end
480
+
481
+ if swagger_def[:type] == "enum"
482
+ swagger_def[:type] = "string"
483
+ swagger_def[:enum] = param_desc.validator.values
484
+ end
485
+
486
+ if swagger_def[:type] == "object" # we only get here if there is no specification of properties for this object
487
+ swagger_def[:additionalProperties] = true
488
+ warn_hash_without_internal_typespec(param_desc.name)
489
+ end
490
+
491
+ if param_desc.is_array?
492
+ new_swagger_def = {
493
+ items: swagger_def,
494
+ type: 'array'
495
+ }
496
+ swagger_def = new_swagger_def
497
+ if allow_nulls
498
+ swagger_def[:type] = [swagger_def[:type], "null"]
499
+ end
500
+ end
501
+
502
+ if allow_nulls
503
+ swagger_def[:type] = [swagger_def[:type], "null"]
504
+ end
505
+
506
+ if !in_schema
507
+ swagger_def[:in] = param_desc.options.fetch(:in, @default_value_for_param_in)
508
+ swagger_def[:required] = param_desc.required if param_desc.required
509
+ end
510
+
511
+ save_field(swagger_def, :description, param_desc.options, :desc, true) unless param_desc.options[:desc].nil?
512
+ save_field(swagger_def, :default, param_desc.options, :default_value)
513
+
514
+ if param_desc.respond_to?(:_gen_added_from_path) && !param_desc.required
515
+ warn_optional_param_in_path(param_desc.name)
516
+ swagger_def[:required] = true
517
+ end
518
+
519
+ if !swagger_def[:required] && !swagger_def.key?(:default)
520
+ warn_optional_without_default_value(param_desc.name) unless @disable_default_value_warning
521
+ end
522
+
523
+ swagger_def
524
+ end
525
+
526
+
527
+ #--------------------------------------------------------------------------
528
+ # JSON schema and referenced-object generation
529
+ #--------------------------------------------------------------------------
530
+
531
+ def ref_to(name)
532
+ "#/definitions/#{name}"
533
+ end
534
+
535
+
536
+ def json_schema_obj_from_params_array(params_array, allow_nulls = false)
537
+ (param_defs, required_params) = json_schema_param_defs_from_params_array(params_array, allow_nulls)
538
+
539
+ result = {type: "object"}
540
+ result[:properties] = param_defs
541
+ result[:additionalProperties] = false unless Apipie.configuration.swagger_allow_additional_properties_in_response
542
+ result[:required] = required_params if required_params.length > 0
543
+
544
+ param_defs.length > 0 ? result : nil
545
+ end
546
+
547
+ def gen_referenced_block_from_params_array(name, params_array, allow_nulls=false)
548
+ return ref_to(:name) if @definitions.key(:name)
549
+
550
+ schema_obj = json_schema_obj_from_params_array(params_array, allow_nulls)
551
+ return nil if schema_obj.nil?
552
+
553
+ @definitions[name.to_sym] = schema_obj
554
+ ref_to(name.to_sym)
555
+ end
556
+
557
+ def json_schema_param_defs_from_params_array(params_array, allow_nulls = false)
558
+ param_defs = {}
559
+ required_params = []
560
+
561
+ params_array ||= []
562
+
563
+
564
+ for param_desc in params_array
565
+ if !param_desc.respond_to?(:required)
566
+ # pp param_desc
567
+ raise ("unexpected param_desc format")
568
+ end
569
+
570
+ required_params.push(param_desc.name.to_sym) if param_desc.required
571
+
572
+ param_type = swagger_param_type(param_desc)
573
+
574
+ if param_type == "object" && param_desc.validator.params_ordered
575
+ schema = json_schema_obj_from_params_array(param_desc.validator.params_ordered, allow_nulls)
576
+ if param_desc.additional_properties
577
+ schema[:additionalProperties] = true
578
+ end
579
+
580
+ if param_desc.is_array?
581
+ new_schema = {
582
+ type: 'array',
583
+ items: schema
584
+ }
585
+ schema = new_schema
586
+ end
587
+
588
+ if allow_nulls
589
+ # ideally we would write schema[:type] = ["object", "null"]
590
+ # but due to a bug in the json-schema gem, we need to use anyOf
591
+ # see https://github.com/ruby-json-schema/json-schema/issues/404
592
+ new_schema = {
593
+ anyOf: [schema, {type: "null"}]
594
+ }
595
+ schema = new_schema
596
+ end
597
+ param_defs[param_desc.name.to_sym] = schema if !schema.nil?
598
+ else
599
+ param_defs[param_desc.name.to_sym] = swagger_atomic_param(param_desc, true, nil, allow_nulls)
600
+ end
601
+ end
602
+
603
+ [param_defs, required_params]
604
+ end
605
+
606
+
607
+
608
+ #--------------------------------------------------------------------------
609
+ # swagger "Params" block generation
610
+ #--------------------------------------------------------------------------
611
+
612
+ def body_allowed_for_current_method
613
+ !(['get', 'head'].include?(@current_http_method))
614
+ end
615
+
616
+ def swagger_params_array_for_method(method, path)
617
+
618
+ swagger_result = []
619
+ all_params_hash = add_missing_params(method, path)
620
+
621
+ body_param_defs_array = all_params_hash.map {|k, v| v if !param_names_from_path(path).include?(k)}.select{|v| !v.nil?}
622
+ body_param_defs_hash = all_params_hash.select {|k, v| v if !param_names_from_path(path).include?(k)}
623
+ path_param_defs_hash = all_params_hash.select {|k, v| v if param_names_from_path(path).include?(k)}
624
+
625
+ path_param_defs_hash.each{|name,desc| desc.required = true}
626
+ add_params_from_hash(swagger_result, path_param_defs_hash, nil, "path")
627
+
628
+ if params_in_body? && body_allowed_for_current_method
629
+ if params_in_body_use_reference?
630
+ swagger_schema_for_body = {"$ref" => gen_referenced_block_from_params_array("#{swagger_op_id_for_method(method)}_input", body_param_defs_array)}
631
+ else
632
+ swagger_schema_for_body = json_schema_obj_from_params_array(body_param_defs_array)
633
+ end
634
+
635
+ swagger_body_param = {
636
+ name: 'body',
637
+ in: 'body',
638
+ schema: swagger_schema_for_body
639
+ }
640
+ swagger_result.push(swagger_body_param) if !swagger_schema_for_body.nil?
641
+
642
+ else
643
+ add_params_from_hash(swagger_result, body_param_defs_hash)
644
+ end
645
+
646
+ add_headers_from_hash(swagger_result, method.headers) if method.headers.present?
647
+
648
+ swagger_result
649
+ end
650
+
651
+
652
+ def add_headers_from_hash(swagger_params_array, headers)
653
+ swagger_headers = headers.map do |header|
654
+ header_hash = {
655
+ name: header[:name],
656
+ in: 'header',
657
+ required: header[:options][:required],
658
+ description: header[:description],
659
+ type: header[:options][:type] || 'string'
660
+ }
661
+ header_hash[:default] = header[:options][:default] if header[:options][:default]
662
+ header_hash
663
+ end
664
+ swagger_params_array.push(*swagger_headers)
665
+ end
666
+
667
+
668
+ def add_params_from_hash(swagger_params_array, param_defs, prefix=nil, default_value_for_in=nil)
669
+
670
+ if default_value_for_in
671
+ @default_value_for_param_in = default_value_for_in
672
+ else
673
+ if body_allowed_for_current_method
674
+ @default_value_for_param_in = "formData"
675
+ else
676
+ @default_value_for_param_in = "query"
677
+ end
678
+ end
679
+
680
+
681
+ param_defs.each do |name, desc|
682
+
683
+ if !prefix.nil?
684
+ name = "#{prefix}[#{name}]"
685
+ end
686
+
687
+ if swagger_param_type(desc) == "object"
688
+ if desc.validator.params_ordered
689
+ params_hash = Hash[desc.validator.params_ordered.map {|desc| [desc.name, desc]}]
690
+ add_params_from_hash(swagger_params_array, params_hash, name)
691
+ else
692
+ warn_param_ignored_in_form_data(desc.name)
693
+ end
694
+ else
695
+ param_entry = swagger_atomic_param(desc, false, name, false)
696
+ if param_entry[:required]
697
+ swagger_params_array.unshift(param_entry)
698
+ else
699
+ swagger_params_array.push(param_entry)
700
+ end
701
+
702
+ end
703
+ end
704
+ end
705
+
706
+ end
707
+
708
+ end