apipie-rails 0.3.6 → 0.9.1

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