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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 37c0e129d50ee2ead759ed4b2ce84fa78ad8b5e0
4
- data.tar.gz: f5576d4598603160aa7624578d62aabdccde8ca9
3
+ metadata.gz: c873515d61e1046750c0ecd967b30f289ac9e3ee
4
+ data.tar.gz: 1bfaf2d71ca3b995045cf2be2c75e6cf846e25fa
5
5
  SHA512:
6
- metadata.gz: 98a8f76dd307fc2fa62825a90ecf5e8c7016a9abb5649cfa79b7d4346102d5f442d3a8ecbfdf603239bd956260fb8d9c117b73c77d536491ed538b38e04ade1b
7
- data.tar.gz: 2687e565d29cf0be95eb232d8b8ab015c6378eff0ae42bf36459d10ad3836447343333e7730bb8389ee003e380063788b515cdaac9012112c0de1b5de217a35e
6
+ metadata.gz: 56d02246c13676a616393bd28a25e297e757aa95ce3b41f6e4f6914cb966afa71ade2eb49ca3b53a39fbbccac3b81bd840e212ef9479de687d628d128626d8d3
7
+ data.tar.gz: 238da70119f0ce29b87ad54b9e0dd903aac41314c79095c75799aeb7226b7ef2abaf78b361e72e7821281dd3e1c21e60a7f5952f51fcff4cb016705ebd993e95
@@ -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
@@ -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
- ./Gemfile.rails50
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
  ===================
@@ -24,4 +24,5 @@ Gem::Specification.new do |s|
24
24
  s.add_development_dependency "RedCloth"
25
25
  s.add_development_dependency "rake"
26
26
  s.add_development_dependency "rdoc"
27
+ s.add_development_dependency "json-schema", "~> 2.8"
27
28
  end
@@ -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
- @doc = authorized_doc
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
@@ -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'
@@ -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
@@ -1,7 +1,7 @@
1
1
  require 'apipie/static_dispatcher'
2
2
  require 'apipie/routes_formatter'
3
3
  require 'yaml'
4
- require 'digest/md5'
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].camelize + "Controller").constantize
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::MD5.hexdigest(JSON.dump(all_docs))
344
+ Digest::SHA1.hexdigest(JSON.dump(all_docs))
325
345
  end
326
346
 
327
347
  def checksum
@@ -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 REGENARATING NEXT TIME"
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
@@ -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::Behavior.instance_eval do
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.constantize)}##{action}"
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