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 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