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