apipie-rails 0.5.6 → 0.5.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +6 -1
- data/CHANGELOG.md +8 -0
- data/Gemfile +1 -1
- data/README.rst +105 -0
- data/apipie-rails.gemspec +1 -0
- data/app/controllers/apipie/apipies_controller.rb +18 -2
- data/lib/apipie-rails.rb +1 -0
- data/lib/apipie/apipie_module.rb +5 -0
- data/lib/apipie/application.rb +23 -3
- data/lib/apipie/configuration.rb +13 -2
- data/lib/apipie/extractor.rb +2 -4
- data/lib/apipie/swagger_generator.rb +545 -0
- data/lib/apipie/validator.rb +4 -0
- data/lib/apipie/version.rb +1 -1
- data/lib/tasks/apipie.rake +95 -1
- data/spec/controllers/apipies_controller_spec.rb +38 -0
- data/spec/dummy/app/controllers/users_controller.rb +5 -0
- data/spec/dummy/config/routes.rb +5 -0
- data/spec/lib/swagger/openapi_2_0_schema.json +1607 -0
- data/spec/lib/swagger/rake_swagger_spec.rb +127 -0
- metadata +22 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c873515d61e1046750c0ecd967b30f289ac9e3ee
|
4
|
+
data.tar.gz: 1bfaf2d71ca3b995045cf2be2c75e6cf846e25fa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 56d02246c13676a616393bd28a25e297e757aa95ce3b41f6e4f6914cb966afa71ade2eb49ca3b53a39fbbccac3b81bd840e212ef9479de687d628d128626d8d3
|
7
|
+
data.tar.gz: 238da70119f0ce29b87ad54b9e0dd903aac41314c79095c75799aeb7226b7ef2abaf78b361e72e7821281dd3e1c21e60a7f5952f51fcff4cb016705ebd993e95
|
data/.travis.yml
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
language: ruby
|
2
2
|
sudo: false
|
3
3
|
rvm:
|
4
|
-
- 2.0.0
|
5
4
|
- 2.1.7
|
6
5
|
- 2.2.3
|
7
6
|
- 2.3.3
|
7
|
+
- 2.4.3
|
8
|
+
- 2.5.0
|
8
9
|
gemfile:
|
9
10
|
- Gemfile.rails41
|
10
11
|
- Gemfile.rails42
|
@@ -20,3 +21,7 @@ matrix:
|
|
20
21
|
gemfile: Gemfile.rails50
|
21
22
|
- rvm: 2.1.7
|
22
23
|
gemfile: Gemfile.rails51
|
24
|
+
- rvm: 2.4.3
|
25
|
+
gemfile: Gemfile.rails41
|
26
|
+
- rvm: 2.5.0
|
27
|
+
gemfile: Gemfile.rails41
|
data/CHANGELOG.md
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
Changelog
|
2
2
|
===========
|
3
3
|
|
4
|
+
v0.5.7
|
5
|
+
------
|
6
|
+
|
7
|
+
- Fix example recording with Rails 5 [\#607](https://github.com/Apipie/apipie-rails/pull/607) ([adamruzicka](https://github.com/adamruzicka))
|
8
|
+
- Use SHA1 instead of MD5 to enable using APIPIE at FIPS-enables systems [\#605](https://github.com/Apipie/apipie-rails/pull/605) ([iNecas](https://github.com/iNecas))
|
9
|
+
- Replaced String\#constantize with String\#safe\_constantize so apipie won't break on a missing constant [\#575](https://github.com/Apipie/apipie-rails/pull/575) ([Haniyya](https://github.com/Haniyya))
|
10
|
+
- Added Swagger generation [\#569](https://github.com/Apipie/apipie-rails/pull/569) ([elasti-ron](https://github.com/elasti-ron))
|
11
|
+
|
4
12
|
v0.5.6
|
5
13
|
------
|
6
14
|
|
data/Gemfile
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
Gemfile.rails50
|
data/README.rst
CHANGED
@@ -1154,6 +1154,111 @@ If, for some complex cases, you need to generate/re-generate just part of the ca
|
|
1154
1154
|
use ``rake apipie:cache cache_part=index`` resp. ``rake apipie:cache cache_part=resources``
|
1155
1155
|
To generate it for different locations for further processing use ``rake apipie:cache OUT=/tmp/apipie_cache``.
|
1156
1156
|
|
1157
|
+
====================================
|
1158
|
+
Static Swagger (OpenAPI 2.0) files
|
1159
|
+
====================================
|
1160
|
+
|
1161
|
+
To generate a static Swagger definition file from the api, run ``rake apipie:static_swagger_json``.
|
1162
|
+
By default the documentation for the default API version is
|
1163
|
+
used. You can specify the version with ``rake apipie:static_swagger_json[2.0]``. A swagger file will be
|
1164
|
+
generated for each locale. The files will be generated in the same location as the static_json files, but
|
1165
|
+
instead of being named ``schema_apipie[.locale].json``, they will be called ``schema_swagger[.locale].json``.
|
1166
|
+
|
1167
|
+
Specifying default values for parameters
|
1168
|
+
-----------------------------------------
|
1169
|
+
Swagger allows method definitions to include an indication of the the default value for each parameter. To include such
|
1170
|
+
indications, use ``:default_value => <some value>`` in the parameter definition DSL. For example:
|
1171
|
+
|
1172
|
+
.. code:: ruby
|
1173
|
+
|
1174
|
+
param :do_something, Boolean, :desc => "take an action", :required => false, :default_value => false
|
1175
|
+
|
1176
|
+
|
1177
|
+
Generated Warnings
|
1178
|
+
-------------------
|
1179
|
+
The help identify potential improvements to your documentation, the swagger generation process issues warnings if
|
1180
|
+
it identifies various shortcomings of the DSL documentation. Each warning has a code to allow selective suppression
|
1181
|
+
(see swagger-specific configuration below)
|
1182
|
+
|
1183
|
+
:100: missing short description for method
|
1184
|
+
:101: added missing / at beginning of path
|
1185
|
+
:102: no return codes specified for method
|
1186
|
+
:103: a parameter is a generic Hash without an internal type specification
|
1187
|
+
:104: a parameter is an 'in-path' parameter, but specified as 'not required' in the DSL
|
1188
|
+
:105: a parameter is optional but does not have a default value specified
|
1189
|
+
:106: a parameter was ommitted from the swagger output because it is a Hash without fields in a formData specification
|
1190
|
+
:107: a path parameter is not described
|
1191
|
+
:108: inferring that a parameter type is boolean because described as an enum with [false,true] values
|
1192
|
+
|
1193
|
+
|
1194
|
+
|
1195
|
+
Swagger-Specific Configuration Parameters
|
1196
|
+
-------------------------------------------------
|
1197
|
+
|
1198
|
+
There are several configuration parameters that determine the structure of the generated swagger file:
|
1199
|
+
|
1200
|
+
``config.swagger_content_type_input``
|
1201
|
+
If the value is ``:form_data`` - the swagger file will indicate that the server consumes the content types
|
1202
|
+
``application/x-www-form-urlencoded`` and ``multipart/form-data``. Non-path parameters will have the
|
1203
|
+
value ``"in": "formData"``. Note that parameters of type Hash that do not have any fields in them will *be ommitted*
|
1204
|
+
from the resulting files, as there is no way to describe them in swagger.
|
1205
|
+
|
1206
|
+
If the value is ``:json`` - the swagger file will indicate that the server consumes the content type
|
1207
|
+
``application/json``. All non-path parameters will be included in the schema of a single ``"in": "body"`` parameter
|
1208
|
+
of type ``object``.
|
1209
|
+
|
1210
|
+
You can specify the value of this configuration parameter as an additional input to the rake command (e.g.,
|
1211
|
+
``rake apipie:static_swagger_json[2.0,form_data]``).
|
1212
|
+
|
1213
|
+
``config.swagger_json_input_uses_refs``
|
1214
|
+
This parameter is only relevant if ``swagger_content_type_input`` is ``:json``.
|
1215
|
+
|
1216
|
+
If ``true``: the schema of the ``"in": "body"`` parameter of each method is given its own entry in the ``definitions``
|
1217
|
+
section, and is referenced using ``$ref`` from the method definition.
|
1218
|
+
|
1219
|
+
If ``false``: the body parameter definitions are inlined within the method definitions.
|
1220
|
+
|
1221
|
+
``config.swagger_include_warning_tags``
|
1222
|
+
If ``true``: in addition to tagging methods with the name of the resource they belong to, methods for which warnings
|
1223
|
+
have been issued will be tagged with.
|
1224
|
+
|
1225
|
+
``config.swagger_suppress_warnings``
|
1226
|
+
If ``false``: no warnings will be suppressed
|
1227
|
+
|
1228
|
+
If ``true``: all warnings will be suppressed
|
1229
|
+
|
1230
|
+
If an array of values (e.g., ``[100,102,107]``), only the warnings identified by the numbers in the array will be suppressed.
|
1231
|
+
|
1232
|
+
``config.swagger_api_host``
|
1233
|
+
The value to place in the swagger host field.
|
1234
|
+
|
1235
|
+
Default is ``localhost:3000``
|
1236
|
+
|
1237
|
+
If ``nil`` then then host field will not be included.
|
1238
|
+
|
1239
|
+
|
1240
|
+
|
1241
|
+
Known limitations of the current implementation
|
1242
|
+
-------------------------------------------------
|
1243
|
+
* There is currently no way to document the structure and content-type of the data returned from a method
|
1244
|
+
* Recorded examples are currently not included in the generated swagger file
|
1245
|
+
* The apipie ``formats`` value is ignored.
|
1246
|
+
* It is not possible to specify the "consumed" content type on a per-method basis
|
1247
|
+
* It is not possible to leverage all of the parameter type/format capabilities of swagger
|
1248
|
+
* Only OpenAPI 2.0 is supported
|
1249
|
+
|
1250
|
+
====================================
|
1251
|
+
Dynamic Swagger generation
|
1252
|
+
====================================
|
1253
|
+
|
1254
|
+
To generate swagger dynamically, use ``http://localhost:3000/apipie.json?type=swagger``.
|
1255
|
+
|
1256
|
+
Note that authorization is not supported for dynamic swagger generation, so if ``config.authorize`` is defined,
|
1257
|
+
dynamic swagger generation will be disabled.
|
1258
|
+
|
1259
|
+
Dynamically generated swagger is not cached, and is always generated on the fly.
|
1260
|
+
|
1261
|
+
|
1157
1262
|
===================
|
1158
1263
|
JSON checksums
|
1159
1264
|
===================
|
data/apipie-rails.gemspec
CHANGED
@@ -14,11 +14,17 @@ module Apipie
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
+
|
17
18
|
def index
|
18
19
|
params[:version] ||= Apipie.configuration.default_version
|
19
20
|
|
20
21
|
get_format
|
21
22
|
|
23
|
+
if params[:type].to_s == 'swagger' && params[:format].to_s == 'json'
|
24
|
+
head :forbidden and return if Apipie.configuration.authorize
|
25
|
+
should_render_swagger = true
|
26
|
+
end
|
27
|
+
|
22
28
|
respond_to do |format|
|
23
29
|
|
24
30
|
if Apipie.configuration.use_cache?
|
@@ -31,9 +37,19 @@ module Apipie
|
|
31
37
|
Apipie.load_documentation if Apipie.configuration.reload_controllers? || (Rails.version.to_i >= 4.0 && !Rails.application.config.eager_load)
|
32
38
|
|
33
39
|
I18n.locale = @language
|
34
|
-
@doc = Apipie.to_json(params[:version], params[:resource], params[:method], @language)
|
35
40
|
|
36
|
-
|
41
|
+
if should_render_swagger
|
42
|
+
prev_warning_value = Apipie.configuration.swagger_suppress_warnings
|
43
|
+
begin
|
44
|
+
Apipie.configuration.swagger_suppress_warnings = true
|
45
|
+
@doc = Apipie.to_swagger_json(params[:version], params[:resource], params[:method], @language)
|
46
|
+
ensure
|
47
|
+
Apipie.configuration.swagger_suppress_warnings = prev_warning_value
|
48
|
+
end
|
49
|
+
else
|
50
|
+
@doc = Apipie.to_json(params[:version], params[:resource], params[:method], @language)
|
51
|
+
@doc = authorized_doc
|
52
|
+
end
|
37
53
|
|
38
54
|
format.json do
|
39
55
|
if @doc
|
data/lib/apipie-rails.rb
CHANGED
@@ -17,6 +17,7 @@ require "apipie/validator"
|
|
17
17
|
require "apipie/railtie"
|
18
18
|
require 'apipie/extractor'
|
19
19
|
require "apipie/version"
|
20
|
+
require "apipie/swagger_generator"
|
20
21
|
|
21
22
|
if Rails.version.start_with?("3.0")
|
22
23
|
warn 'Warning: apipie-rails is not going to support Rails 3.0 anymore in future versions'
|
data/lib/apipie/apipie_module.rb
CHANGED
@@ -13,6 +13,11 @@ module Apipie
|
|
13
13
|
app.to_json(version, resource_name, method_name, lang)
|
14
14
|
end
|
15
15
|
|
16
|
+
def self.to_swagger_json(version = nil, resource_name = nil, method_name = nil, lang = nil, clear_warnings=true)
|
17
|
+
version ||= Apipie.configuration.default_version
|
18
|
+
app.to_swagger_json(version, resource_name, method_name, lang, clear_warnings)
|
19
|
+
end
|
20
|
+
|
16
21
|
# all calls delegated to Apipie::Application instance
|
17
22
|
def self.method_missing(method, *args, &block)
|
18
23
|
app.respond_to?(method) ? app.send(method, *args, &block) : super
|
data/lib/apipie/application.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'apipie/static_dispatcher'
|
2
2
|
require 'apipie/routes_formatter'
|
3
3
|
require 'yaml'
|
4
|
-
require 'digest/
|
4
|
+
require 'digest/sha1'
|
5
5
|
require 'json'
|
6
6
|
|
7
7
|
module Apipie
|
@@ -55,7 +55,8 @@ module Apipie
|
|
55
55
|
# this method does in depth search for the route controller
|
56
56
|
def route_app_controller(app, route, visited_apps = [])
|
57
57
|
if route.defaults[:controller]
|
58
|
-
(route.defaults[:controller]
|
58
|
+
controller_name = (route.defaults[:controller] + 'Controller').camelize
|
59
|
+
controller_name.safe_constantize
|
59
60
|
end
|
60
61
|
end
|
61
62
|
|
@@ -239,6 +240,7 @@ module Apipie
|
|
239
240
|
@resource_descriptions ||= HashWithIndifferentAccess.new { |h, version| h[version] = {} }
|
240
241
|
@controller_to_resource_id ||= {}
|
241
242
|
@param_groups ||= {}
|
243
|
+
@swagger_generator = Apipie::SwaggerGenerator.new(self)
|
242
244
|
|
243
245
|
# what versions does the controller belong in (specified by resource_description)?
|
244
246
|
@controller_versions ||= Hash.new { |h, controller| h[controller.to_s] = [] }
|
@@ -253,6 +255,24 @@ module Apipie
|
|
253
255
|
@recorded_examples = nil
|
254
256
|
end
|
255
257
|
|
258
|
+
def to_swagger_json(version, resource_name, method_name, lang, clear_warnings=false)
|
259
|
+
return unless valid_search_args?(version, resource_name, method_name)
|
260
|
+
|
261
|
+
# if resource_name is blank, take just resources which have some methods because
|
262
|
+
# we dont want to show eg ApplicationController as resource
|
263
|
+
# otherwise, take only the specified resource
|
264
|
+
_resources = resource_descriptions[version].inject({}) do |result, (k,v)|
|
265
|
+
if resource_name.blank?
|
266
|
+
result[k] = v unless v._methods.blank?
|
267
|
+
else
|
268
|
+
result[k] = v if k == resource_name
|
269
|
+
end
|
270
|
+
result
|
271
|
+
end
|
272
|
+
|
273
|
+
@swagger_generator.generate_from_resources(version,_resources, method_name, lang, clear_warnings)
|
274
|
+
end
|
275
|
+
|
256
276
|
def to_json(version, resource_name, method_name, lang)
|
257
277
|
|
258
278
|
return unless valid_search_args?(version, resource_name, method_name)
|
@@ -321,7 +341,7 @@ module Apipie
|
|
321
341
|
all.update(version => Apipie.to_json(version))
|
322
342
|
end
|
323
343
|
end
|
324
|
-
Digest::
|
344
|
+
Digest::SHA1.hexdigest(JSON.dump(all_docs))
|
325
345
|
end
|
326
346
|
|
327
347
|
def checksum
|
data/lib/apipie/configuration.rb
CHANGED
@@ -7,11 +7,16 @@ module Apipie
|
|
7
7
|
:validate, :validate_value, :validate_presence, :validate_key, :authenticate, :doc_path,
|
8
8
|
:show_all_examples, :process_params, :update_checksum, :checksum_path,
|
9
9
|
:link_extension, :record, :languages, :translate, :locale, :default_locale,
|
10
|
-
:persist_show_in_doc, :authorize
|
10
|
+
:persist_show_in_doc, :authorize,
|
11
|
+
:swagger_include_warning_tags, :swagger_content_type_input, :swagger_json_input_uses_refs,
|
12
|
+
:swagger_suppress_warnings, :swagger_api_host, :swagger_generate_x_computed_id_field
|
11
13
|
|
12
14
|
alias_method :validate?, :validate
|
13
15
|
alias_method :required_by_default?, :required_by_default
|
14
16
|
alias_method :namespaced_resources?, :namespaced_resources
|
17
|
+
alias_method :swagger_include_warning_tags?, :swagger_include_warning_tags
|
18
|
+
alias_method :swagger_json_input_uses_refs?, :swagger_json_input_uses_refs
|
19
|
+
alias_method :swagger_generate_x_computed_id_field?, :swagger_generate_x_computed_id_field
|
15
20
|
|
16
21
|
# matcher to be used in Dir.glob to find controllers to be reloaded e.g.
|
17
22
|
#
|
@@ -108,7 +113,7 @@ module Apipie
|
|
108
113
|
# the line above the docs.
|
109
114
|
attr_writer :generated_doc_disclaimer
|
110
115
|
def generated_doc_disclaimer
|
111
|
-
@generated_doc_disclaimer ||= "# DOC GENERATED AUTOMATICALLY: REMOVE THIS LINE TO PREVENT
|
116
|
+
@generated_doc_disclaimer ||= "# DOC GENERATED AUTOMATICALLY: REMOVE THIS LINE TO PREVENT REGENERATING NEXT TIME"
|
112
117
|
end
|
113
118
|
|
114
119
|
def use_disqus?
|
@@ -165,6 +170,12 @@ module Apipie
|
|
165
170
|
@translate = lambda { |str, locale| str }
|
166
171
|
@persist_show_in_doc = false
|
167
172
|
@routes_formatter = RoutesFormatter.new
|
173
|
+
@swagger_content_type_input = :form_data # this can be :json or :form_data
|
174
|
+
@swagger_json_input_uses_refs = false
|
175
|
+
@swagger_include_warning_tags = false
|
176
|
+
@swagger_suppress_warnings = false #[105,100,102]
|
177
|
+
@swagger_api_host = "localhost:3000"
|
178
|
+
@swagger_generate_x_computed_id_field = false
|
168
179
|
end
|
169
180
|
end
|
170
181
|
end
|
data/lib/apipie/extractor.rb
CHANGED
@@ -16,9 +16,7 @@ class Apipie::Railtie
|
|
16
16
|
end
|
17
17
|
end
|
18
18
|
app.middleware.use ::Apipie::Extractor::Recorder::Middleware
|
19
|
-
ActionController::TestCase
|
20
|
-
prepend Apipie::Extractor::Recorder::FunctionalTestRecording
|
21
|
-
end
|
19
|
+
ActionController::TestCase.send(:prepend, Apipie::Extractor::Recorder::FunctionalTestRecording)
|
22
20
|
end
|
23
21
|
end
|
24
22
|
|
@@ -154,7 +152,7 @@ module Apipie
|
|
154
152
|
def update_api_descriptions
|
155
153
|
apis_from_docs = all_apis_from_docs
|
156
154
|
@apis_from_routes.each do |(controller, action), new_apis|
|
157
|
-
method_key = "#{Apipie.get_resource_name(controller.
|
155
|
+
method_key = "#{Apipie.get_resource_name(controller.safe_constantize || next)}##{action}"
|
158
156
|
old_apis = apis_from_docs[method_key] || []
|
159
157
|
new_apis.each do |new_api|
|
160
158
|
new_api[:path].sub!(/\(\.:format\)$/,"") if new_api[:path]
|
@@ -0,0 +1,545 @@
|
|
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
|
+
end
|
17
|
+
|
18
|
+
def params_in_body?
|
19
|
+
Apipie.configuration.swagger_content_type_input == :json
|
20
|
+
end
|
21
|
+
|
22
|
+
def params_in_body_use_reference?
|
23
|
+
Apipie.configuration.swagger_json_input_uses_refs
|
24
|
+
end
|
25
|
+
|
26
|
+
def include_warning_tags?
|
27
|
+
Apipie.configuration.swagger_include_warning_tags
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
def generate_from_resources(version, resources, method_name, lang, clear_warnings=false)
|
32
|
+
init_swagger_vars(version, lang, clear_warnings)
|
33
|
+
|
34
|
+
@lang = lang
|
35
|
+
@only_method = method_name
|
36
|
+
add_resources(resources)
|
37
|
+
|
38
|
+
@swagger[:info]["x-computed-id"] = @computed_interface_id if Apipie.configuration.swagger_generate_x_computed_id_field?
|
39
|
+
return @swagger
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
#--------------------------------------------------------------------------
|
44
|
+
# Initialization
|
45
|
+
#--------------------------------------------------------------------------
|
46
|
+
|
47
|
+
def init_swagger_vars(version, lang, clear_warnings=false)
|
48
|
+
|
49
|
+
# docs = {
|
50
|
+
# :name => Apipie.configuration.app_name,
|
51
|
+
# :info => Apipie.app_info(version, lang),
|
52
|
+
# :copyright => Apipie.configuration.copyright,
|
53
|
+
# :doc_url => Apipie.full_url(url_args),
|
54
|
+
# :api_url => Apipie.api_base_url(version),
|
55
|
+
# :resources => _resources
|
56
|
+
# }
|
57
|
+
|
58
|
+
|
59
|
+
@swagger = {
|
60
|
+
swagger: '2.0',
|
61
|
+
info: {
|
62
|
+
title: "#{Apipie.configuration.app_name}",
|
63
|
+
description: "#{Apipie.app_info(version, lang)}#{Apipie.configuration.copyright}",
|
64
|
+
version: "#{version}",
|
65
|
+
"x-copyright" => Apipie.configuration.copyright,
|
66
|
+
},
|
67
|
+
basePath: Apipie.api_base_url(version),
|
68
|
+
consumes: [],
|
69
|
+
paths: {},
|
70
|
+
definitions: {},
|
71
|
+
tags: [],
|
72
|
+
}
|
73
|
+
|
74
|
+
if Apipie.configuration.swagger_api_host
|
75
|
+
@swagger[:host] = Apipie.configuration.swagger_api_host
|
76
|
+
end
|
77
|
+
|
78
|
+
if params_in_body?
|
79
|
+
@swagger[:consumes] = ['application/json']
|
80
|
+
@swagger[:info][:title] += " (params in:body)"
|
81
|
+
else
|
82
|
+
@swagger[:consumes] = ['application/x-www-form-urlencoded', 'multipart/form-data']
|
83
|
+
@swagger[:info][:title] += " (params in:formData)"
|
84
|
+
end
|
85
|
+
|
86
|
+
@paths = @swagger[:paths]
|
87
|
+
@definitions = @swagger[:definitions]
|
88
|
+
@tags = @swagger[:tags]
|
89
|
+
|
90
|
+
@issued_warnings = [] if clear_warnings || @issued_warnings.nil?
|
91
|
+
@computed_interface_id = 0
|
92
|
+
|
93
|
+
@current_lang = lang
|
94
|
+
end
|
95
|
+
|
96
|
+
#--------------------------------------------------------------------------
|
97
|
+
# Engine interface methods
|
98
|
+
#--------------------------------------------------------------------------
|
99
|
+
|
100
|
+
def add_resources(resources)
|
101
|
+
resources.each do |resource_name, resource_defs|
|
102
|
+
add_resource_description(resource_name, resource_defs)
|
103
|
+
add_resource_methods(resource_name, resource_defs)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def add_resource_methods(resource_name, resource_defs)
|
108
|
+
resource_defs._methods.each do |apipie_method_name, apipie_method_defs|
|
109
|
+
add_ruby_method(@paths, apipie_method_defs)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
#--------------------------------------------------------------------------
|
115
|
+
# Logging, debugging and regression-testing utilities
|
116
|
+
#--------------------------------------------------------------------------
|
117
|
+
|
118
|
+
def ruby_name_for_method(method)
|
119
|
+
return "<no method>" if method.nil?
|
120
|
+
method.resource.controller.name + "#" + method.method
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
def warn_missing_method_summary() warn 100, "missing short description for method"; end
|
125
|
+
def warn_added_missing_slash(path) warn 101,"added missing / at beginning of path: #{path}"; end
|
126
|
+
def warn_no_return_codes_specified() warn 102,"no return codes ('errors') specified"; end
|
127
|
+
def warn_hash_without_internal_typespec(param_name) warn 103,"the parameter :#{param_name} is a generic Hash without an internal type specification"; end
|
128
|
+
def warn_optional_param_in_path(param_name) warn 104, "the parameter :#{param_name} is 'in-path'. Ignoring 'not required' in DSL"; end
|
129
|
+
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
|
130
|
+
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
|
131
|
+
def warn_path_parameter_not_described(name,path) warn 107,"the parameter :#{name} appears in the path #{path} but is not described"; end
|
132
|
+
def warn_inferring_boolean(name) warn 108,"the parameter [#{name}] is Enum with [true,false] values. Inferring 'boolean'"; end
|
133
|
+
|
134
|
+
def warn(warning_num, msg)
|
135
|
+
suppress = Apipie.configuration.swagger_suppress_warnings
|
136
|
+
return if suppress == true
|
137
|
+
return if suppress.is_a?(Array) && suppress.include?(warning_num)
|
138
|
+
|
139
|
+
method_id = ruby_name_for_method(@current_method)
|
140
|
+
warning_id = "#{method_id}#{warning_num}#{msg}"
|
141
|
+
|
142
|
+
if @issued_warnings.include?(warning_id)
|
143
|
+
# Already issued this warning for the current method
|
144
|
+
return
|
145
|
+
end
|
146
|
+
|
147
|
+
print "WARNING (#{warning_num}): [#{method_id}] -- #{msg}\n"
|
148
|
+
@issued_warnings.push(warning_id)
|
149
|
+
@warnings_issued = true
|
150
|
+
end
|
151
|
+
|
152
|
+
def info(msg)
|
153
|
+
print "--- INFO: [#{ruby_name_for_method(@current_method)}] -- #{msg}\n"
|
154
|
+
end
|
155
|
+
|
156
|
+
|
157
|
+
# the @computed_interface_id is a number that is uniquely derived from the list of operations
|
158
|
+
# added to the swagger definition (in an order-dependent way).
|
159
|
+
# it can be used for regression testing, allowing some differentiation between changes that
|
160
|
+
# result from changes to the input and those that result from changes to the generation
|
161
|
+
# algorithms.
|
162
|
+
# note that at the moment, this only takes operation ids into account, and ignores parameter
|
163
|
+
# definitions, so it's only partially useful.
|
164
|
+
def include_op_id_in_computed_interface_id(op_id)
|
165
|
+
@computed_interface_id = Zlib::crc32("#{@computed_interface_id} #{op_id}") if Apipie.configuration.swagger_generate_x_computed_id_field?
|
166
|
+
end
|
167
|
+
|
168
|
+
#--------------------------------------------------------------------------
|
169
|
+
# Create a tag description for a described resource
|
170
|
+
#--------------------------------------------------------------------------
|
171
|
+
|
172
|
+
def tag_name_for_resource(resource)
|
173
|
+
# resource.controller
|
174
|
+
resource._id
|
175
|
+
end
|
176
|
+
|
177
|
+
def add_resource_description(resource_name, resource)
|
178
|
+
if resource._full_description
|
179
|
+
@tags << {
|
180
|
+
name: tag_name_for_resource(resource),
|
181
|
+
description: Apipie.app.translate(resource._full_description, @current_lang)
|
182
|
+
}
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
#--------------------------------------------------------------------------
|
187
|
+
# Create swagger definitions for a ruby method
|
188
|
+
#--------------------------------------------------------------------------
|
189
|
+
|
190
|
+
def add_ruby_method(paths, ruby_method)
|
191
|
+
|
192
|
+
if @only_method
|
193
|
+
return unless ruby_method.method == @only_method
|
194
|
+
else
|
195
|
+
return if !ruby_method.show
|
196
|
+
end
|
197
|
+
|
198
|
+
for api in ruby_method.apis do
|
199
|
+
# controller: ruby_method.resource.controller.name,
|
200
|
+
|
201
|
+
path = swagger_path(api.path)
|
202
|
+
paths[path] ||= {}
|
203
|
+
methods = paths[path]
|
204
|
+
@current_method = ruby_method
|
205
|
+
|
206
|
+
@warnings_issued = false
|
207
|
+
responses = swagger_responses_hash_for_method(ruby_method)
|
208
|
+
if include_warning_tags?
|
209
|
+
warning_tags = @warnings_issued ? ['warnings issued'] : []
|
210
|
+
else
|
211
|
+
warning_tags = []
|
212
|
+
end
|
213
|
+
|
214
|
+
op_id = swagger_op_id_for_path(api.http_method, api.path)
|
215
|
+
|
216
|
+
include_op_id_in_computed_interface_id(op_id)
|
217
|
+
|
218
|
+
method_key = api.http_method.downcase
|
219
|
+
@current_http_method = method_key
|
220
|
+
|
221
|
+
methods[method_key] = {
|
222
|
+
tags: [tag_name_for_resource(ruby_method.resource)] + warning_tags,
|
223
|
+
operationId: op_id,
|
224
|
+
summary: Apipie.app.translate(api.short_description, @current_lang),
|
225
|
+
parameters: swagger_params_array_for_method(ruby_method, api.path),
|
226
|
+
responses: responses
|
227
|
+
}
|
228
|
+
|
229
|
+
if methods[method_key][:summary].nil?
|
230
|
+
methods[method_key].delete(:summary)
|
231
|
+
warn_missing_method_summary
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
#--------------------------------------------------------------------------
|
237
|
+
# Utilities for conversion of ruby syntax to swagger syntax
|
238
|
+
#--------------------------------------------------------------------------
|
239
|
+
|
240
|
+
def swagger_path(str)
|
241
|
+
str = str.gsub(/:(\w+)/, '{\1}')
|
242
|
+
str = str.gsub(/\/$/, '')
|
243
|
+
|
244
|
+
if str[0] != '/'
|
245
|
+
warn_added_missing_slash(str)
|
246
|
+
str = '/' + str
|
247
|
+
end
|
248
|
+
str
|
249
|
+
end
|
250
|
+
|
251
|
+
def remove_colons(str)
|
252
|
+
str.gsub(":", "_")
|
253
|
+
end
|
254
|
+
|
255
|
+
def swagger_op_id_for_method(method)
|
256
|
+
remove_colons method.resource.controller.name + "::" + method.method
|
257
|
+
end
|
258
|
+
|
259
|
+
def swagger_op_id_for_path(http_method, path)
|
260
|
+
# using lowercase http method, because the 'swagger-codegen' tool outputs
|
261
|
+
# strange method names if the http method is in uppercase
|
262
|
+
http_method.downcase + path.gsub(/\//,'_').gsub(/:(\w+)/, '\1').gsub(/_$/,'')
|
263
|
+
end
|
264
|
+
|
265
|
+
def swagger_param_type(param_desc)
|
266
|
+
if param_desc.nil?
|
267
|
+
raise("problem")
|
268
|
+
end
|
269
|
+
|
270
|
+
v = param_desc.validator
|
271
|
+
if v.nil?
|
272
|
+
return "string"
|
273
|
+
end
|
274
|
+
|
275
|
+
if v.class == Apipie::Validator::EnumValidator
|
276
|
+
if v.values - [true, false] == [] && [true, false] - v.values == []
|
277
|
+
warn_inferring_boolean(param_desc.name)
|
278
|
+
return "boolean"
|
279
|
+
else
|
280
|
+
return "enum"
|
281
|
+
end
|
282
|
+
elsif v.class == Apipie::Validator::HashValidator
|
283
|
+
# pp v
|
284
|
+
end
|
285
|
+
|
286
|
+
lookup = {
|
287
|
+
numeric: "number",
|
288
|
+
hash: "object",
|
289
|
+
array: "array"
|
290
|
+
}
|
291
|
+
|
292
|
+
return lookup[v.expected_type.to_sym] || v.expected_type
|
293
|
+
end
|
294
|
+
|
295
|
+
|
296
|
+
#--------------------------------------------------------------------------
|
297
|
+
# Responses
|
298
|
+
#--------------------------------------------------------------------------
|
299
|
+
|
300
|
+
def swagger_responses_hash_for_method(method)
|
301
|
+
result = {}
|
302
|
+
|
303
|
+
for error in method.errors
|
304
|
+
error_block = {description: Apipie.app.translate(error.description, @current_lang)}
|
305
|
+
result[error.code] = error_block
|
306
|
+
end
|
307
|
+
|
308
|
+
if result.length == 0
|
309
|
+
warn_no_return_codes_specified
|
310
|
+
result[200] = {description: 'ok'}
|
311
|
+
end
|
312
|
+
|
313
|
+
result
|
314
|
+
end
|
315
|
+
|
316
|
+
|
317
|
+
|
318
|
+
#--------------------------------------------------------------------------
|
319
|
+
# Auto-insertion of parameters that are implicitly defined in the path
|
320
|
+
#--------------------------------------------------------------------------
|
321
|
+
|
322
|
+
def param_names_from_path(path)
|
323
|
+
path.scan(/:(\w+)/).map do |ar|
|
324
|
+
ar[0].to_sym
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
def add_missing_params(method, path)
|
329
|
+
param_names_from_method = method.params.map {|name, desc| name}
|
330
|
+
missing = param_names_from_path(path) - param_names_from_method
|
331
|
+
|
332
|
+
result = method.params
|
333
|
+
|
334
|
+
missing.each do |name|
|
335
|
+
warn_path_parameter_not_described(name, path)
|
336
|
+
result[name.to_sym] = OpenStruct.new({
|
337
|
+
required: true,
|
338
|
+
_gen_added_from_path: true,
|
339
|
+
name: name,
|
340
|
+
validator: Apipie::Validator::NumberValidator.new(nil),
|
341
|
+
options: {
|
342
|
+
in: "path"
|
343
|
+
}
|
344
|
+
})
|
345
|
+
end
|
346
|
+
|
347
|
+
result
|
348
|
+
end
|
349
|
+
|
350
|
+
#--------------------------------------------------------------------------
|
351
|
+
# The core routine for creating a swagger parameter definition block.
|
352
|
+
# The output is slightly different when the parameter is inside a schema block.
|
353
|
+
#--------------------------------------------------------------------------
|
354
|
+
def swagger_atomic_param(param_desc, in_schema, name=nil)
|
355
|
+
def save_field(entry, openapi_key, v, apipie_key=openapi_key, translate=false)
|
356
|
+
if v.key?(apipie_key)
|
357
|
+
if translate
|
358
|
+
entry[openapi_key] = Apipie.app.translate(v[apipie_key], @current_lang)
|
359
|
+
else
|
360
|
+
entry[openapi_key] = v[apipie_key]
|
361
|
+
end
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
swagger_def = {}
|
366
|
+
swagger_def[:name] = name if !name.nil?
|
367
|
+
swagger_def[:type] = swagger_param_type(param_desc)
|
368
|
+
|
369
|
+
if swagger_def[:type] == "array"
|
370
|
+
swagger_def[:items] = {type: "string"} # TODO: add support for arrays of non-string items
|
371
|
+
end
|
372
|
+
|
373
|
+
if swagger_def[:type] == "enum"
|
374
|
+
swagger_def[:type] = "string"
|
375
|
+
swagger_def[:enum] = param_desc.validator.values
|
376
|
+
end
|
377
|
+
|
378
|
+
if swagger_def[:type] == "object" # we only get here if there is no specification of properties for this object
|
379
|
+
swagger_def[:additionalProperties] = true
|
380
|
+
warn_hash_without_internal_typespec(param_desc.name)
|
381
|
+
end
|
382
|
+
|
383
|
+
if !in_schema
|
384
|
+
swagger_def[:in] = param_desc.options.fetch(:in, @default_value_for_param_in)
|
385
|
+
swagger_def[:required] = param_desc.required if param_desc.required
|
386
|
+
end
|
387
|
+
|
388
|
+
save_field(swagger_def, :description, param_desc.options, :desc, true)
|
389
|
+
save_field(swagger_def, :default, param_desc.options, :default_value)
|
390
|
+
|
391
|
+
if param_desc.respond_to?(:_gen_added_from_path) && !param_desc.required
|
392
|
+
warn_optional_param_in_path(param_desc.name)
|
393
|
+
swagger_def[:required] = true
|
394
|
+
end
|
395
|
+
|
396
|
+
if !swagger_def[:required] && !swagger_def.key?(:default)
|
397
|
+
warn_optional_without_default_value(param_desc.name)
|
398
|
+
end
|
399
|
+
|
400
|
+
swagger_def
|
401
|
+
end
|
402
|
+
|
403
|
+
|
404
|
+
#--------------------------------------------------------------------------
|
405
|
+
# JSON schema and referenced-object generation
|
406
|
+
#--------------------------------------------------------------------------
|
407
|
+
|
408
|
+
def ref_to(name)
|
409
|
+
"#/definitions/#{name}"
|
410
|
+
end
|
411
|
+
|
412
|
+
|
413
|
+
def json_schema_obj_from_params_array(params_array)
|
414
|
+
(param_defs, required_params) = json_schema_param_defs_from_params_array(params_array)
|
415
|
+
|
416
|
+
result = {type: "object"}
|
417
|
+
result[:properties] = param_defs
|
418
|
+
result[:required] = required_params if required_params.length > 0
|
419
|
+
|
420
|
+
param_defs.length > 0 ? result : nil
|
421
|
+
end
|
422
|
+
|
423
|
+
def gen_referenced_block_from_params_array(name, params_array)
|
424
|
+
return ref_to(:name) if @definitions.key(:name)
|
425
|
+
|
426
|
+
schema_obj = json_schema_obj_from_params_array(params_array)
|
427
|
+
return nil if schema_obj.nil?
|
428
|
+
|
429
|
+
@definitions[name] = schema_obj
|
430
|
+
ref_to(name)
|
431
|
+
end
|
432
|
+
|
433
|
+
def json_schema_param_defs_from_params_array(params_array)
|
434
|
+
param_defs = {}
|
435
|
+
required_params = []
|
436
|
+
|
437
|
+
params_array ||= []
|
438
|
+
|
439
|
+
|
440
|
+
for param_desc in params_array
|
441
|
+
if !param_desc.respond_to?(:required)
|
442
|
+
# pp param_desc
|
443
|
+
raise ("unexpected param_desc format")
|
444
|
+
end
|
445
|
+
|
446
|
+
required_params.push(param_desc.name) if param_desc.required
|
447
|
+
|
448
|
+
param_type = swagger_param_type(param_desc)
|
449
|
+
|
450
|
+
if param_type == "object" && param_desc.validator.params_ordered
|
451
|
+
schema = json_schema_obj_from_params_array(param_desc.validator.params_ordered)
|
452
|
+
param_defs[param_desc.name] = schema if !schema.nil?
|
453
|
+
else
|
454
|
+
param_defs[param_desc.name] = swagger_atomic_param(param_desc, true)
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
[param_defs, required_params]
|
459
|
+
end
|
460
|
+
|
461
|
+
|
462
|
+
|
463
|
+
#--------------------------------------------------------------------------
|
464
|
+
# swagger "Params" block generation
|
465
|
+
#--------------------------------------------------------------------------
|
466
|
+
|
467
|
+
def body_allowed_for_current_method
|
468
|
+
!(['get', 'head'].include?(@current_http_method))
|
469
|
+
end
|
470
|
+
|
471
|
+
def swagger_params_array_for_method(method, path)
|
472
|
+
|
473
|
+
swagger_result = []
|
474
|
+
all_params_hash = add_missing_params(method, path)
|
475
|
+
|
476
|
+
body_param_defs_array = all_params_hash.map {|k, v| v if !param_names_from_path(path).include?(k)}.select{|v| !v.nil?}
|
477
|
+
body_param_defs_hash = all_params_hash.select {|k, v| v if !param_names_from_path(path).include?(k)}
|
478
|
+
path_param_defs_hash = all_params_hash.select {|k, v| v if param_names_from_path(path).include?(k)}
|
479
|
+
|
480
|
+
path_param_defs_hash.each{|name,desc| desc.required = true}
|
481
|
+
add_params_from_hash(swagger_result, path_param_defs_hash, nil, "path")
|
482
|
+
|
483
|
+
if params_in_body? && body_allowed_for_current_method
|
484
|
+
if params_in_body_use_reference?
|
485
|
+
swagger_schema_for_body = {"$ref" => gen_referenced_block_from_params_array("#{swagger_op_id_for_method(method)}_input", body_param_defs_array)}
|
486
|
+
else
|
487
|
+
swagger_schema_for_body = json_schema_obj_from_params_array(body_param_defs_array)
|
488
|
+
end
|
489
|
+
|
490
|
+
swagger_body_param = {
|
491
|
+
name: 'body',
|
492
|
+
in: 'body',
|
493
|
+
schema: swagger_schema_for_body
|
494
|
+
}
|
495
|
+
swagger_result.push(swagger_body_param) if !swagger_schema_for_body.nil?
|
496
|
+
|
497
|
+
else
|
498
|
+
add_params_from_hash(swagger_result, body_param_defs_hash)
|
499
|
+
end
|
500
|
+
|
501
|
+
swagger_result
|
502
|
+
end
|
503
|
+
|
504
|
+
|
505
|
+
def add_params_from_hash(swagger_params_array, param_defs, prefix=nil, default_value_for_in=nil)
|
506
|
+
|
507
|
+
if default_value_for_in
|
508
|
+
@default_value_for_param_in = default_value_for_in
|
509
|
+
else
|
510
|
+
if body_allowed_for_current_method
|
511
|
+
@default_value_for_param_in = "formData"
|
512
|
+
else
|
513
|
+
@default_value_for_param_in = "query"
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
|
518
|
+
param_defs.each do |name, desc|
|
519
|
+
|
520
|
+
if !prefix.nil?
|
521
|
+
name = "#{prefix}[#{name}]"
|
522
|
+
end
|
523
|
+
|
524
|
+
if swagger_param_type(desc) == "object"
|
525
|
+
if desc.validator.params_ordered
|
526
|
+
params_hash = Hash[desc.validator.params_ordered.map {|desc| [desc.name, desc]}]
|
527
|
+
add_params_from_hash(swagger_params_array, params_hash, name)
|
528
|
+
else
|
529
|
+
warn_param_ignored_in_form_data(desc.name)
|
530
|
+
end
|
531
|
+
else
|
532
|
+
param_entry = swagger_atomic_param(desc, false, name)
|
533
|
+
if param_entry[:required]
|
534
|
+
swagger_params_array.unshift(param_entry)
|
535
|
+
else
|
536
|
+
swagger_params_array.push(param_entry)
|
537
|
+
end
|
538
|
+
|
539
|
+
end
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
end
|
544
|
+
|
545
|
+
end
|