apipierails3 0.0.1
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 +17 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/.travis.yml +27 -0
- data/APACHE-LICENSE-2.0 +202 -0
- data/CHANGELOG.md +469 -0
- data/Gemfile +1 -0
- data/Gemfile.rails32 +6 -0
- data/Gemfile.rails41 +6 -0
- data/Gemfile.rails42 +11 -0
- data/Gemfile.rails50 +6 -0
- data/Gemfile.rails51 +7 -0
- data/MIT-LICENSE +20 -0
- data/NOTICE +4 -0
- data/PROPOSAL_FOR_RESPONSE_DESCRIPTIONS.md +244 -0
- data/README.rst +1874 -0
- data/Rakefile +13 -0
- data/apipierails3.gemspec +27 -0
- data/app/controllers/apipie/apipies_controller.rb +199 -0
- data/app/helpers/apipie_helper.rb +10 -0
- data/app/public/apipie/javascripts/apipie.js +6 -0
- data/app/public/apipie/javascripts/bundled/bootstrap-collapse.js +138 -0
- data/app/public/apipie/javascripts/bundled/bootstrap.js +1726 -0
- data/app/public/apipie/javascripts/bundled/jquery.js +5 -0
- data/app/public/apipie/javascripts/bundled/prettify.js +28 -0
- data/app/public/apipie/stylesheets/application.css +7 -0
- data/app/public/apipie/stylesheets/bundled/bootstrap-responsive.min.css +12 -0
- data/app/public/apipie/stylesheets/bundled/bootstrap.min.css +689 -0
- data/app/public/apipie/stylesheets/bundled/prettify.css +30 -0
- data/app/views/apipie/apipies/_disqus.html.erb +13 -0
- data/app/views/apipie/apipies/_errors.html.erb +23 -0
- data/app/views/apipie/apipies/_headers.html.erb +26 -0
- data/app/views/apipie/apipies/_languages.erb +6 -0
- data/app/views/apipie/apipies/_metadata.erb +1 -0
- data/app/views/apipie/apipies/_method_detail.erb +61 -0
- data/app/views/apipie/apipies/_params.html.erb +42 -0
- data/app/views/apipie/apipies/_params_plain.html.erb +20 -0
- data/app/views/apipie/apipies/apipie_404.html.erb +17 -0
- data/app/views/apipie/apipies/apipie_checksum.json.erb +1 -0
- data/app/views/apipie/apipies/getting_started.html.erb +6 -0
- data/app/views/apipie/apipies/index.html.erb +56 -0
- data/app/views/apipie/apipies/method.html.erb +41 -0
- data/app/views/apipie/apipies/plain.html.erb +77 -0
- data/app/views/apipie/apipies/resource.html.erb +80 -0
- data/app/views/apipie/apipies/static.html.erb +103 -0
- data/app/views/layouts/apipie/apipie.html.erb +27 -0
- data/config/locales/de.yml +28 -0
- data/config/locales/en.yml +32 -0
- data/config/locales/es.yml +28 -0
- data/config/locales/fr.yml +31 -0
- data/config/locales/it.yml +31 -0
- data/config/locales/ja.yml +31 -0
- data/config/locales/pl.yml +28 -0
- data/config/locales/pt-BR.yml +28 -0
- data/config/locales/ru.yml +28 -0
- data/config/locales/tr.yml +28 -0
- data/config/locales/zh-CN.yml +28 -0
- data/config/locales/zh-TW.yml +28 -0
- data/images/screenshot-1.png +0 -0
- data/images/screenshot-2.png +0 -0
- data/lib/apipie/apipie_module.rb +83 -0
- data/lib/apipie/application.rb +462 -0
- data/lib/apipie/configuration.rb +186 -0
- data/lib/apipie/dsl_definition.rb +607 -0
- data/lib/apipie/error_description.rb +44 -0
- data/lib/apipie/errors.rb +86 -0
- data/lib/apipie/extractor.rb +177 -0
- data/lib/apipie/extractor/collector.rb +117 -0
- data/lib/apipie/extractor/recorder.rb +166 -0
- data/lib/apipie/extractor/writer.rb +454 -0
- data/lib/apipie/helpers.rb +73 -0
- data/lib/apipie/markup.rb +48 -0
- data/lib/apipie/method_description.rb +273 -0
- data/lib/apipie/middleware/checksum_in_headers.rb +35 -0
- data/lib/apipie/param_description.rb +280 -0
- data/lib/apipie/railtie.rb +9 -0
- data/lib/apipie/resource_description.rb +124 -0
- data/lib/apipie/response_description.rb +131 -0
- data/lib/apipie/response_description_adapter.rb +200 -0
- data/lib/apipie/routes_formatter.rb +33 -0
- data/lib/apipie/routing.rb +16 -0
- data/lib/apipie/rspec/response_validation_helper.rb +192 -0
- data/lib/apipie/see_description.rb +39 -0
- data/lib/apipie/static_dispatcher.rb +69 -0
- data/lib/apipie/swagger_generator.rb +707 -0
- data/lib/apipie/tag_list_description.rb +11 -0
- data/lib/apipie/validator.rb +526 -0
- data/lib/apipie/version.rb +3 -0
- data/lib/apipierails3.rb +25 -0
- data/lib/generators/apipie/install/README +6 -0
- data/lib/generators/apipie/install/install_generator.rb +25 -0
- data/lib/generators/apipie/install/templates/initializer.rb.erb +7 -0
- data/lib/generators/apipie/views_generator.rb +11 -0
- data/lib/tasks/apipie.rake +345 -0
- data/rel-eng/packages/.readme +3 -0
- data/rel-eng/packages/rubygem-apipie-rails +1 -0
- data/rel-eng/tito.props +5 -0
- data/spec/controllers/api/v1/architectures_controller_spec.rb +29 -0
- data/spec/controllers/api/v2/architectures_controller_spec.rb +12 -0
- data/spec/controllers/api/v2/nested/resources_controller_spec.rb +11 -0
- data/spec/controllers/apipies_controller_spec.rb +273 -0
- data/spec/controllers/concerns_controller_spec.rb +42 -0
- data/spec/controllers/extended_controller_spec.rb +11 -0
- data/spec/controllers/users_controller_spec.rb +740 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/controllers/api/base_controller.rb +4 -0
- data/spec/dummy/app/controllers/api/v1/architectures_controller.rb +43 -0
- data/spec/dummy/app/controllers/api/v1/base_controller.rb +11 -0
- data/spec/dummy/app/controllers/api/v2/architectures_controller.rb +30 -0
- data/spec/dummy/app/controllers/api/v2/base_controller.rb +11 -0
- data/spec/dummy/app/controllers/api/v2/nested/architectures_controller.rb +32 -0
- data/spec/dummy/app/controllers/api/v2/nested/resources_controller.rb +33 -0
- data/spec/dummy/app/controllers/application_controller.rb +18 -0
- data/spec/dummy/app/controllers/concerns/extending_concern.rb +11 -0
- data/spec/dummy/app/controllers/concerns/sample_controller.rb +41 -0
- data/spec/dummy/app/controllers/concerns_controller.rb +8 -0
- data/spec/dummy/app/controllers/extended_controller.rb +14 -0
- data/spec/dummy/app/controllers/files_controller.rb +5 -0
- data/spec/dummy/app/controllers/overridden_concerns_controller.rb +31 -0
- data/spec/dummy/app/controllers/pets_controller.rb +408 -0
- data/spec/dummy/app/controllers/pets_using_auto_views_controller.rb +73 -0
- data/spec/dummy/app/controllers/pets_using_self_describing_classes_controller.rb +95 -0
- data/spec/dummy/app/controllers/tagged_cats_controller.rb +32 -0
- data/spec/dummy/app/controllers/tagged_dogs_controller.rb +15 -0
- data/spec/dummy/app/controllers/twitter_example_controller.rb +307 -0
- data/spec/dummy/app/controllers/users_controller.rb +297 -0
- data/spec/dummy/app/views/layouts/application.html.erb +21 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +49 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +21 -0
- data/spec/dummy/config/environment.rb +8 -0
- data/spec/dummy/config/environments/development.rb +28 -0
- data/spec/dummy/config/environments/production.rb +52 -0
- data/spec/dummy/config/environments/test.rb +38 -0
- data/spec/dummy/config/initializers/apipie.rb +110 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +10 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +8 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +51 -0
- data/spec/dummy/db/.gitkeep +0 -0
- data/spec/dummy/doc/apipie_examples.json +1 -0
- data/spec/dummy/doc/users/desc_from_file.md +1 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +26 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/stylesheets/.gitkeep +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/lib/application_spec.rb +49 -0
- data/spec/lib/extractor/extractor_spec.rb +9 -0
- data/spec/lib/extractor/middleware_spec.rb +44 -0
- data/spec/lib/extractor/writer_spec.rb +110 -0
- data/spec/lib/file_handler_spec.rb +18 -0
- data/spec/lib/method_description_spec.rb +98 -0
- data/spec/lib/param_description_spec.rb +345 -0
- data/spec/lib/param_group_spec.rb +60 -0
- data/spec/lib/rake_spec.rb +71 -0
- data/spec/lib/resource_description_spec.rb +48 -0
- data/spec/lib/swagger/openapi_2_0_schema.json +1607 -0
- data/spec/lib/swagger/rake_swagger_spec.rb +139 -0
- data/spec/lib/swagger/response_validation_spec.rb +104 -0
- data/spec/lib/swagger/swagger_dsl_spec.rb +658 -0
- data/spec/lib/validator_spec.rb +113 -0
- data/spec/lib/validators/array_validator_spec.rb +85 -0
- data/spec/spec_helper.rb +109 -0
- data/spec/support/rake.rb +21 -0
- metadata +415 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Apipie
|
|
2
|
+
class RoutesFormatter
|
|
3
|
+
API_METHODS = %w{GET POST PUT PATCH OPTIONS DELETE}
|
|
4
|
+
|
|
5
|
+
# The entry method called by Apipie to extract the array
|
|
6
|
+
# representing the api dsl from the routes definition.
|
|
7
|
+
def format_routes(rails_routes, args)
|
|
8
|
+
rails_routes.map { |rails_route| format_route(rails_route, args) }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def format_route(rails_route, args)
|
|
12
|
+
{ :path => format_path(rails_route),
|
|
13
|
+
:verb => format_verb(rails_route),
|
|
14
|
+
:desc => args[:desc],
|
|
15
|
+
:options => args[:options] }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def format_path(rails_route)
|
|
19
|
+
rails_route.path.spec.to_s.gsub('(.:format)', '')
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def format_verb(rails_route)
|
|
23
|
+
verb = API_METHODS.select{|defined_verb| defined_verb =~ /\A#{rails_route.verb}\z/}
|
|
24
|
+
if verb.count != 1
|
|
25
|
+
verb = API_METHODS.select{|defined_verb| defined_verb == rails_route.constraints[:method]}
|
|
26
|
+
if verb.blank?
|
|
27
|
+
raise "Unknow verb #{rails_route.path.spec.to_s}"
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
verb.first
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Apipie
|
|
2
|
+
module Routing
|
|
3
|
+
module MapperExtensions
|
|
4
|
+
def apipie(options = {})
|
|
5
|
+
namespace "apipie", :path => Apipie.configuration.doc_base_url do
|
|
6
|
+
get 'apipie_checksum', :to => "apipies#apipie_checksum", :format => "json"
|
|
7
|
+
constraints(:version => /[^\/]+/, :resource => /[^\/]+/, :method => /[^\/]+/) do
|
|
8
|
+
get(options.reverse_merge("(:version)/(:resource)/(:method)" => "apipies#index", :as => :apipie))
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
ActionDispatch::Routing::Mapper.send :include, Apipie::Routing::MapperExtensions
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#----------------------------------------------------------------------------------------------
|
|
2
|
+
# response_validation_helper.rb:
|
|
3
|
+
#
|
|
4
|
+
# this is an rspec utility to allow validation of responses against the swagger schema generated
|
|
5
|
+
# from the Apipie 'returns' definition for the call.
|
|
6
|
+
#
|
|
7
|
+
#
|
|
8
|
+
# to use this file in a controller rspec you should
|
|
9
|
+
# require 'apipie/rspec/response_validation_helper' in the spec file
|
|
10
|
+
#
|
|
11
|
+
#
|
|
12
|
+
# this utility provides two mechanisms: matcher-based validation and auto-validation
|
|
13
|
+
#
|
|
14
|
+
# matcher-based: an rspec matcher allowing 'expect(response).to match_declared_responses'
|
|
15
|
+
# auto-validation: all responses returned from 'get', 'post', etc. are automatically tested
|
|
16
|
+
#
|
|
17
|
+
# ===================================
|
|
18
|
+
# Matcher-based validation - example
|
|
19
|
+
# ===================================
|
|
20
|
+
# Assume the file 'my_controller_spec.rb':
|
|
21
|
+
#
|
|
22
|
+
# require 'apipie/rspec/response_validation_helper'
|
|
23
|
+
#
|
|
24
|
+
# RSpec.describe MyController, :type => :controller, :show_in_doc => true do
|
|
25
|
+
#
|
|
26
|
+
# describe "GET stuff with response validation" do
|
|
27
|
+
# render_views # this makes sure the 'get' operation will actually
|
|
28
|
+
# # return the rendered view even though this is a Controller spec
|
|
29
|
+
#
|
|
30
|
+
# it "does something" do
|
|
31
|
+
# response = get :index, {format: :json}
|
|
32
|
+
#
|
|
33
|
+
# # the following expectation will fail if the returned object
|
|
34
|
+
# # does not match the 'returns' declaration in the Controller,
|
|
35
|
+
# # or if there is no 'returns' declaration for the returned
|
|
36
|
+
# # HTTP status code
|
|
37
|
+
# expect(response).to match_declared_responses
|
|
38
|
+
# end
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
#
|
|
42
|
+
# ===================================
|
|
43
|
+
# Auto-validation
|
|
44
|
+
# ===================================
|
|
45
|
+
# To use auto-validation, at the beginning of the block in which you want to turn on validation:
|
|
46
|
+
# -) turn on view rendering (by stating 'render_views')
|
|
47
|
+
# -) turn on response validation by stating 'auto_validate_rendered_views'
|
|
48
|
+
#
|
|
49
|
+
# For example, assume the file 'my_controller_spec.rb':
|
|
50
|
+
#
|
|
51
|
+
# require 'apipie/rspec/response_validation_helper'
|
|
52
|
+
#
|
|
53
|
+
# RSpec.describe MyController, :type => :controller, :show_in_doc => true do
|
|
54
|
+
#
|
|
55
|
+
# describe "GET stuff with response validation" do
|
|
56
|
+
# render_views
|
|
57
|
+
# auto_validate_rendered_views
|
|
58
|
+
#
|
|
59
|
+
# it "does something" do
|
|
60
|
+
# get :index, {format: :json}
|
|
61
|
+
# end
|
|
62
|
+
# it "does something else" do
|
|
63
|
+
# get :another_index, {format: :json}
|
|
64
|
+
# end
|
|
65
|
+
# end
|
|
66
|
+
#
|
|
67
|
+
# describe "GET stuff without response validation" do
|
|
68
|
+
# it "does something" do
|
|
69
|
+
# get :index, {format: :json}
|
|
70
|
+
# end
|
|
71
|
+
# it "does something else" do
|
|
72
|
+
# get :another_index, {format: :json}
|
|
73
|
+
# end
|
|
74
|
+
# end
|
|
75
|
+
#
|
|
76
|
+
#
|
|
77
|
+
# Once this is done, responses from http operations ('get', 'post', 'delete', etc.)
|
|
78
|
+
# will fail the test if the response structure does not match the 'returns' declaration
|
|
79
|
+
# on the method (for the actual HTTP status code), or if there is no 'returns' declaration
|
|
80
|
+
# for the HTTP status code.
|
|
81
|
+
#----------------------------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
#----------------------------------------------------------------------------------------------
|
|
85
|
+
# Response validation: core logic (used by auto-validation and manual-validation mechanisms)
|
|
86
|
+
#----------------------------------------------------------------------------------------------
|
|
87
|
+
class ActionController::Base
|
|
88
|
+
module Apipie::ControllerValidationHelpers
|
|
89
|
+
# this method is injected into ActionController::Base in order to
|
|
90
|
+
# get access to the names of the current controller, current action, as well as to the response
|
|
91
|
+
def schema_validation_errors_for_response
|
|
92
|
+
unprocessed_schema = Apipie::json_schema_for_method_response(controller_name, action_name, response.code, true)
|
|
93
|
+
|
|
94
|
+
if unprocessed_schema.nil?
|
|
95
|
+
err = "no schema defined for #{controller_name}##{action_name}[#{response.code}]"
|
|
96
|
+
return [nil, [err], RuntimeError.new(err)]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
schema = JSON.parse(JSON(unprocessed_schema))
|
|
100
|
+
|
|
101
|
+
error_list = JSON::Validator.fully_validate(schema, response.body, :strict => false, :version => :draft4, :json => true)
|
|
102
|
+
|
|
103
|
+
error_object = Apipie::ResponseDoesNotMatchSwaggerSchema.new(controller_name, action_name, response.code, error_list, schema, response.body)
|
|
104
|
+
|
|
105
|
+
[schema, error_list, error_object]
|
|
106
|
+
rescue Apipie::NoDocumentedMethod
|
|
107
|
+
[nil, [], nil]
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
module Apipie
|
|
114
|
+
def self.print_validation_errors(validation_errors, schema, response, error_object=nil)
|
|
115
|
+
Rails.logger.warn(validation_errors.to_s)
|
|
116
|
+
if Rails.env.test?
|
|
117
|
+
puts "schema validation errors:"
|
|
118
|
+
validation_errors.each { |e| puts "--> #{e.to_s}" }
|
|
119
|
+
puts "schema: #{schema.nil? ? '<none>' : JSON(schema)}"
|
|
120
|
+
puts "response: #{response.body}"
|
|
121
|
+
raise error_object if error_object
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
#---------------------------------
|
|
127
|
+
# Manual-validation (RSpec matcher)
|
|
128
|
+
#---------------------------------
|
|
129
|
+
RSpec::Matchers.define :match_declared_responses do
|
|
130
|
+
match do |actual|
|
|
131
|
+
(schema, validation_errors) = subject.send(:schema_validation_errors_for_response)
|
|
132
|
+
valid = (validation_errors == [])
|
|
133
|
+
Apipie::print_validation_errors(validation_errors, schema, response) unless valid
|
|
134
|
+
|
|
135
|
+
valid
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
#---------------------------------
|
|
141
|
+
# Auto-validation logic
|
|
142
|
+
#---------------------------------
|
|
143
|
+
module RSpec::Rails::ViewRendering
|
|
144
|
+
# Augment the RSpec DSL
|
|
145
|
+
module ClassMethods
|
|
146
|
+
def auto_validate_rendered_views
|
|
147
|
+
before do
|
|
148
|
+
@is_response_validation_on = true
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
after do
|
|
152
|
+
@is_response_validation_on = false
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
ActionController::TestCase::Behavior.instance_eval do
|
|
160
|
+
# instrument the 'process' method in ActionController::TestCase to enable response validation
|
|
161
|
+
module Apipie::ResponseValidationHelpers
|
|
162
|
+
@is_response_validation_on = false
|
|
163
|
+
def process(*args)
|
|
164
|
+
result = super(*args)
|
|
165
|
+
validate_response if @is_response_validation_on
|
|
166
|
+
|
|
167
|
+
result
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def validate_response
|
|
171
|
+
controller.send(:validate_response_and_abort_with_info_if_errors)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class ActionController::Base
|
|
179
|
+
module Apipie::ControllerValidationHelpers
|
|
180
|
+
def validate_response_and_abort_with_info_if_errors
|
|
181
|
+
|
|
182
|
+
(schema, validation_errors, error_object) = schema_validation_errors_for_response
|
|
183
|
+
|
|
184
|
+
valid = (validation_errors == [])
|
|
185
|
+
if !valid
|
|
186
|
+
Apipie::print_validation_errors(validation_errors, schema, response, error_object)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Apipie
|
|
2
|
+
|
|
3
|
+
class SeeDescription
|
|
4
|
+
|
|
5
|
+
attr_reader :link, :description
|
|
6
|
+
|
|
7
|
+
def initialize(args)
|
|
8
|
+
if args.first.is_a? Hash
|
|
9
|
+
args = args.first
|
|
10
|
+
elsif args.count == 2
|
|
11
|
+
if args.last.is_a? Hash
|
|
12
|
+
args = {:link => args.first}.merge(args.last)
|
|
13
|
+
else
|
|
14
|
+
args = {:link => args.first, :description => args.second}
|
|
15
|
+
end
|
|
16
|
+
elsif args.count == 1 && args.first.is_a?(String)
|
|
17
|
+
args = {:link => args.first, :description => args.first}
|
|
18
|
+
else
|
|
19
|
+
raise ArgumentError "ApipieError: Bad use of see method."
|
|
20
|
+
end
|
|
21
|
+
@link = args[:link] || args['link']
|
|
22
|
+
@description = args[:desc] || args[:description] || args['desc'] || args['description']
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def to_json
|
|
26
|
+
{:link => see_url, :description => description}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def see_url
|
|
30
|
+
method_description = Apipie[@link]
|
|
31
|
+
if method_description.nil?
|
|
32
|
+
raise ArgumentError.new("Method #{@link} referenced in 'see' does not exist.")
|
|
33
|
+
end
|
|
34
|
+
method_description.doc_url
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module Apipie
|
|
2
|
+
|
|
3
|
+
class FileHandler
|
|
4
|
+
def initialize(root)
|
|
5
|
+
@root = root.chomp('/')
|
|
6
|
+
@compiled_root = /^#{Regexp.escape(root)}/
|
|
7
|
+
@file_server = ::Rack::File.new(@root)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def match?(path)
|
|
11
|
+
# Replace all null bytes
|
|
12
|
+
path = ::Rack::Utils.unescape(path || '').gsub(/\x0/, '')
|
|
13
|
+
|
|
14
|
+
full_path = path.empty? ? @root : File.join(@root, path)
|
|
15
|
+
paths = "#{full_path}#{ext}"
|
|
16
|
+
|
|
17
|
+
matches = Dir[paths]
|
|
18
|
+
match = matches.detect { |m| File.file?(m) }
|
|
19
|
+
if match
|
|
20
|
+
match.sub!(@compiled_root, '')
|
|
21
|
+
match
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def call(env)
|
|
26
|
+
@file_server.call(env)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def ext
|
|
30
|
+
@ext ||= begin
|
|
31
|
+
ext = cache_extension
|
|
32
|
+
"{,#{ext},/index#{ext}}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def cache_extension
|
|
37
|
+
if ::ActionController::Base.respond_to?(:default_static_extension)
|
|
38
|
+
::ActionController::Base.default_static_extension
|
|
39
|
+
else
|
|
40
|
+
::ActionController::Base.page_cache_extension
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class StaticDispatcher
|
|
47
|
+
# Dispatches the static files. Similar to ActionDispatch::Static, but
|
|
48
|
+
# it supports different baseurl configurations
|
|
49
|
+
def initialize(app, path)
|
|
50
|
+
@app = app
|
|
51
|
+
@file_handler = Apipie::FileHandler.new(path)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def call(env)
|
|
55
|
+
@baseurl ||= Apipie.configuration.doc_base_url
|
|
56
|
+
case env['REQUEST_METHOD']
|
|
57
|
+
when 'GET', 'HEAD'
|
|
58
|
+
path = env['PATH_INFO'].sub("#{@baseurl}/","/apipie/").chomp('/')
|
|
59
|
+
|
|
60
|
+
if match = @file_handler.match?(path)
|
|
61
|
+
env["PATH_INFO"] = match
|
|
62
|
+
return @file_handler.call(env)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
@app.call(env)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,707 @@
|
|
|
1
|
+
module Apipie
|
|
2
|
+
|
|
3
|
+
#--------------------------------------------------------------------------
|
|
4
|
+
# Configuration. Should be moved to Apipie config.
|
|
5
|
+
#--------------------------------------------------------------------------
|
|
6
|
+
class SwaggerGenerator
|
|
7
|
+
require 'json'
|
|
8
|
+
require 'ostruct'
|
|
9
|
+
require 'open3'
|
|
10
|
+
require 'zlib' if Apipie.configuration.swagger_generate_x_computed_id_field?
|
|
11
|
+
|
|
12
|
+
attr_reader :computed_interface_id
|
|
13
|
+
|
|
14
|
+
def initialize(apipie)
|
|
15
|
+
@apipie = apipie
|
|
16
|
+
@issued_warnings = []
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def params_in_body?
|
|
20
|
+
Apipie.configuration.swagger_content_type_input == :json
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def params_in_body_use_reference?
|
|
24
|
+
Apipie.configuration.swagger_json_input_uses_refs
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def responses_use_reference?
|
|
28
|
+
Apipie.configuration.swagger_responses_use_refs?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def include_warning_tags?
|
|
32
|
+
Apipie.configuration.swagger_include_warning_tags
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def generate_from_resources(version, resources, method_name, lang, clear_warnings=false)
|
|
37
|
+
init_swagger_vars(version, lang, clear_warnings)
|
|
38
|
+
|
|
39
|
+
@lang = lang
|
|
40
|
+
@only_method = method_name
|
|
41
|
+
add_resources(resources)
|
|
42
|
+
|
|
43
|
+
@swagger[:info]["x-computed-id"] = @computed_interface_id if Apipie.configuration.swagger_generate_x_computed_id_field?
|
|
44
|
+
return @swagger
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
#--------------------------------------------------------------------------
|
|
49
|
+
# Initialization
|
|
50
|
+
#--------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
def init_swagger_vars(version, lang, clear_warnings=false)
|
|
53
|
+
|
|
54
|
+
# docs = {
|
|
55
|
+
# :name => Apipie.configuration.app_name,
|
|
56
|
+
# :info => Apipie.app_info(version, lang),
|
|
57
|
+
# :copyright => Apipie.configuration.copyright,
|
|
58
|
+
# :doc_url => Apipie.full_url(url_args),
|
|
59
|
+
# :api_url => Apipie.api_base_url(version),
|
|
60
|
+
# :resources => _resources
|
|
61
|
+
# }
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@swagger = {
|
|
65
|
+
swagger: '2.0',
|
|
66
|
+
info: {
|
|
67
|
+
title: "#{Apipie.configuration.app_name}",
|
|
68
|
+
description: "#{Apipie.app_info(version, lang)}#{Apipie.configuration.copyright}",
|
|
69
|
+
version: "#{version}",
|
|
70
|
+
"x-copyright" => Apipie.configuration.copyright,
|
|
71
|
+
},
|
|
72
|
+
basePath: Apipie.api_base_url(version),
|
|
73
|
+
consumes: [],
|
|
74
|
+
paths: {},
|
|
75
|
+
definitions: {},
|
|
76
|
+
tags: [],
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if Apipie.configuration.swagger_api_host
|
|
80
|
+
@swagger[:host] = Apipie.configuration.swagger_api_host
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
if params_in_body?
|
|
84
|
+
@swagger[:consumes] = ['application/json']
|
|
85
|
+
@swagger[:info][:title] += " (params in:body)"
|
|
86
|
+
else
|
|
87
|
+
@swagger[:consumes] = ['application/x-www-form-urlencoded', 'multipart/form-data']
|
|
88
|
+
@swagger[:info][:title] += " (params in:formData)"
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
@paths = @swagger[:paths]
|
|
92
|
+
@definitions = @swagger[:definitions]
|
|
93
|
+
@tags = @swagger[:tags]
|
|
94
|
+
|
|
95
|
+
@issued_warnings = [] if clear_warnings || @issued_warnings.nil?
|
|
96
|
+
@computed_interface_id = 0
|
|
97
|
+
|
|
98
|
+
@current_lang = lang
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
#--------------------------------------------------------------------------
|
|
102
|
+
# Engine interface methods
|
|
103
|
+
#--------------------------------------------------------------------------
|
|
104
|
+
|
|
105
|
+
def add_resources(resources)
|
|
106
|
+
resources.each do |resource_name, resource_defs|
|
|
107
|
+
add_resource_description(resource_name, resource_defs)
|
|
108
|
+
add_resource_methods(resource_name, resource_defs)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def add_resource_methods(resource_name, resource_defs)
|
|
113
|
+
resource_defs._methods.each do |apipie_method_name, apipie_method_defs|
|
|
114
|
+
add_ruby_method(@paths, apipie_method_defs)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
#--------------------------------------------------------------------------
|
|
120
|
+
# Logging, debugging and regression-testing utilities
|
|
121
|
+
#--------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
def ruby_name_for_method(method)
|
|
124
|
+
return "<no method>" if method.nil?
|
|
125
|
+
method.resource.controller.name + "#" + method.method
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def warn_missing_method_summary() warn 100, "missing short description for method"; end
|
|
130
|
+
def warn_added_missing_slash(path) warn 101,"added missing / at beginning of path: #{path}"; end
|
|
131
|
+
def warn_no_return_codes_specified() warn 102,"no return codes ('errors') specified"; end
|
|
132
|
+
def warn_hash_without_internal_typespec(param_name) warn 103,"the parameter :#{param_name} is a generic Hash without an internal type specification"; end
|
|
133
|
+
def warn_optional_param_in_path(param_name) warn 104, "the parameter :#{param_name} is 'in-path'. Ignoring 'not required' in DSL"; end
|
|
134
|
+
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
|
|
135
|
+
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
|
|
136
|
+
def warn_path_parameter_not_described(name,path) warn 107,"the parameter :#{name} appears in the path #{path} but is not described"; end
|
|
137
|
+
def warn_inferring_boolean(name) warn 108,"the parameter [#{name}] is Enum with [true,false] values. Inferring 'boolean'"; end
|
|
138
|
+
|
|
139
|
+
def warn(warning_num, msg)
|
|
140
|
+
suppress = Apipie.configuration.swagger_suppress_warnings
|
|
141
|
+
return if suppress == true
|
|
142
|
+
return if suppress.is_a?(Array) && suppress.include?(warning_num)
|
|
143
|
+
|
|
144
|
+
method_id = ruby_name_for_method(@current_method)
|
|
145
|
+
warning_id = "#{method_id}#{warning_num}#{msg}"
|
|
146
|
+
|
|
147
|
+
if @issued_warnings.include?(warning_id)
|
|
148
|
+
# Already issued this warning for the current method
|
|
149
|
+
return
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
print "WARNING (#{warning_num}): [#{method_id}] -- #{msg}\n"
|
|
153
|
+
@issued_warnings.push(warning_id)
|
|
154
|
+
@warnings_issued = true
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def info(msg)
|
|
158
|
+
print "--- INFO: [#{ruby_name_for_method(@current_method)}] -- #{msg}\n"
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
# the @computed_interface_id is a number that is uniquely derived from the list of operations
|
|
163
|
+
# added to the swagger definition (in an order-dependent way).
|
|
164
|
+
# it can be used for regression testing, allowing some differentiation between changes that
|
|
165
|
+
# result from changes to the input and those that result from changes to the generation
|
|
166
|
+
# algorithms.
|
|
167
|
+
# note that at the moment, this only takes operation ids into account, and ignores parameter
|
|
168
|
+
# definitions, so it's only partially useful.
|
|
169
|
+
def include_op_id_in_computed_interface_id(op_id)
|
|
170
|
+
@computed_interface_id = Zlib::crc32("#{@computed_interface_id} #{op_id}") if Apipie.configuration.swagger_generate_x_computed_id_field?
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
#--------------------------------------------------------------------------
|
|
174
|
+
# Create a tag description for a described resource
|
|
175
|
+
#--------------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
def tag_name_for_resource(resource)
|
|
178
|
+
# resource.controller
|
|
179
|
+
resource._id
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def add_resource_description(resource_name, resource)
|
|
183
|
+
if resource._full_description
|
|
184
|
+
@tags << {
|
|
185
|
+
name: tag_name_for_resource(resource),
|
|
186
|
+
description: Apipie.app.translate(resource._full_description, @current_lang)
|
|
187
|
+
}
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
#--------------------------------------------------------------------------
|
|
192
|
+
# Create swagger definitions for a ruby method
|
|
193
|
+
#--------------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
def add_ruby_method(paths, ruby_method)
|
|
196
|
+
|
|
197
|
+
if @only_method
|
|
198
|
+
return unless ruby_method.method == @only_method
|
|
199
|
+
else
|
|
200
|
+
return if !ruby_method.show
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
for api in ruby_method.apis do
|
|
204
|
+
# controller: ruby_method.resource.controller.name,
|
|
205
|
+
|
|
206
|
+
path = swagger_path(api.path)
|
|
207
|
+
paths[path] ||= {}
|
|
208
|
+
methods = paths[path]
|
|
209
|
+
@current_method = ruby_method
|
|
210
|
+
|
|
211
|
+
@warnings_issued = false
|
|
212
|
+
responses = swagger_responses_hash_for_method(ruby_method)
|
|
213
|
+
if include_warning_tags?
|
|
214
|
+
warning_tags = @warnings_issued ? ['warnings issued'] : []
|
|
215
|
+
else
|
|
216
|
+
warning_tags = []
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
op_id = swagger_op_id_for_path(api.http_method, api.path)
|
|
220
|
+
|
|
221
|
+
include_op_id_in_computed_interface_id(op_id)
|
|
222
|
+
|
|
223
|
+
method_key = api.http_method.downcase
|
|
224
|
+
@current_http_method = method_key
|
|
225
|
+
|
|
226
|
+
methods[method_key] = {
|
|
227
|
+
tags: [tag_name_for_resource(ruby_method.resource)] + warning_tags + ruby_method.tag_list.tags,
|
|
228
|
+
consumes: params_in_body? ? ['application/json'] : ['application/x-www-form-urlencoded', 'multipart/form-data'],
|
|
229
|
+
operationId: op_id,
|
|
230
|
+
summary: Apipie.app.translate(api.short_description, @current_lang),
|
|
231
|
+
parameters: swagger_params_array_for_method(ruby_method, api.path),
|
|
232
|
+
responses: responses,
|
|
233
|
+
description: ruby_method.full_description
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if methods[method_key][:summary].nil?
|
|
237
|
+
methods[method_key].delete(:summary)
|
|
238
|
+
warn_missing_method_summary
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
#--------------------------------------------------------------------------
|
|
244
|
+
# Utilities for conversion of ruby syntax to swagger syntax
|
|
245
|
+
#--------------------------------------------------------------------------
|
|
246
|
+
|
|
247
|
+
def swagger_path(str)
|
|
248
|
+
str = str.gsub(/:(\w+)/, '{\1}')
|
|
249
|
+
str = str.gsub(/\/$/, '')
|
|
250
|
+
|
|
251
|
+
if str[0] != '/'
|
|
252
|
+
warn_added_missing_slash(str)
|
|
253
|
+
str = '/' + str
|
|
254
|
+
end
|
|
255
|
+
str
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def remove_colons(str)
|
|
259
|
+
str.gsub(":", "_")
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def swagger_op_id_for_method(method)
|
|
263
|
+
remove_colons method.resource.controller.name + "::" + method.method
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def swagger_id_for_typename(typename)
|
|
267
|
+
typename
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def swagger_op_id_for_path(http_method, path)
|
|
271
|
+
# using lowercase http method, because the 'swagger-codegen' tool outputs
|
|
272
|
+
# strange method names if the http method is in uppercase
|
|
273
|
+
http_method.downcase + path.gsub(/\//,'_').gsub(/:(\w+)/, '\1').gsub(/_$/,'')
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
class SwaggerTypeWithFormat
|
|
277
|
+
attr_reader :str_format
|
|
278
|
+
def initialize(type, str_format)
|
|
279
|
+
@type = type
|
|
280
|
+
@str_format = str_format
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def to_s
|
|
284
|
+
@type
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def ==(other)
|
|
288
|
+
other.to_s == self.to_s
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def lookup
|
|
293
|
+
@lookup ||= {
|
|
294
|
+
numeric: "number",
|
|
295
|
+
hash: "object",
|
|
296
|
+
array: "array",
|
|
297
|
+
|
|
298
|
+
# see https://github.com/OAI/OpenAPI-Specification/blob/master/versions/2.0.md#data-types
|
|
299
|
+
integer: SwaggerTypeWithFormat.new("integer", "int32"),
|
|
300
|
+
long: SwaggerTypeWithFormat.new("integer", "int64"),
|
|
301
|
+
number: SwaggerTypeWithFormat.new("number", nil), # here just for completeness
|
|
302
|
+
float: SwaggerTypeWithFormat.new("number", "float"),
|
|
303
|
+
double: SwaggerTypeWithFormat.new("number", "double"),
|
|
304
|
+
string: SwaggerTypeWithFormat.new("string", nil), # here just for completeness
|
|
305
|
+
byte: SwaggerTypeWithFormat.new("string", "byte"),
|
|
306
|
+
binary: SwaggerTypeWithFormat.new("string", "binary"),
|
|
307
|
+
boolean: SwaggerTypeWithFormat.new("boolean", nil), # here just for completeness
|
|
308
|
+
date: SwaggerTypeWithFormat.new("string", "date"),
|
|
309
|
+
dateTime: SwaggerTypeWithFormat.new("string", "date-time"),
|
|
310
|
+
password: SwaggerTypeWithFormat.new("string", "password"),
|
|
311
|
+
}
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def swagger_param_type(param_desc)
|
|
316
|
+
if param_desc.nil?
|
|
317
|
+
raise("problem")
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
v = param_desc.validator
|
|
321
|
+
if v.nil?
|
|
322
|
+
return "string"
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
if v.class == Apipie::Validator::EnumValidator || (v.respond_to?(:is_enum?) && v.is_enum?)
|
|
326
|
+
if v.values - [true, false] == [] && [true, false] - v.values == []
|
|
327
|
+
warn_inferring_boolean(param_desc.name)
|
|
328
|
+
return "boolean"
|
|
329
|
+
else
|
|
330
|
+
return "enum"
|
|
331
|
+
end
|
|
332
|
+
elsif v.class == Apipie::Validator::HashValidator
|
|
333
|
+
# pp v
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
return lookup[v.expected_type.to_sym] || v.expected_type
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
#--------------------------------------------------------------------------
|
|
342
|
+
# Responses
|
|
343
|
+
#--------------------------------------------------------------------------
|
|
344
|
+
|
|
345
|
+
def json_schema_for_method_response(method, return_code, allow_nulls)
|
|
346
|
+
@definitions = {}
|
|
347
|
+
for response in method.returns
|
|
348
|
+
if response.code.to_s == return_code.to_s
|
|
349
|
+
schema = response_schema(response, allow_nulls) if response.code.to_s == return_code.to_s
|
|
350
|
+
schema[:definitions] = @definitions if @definitions != {}
|
|
351
|
+
return schema
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
nil
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
def json_schema_for_self_describing_class(cls, allow_nulls)
|
|
358
|
+
adapter = ResponseDescriptionAdapter.from_self_describing_class(cls)
|
|
359
|
+
response_schema(adapter, allow_nulls)
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def response_schema(response, allow_nulls=false)
|
|
363
|
+
begin
|
|
364
|
+
# no need to warn about "missing default value for optional param" when processing response definitions
|
|
365
|
+
prev_value = @disable_default_value_warning
|
|
366
|
+
@disable_default_value_warning = true
|
|
367
|
+
|
|
368
|
+
if responses_use_reference? && response.typename
|
|
369
|
+
schema = {"$ref" => gen_referenced_block_from_params_array(swagger_id_for_typename(response.typename), response.params_ordered, allow_nulls)}
|
|
370
|
+
else
|
|
371
|
+
schema = json_schema_obj_from_params_array(response.params_ordered, allow_nulls)
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
ensure
|
|
375
|
+
@disable_default_value_warning = prev_value
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
if response.is_array? && schema
|
|
379
|
+
schema = {
|
|
380
|
+
type: allow_nulls ? ["array","null"] : "array",
|
|
381
|
+
items: schema
|
|
382
|
+
}
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
if response.allow_additional_properties
|
|
386
|
+
schema[:additionalProperties] = true
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
schema
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
def swagger_responses_hash_for_method(method)
|
|
393
|
+
result = {}
|
|
394
|
+
|
|
395
|
+
for error in method.errors
|
|
396
|
+
error_block = {description: Apipie.app.translate(error.description, @current_lang)}
|
|
397
|
+
result[error.code] = error_block
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
for response in method.returns
|
|
401
|
+
swagger_response_block = {
|
|
402
|
+
description: response.description
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
schema = response_schema(response)
|
|
406
|
+
swagger_response_block[:schema] = schema if schema
|
|
407
|
+
|
|
408
|
+
result[response.code] = swagger_response_block
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
if result.length == 0
|
|
412
|
+
warn_no_return_codes_specified
|
|
413
|
+
result[200] = {description: 'ok'}
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
result
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
#--------------------------------------------------------------------------
|
|
422
|
+
# Auto-insertion of parameters that are implicitly defined in the path
|
|
423
|
+
#--------------------------------------------------------------------------
|
|
424
|
+
|
|
425
|
+
def param_names_from_path(path)
|
|
426
|
+
path.scan(/:(\w+)/).map do |ar|
|
|
427
|
+
ar[0].to_sym
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
def add_missing_params(method, path)
|
|
432
|
+
param_names_from_method = method.params.map {|name, desc| name}
|
|
433
|
+
missing = param_names_from_path(path) - param_names_from_method
|
|
434
|
+
|
|
435
|
+
result = method.params
|
|
436
|
+
|
|
437
|
+
missing.each do |name|
|
|
438
|
+
warn_path_parameter_not_described(name, path)
|
|
439
|
+
result[name.to_sym] = OpenStruct.new({
|
|
440
|
+
required: true,
|
|
441
|
+
_gen_added_from_path: true,
|
|
442
|
+
name: name,
|
|
443
|
+
validator: Apipie::Validator::NumberValidator.new(nil),
|
|
444
|
+
options: {
|
|
445
|
+
in: "path"
|
|
446
|
+
}
|
|
447
|
+
})
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
result
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
#--------------------------------------------------------------------------
|
|
454
|
+
# The core routine for creating a swagger parameter definition block.
|
|
455
|
+
# The output is slightly different when the parameter is inside a schema block.
|
|
456
|
+
#--------------------------------------------------------------------------
|
|
457
|
+
def swagger_atomic_param(param_desc, in_schema, name, allow_nulls)
|
|
458
|
+
def save_field(entry, openapi_key, v, apipie_key=openapi_key, translate=false)
|
|
459
|
+
if v.key?(apipie_key)
|
|
460
|
+
if translate
|
|
461
|
+
entry[openapi_key] = Apipie.app.translate(v[apipie_key], @current_lang)
|
|
462
|
+
else
|
|
463
|
+
entry[openapi_key] = v[apipie_key]
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
swagger_def = {}
|
|
469
|
+
swagger_def[:name] = name if !name.nil?
|
|
470
|
+
|
|
471
|
+
swg_param_type = swagger_param_type(param_desc)
|
|
472
|
+
swagger_def[:type] = swg_param_type.to_s
|
|
473
|
+
if (swg_param_type.is_a? SwaggerTypeWithFormat) && !swg_param_type.str_format.nil?
|
|
474
|
+
swagger_def[:format] = swg_param_type.str_format
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
if swagger_def[:type] == "array"
|
|
478
|
+
swagger_def[:items] = {type: "string"}
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
if swagger_def[:type] == "enum"
|
|
482
|
+
swagger_def[:type] = "string"
|
|
483
|
+
swagger_def[:enum] = param_desc.validator.values
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
if swagger_def[:type] == "object" # we only get here if there is no specification of properties for this object
|
|
487
|
+
swagger_def[:additionalProperties] = true
|
|
488
|
+
warn_hash_without_internal_typespec(param_desc.name)
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
if param_desc.is_array?
|
|
492
|
+
new_swagger_def = {
|
|
493
|
+
items: swagger_def,
|
|
494
|
+
type: 'array'
|
|
495
|
+
}
|
|
496
|
+
swagger_def = new_swagger_def
|
|
497
|
+
if allow_nulls
|
|
498
|
+
swagger_def[:type] = [swagger_def[:type], "null"]
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
if allow_nulls
|
|
503
|
+
swagger_def[:type] = [swagger_def[:type], "null"]
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
if !in_schema
|
|
507
|
+
swagger_def[:in] = param_desc.options.fetch(:in, @default_value_for_param_in)
|
|
508
|
+
swagger_def[:required] = param_desc.required if param_desc.required
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
save_field(swagger_def, :description, param_desc.options, :desc, true) unless param_desc.options[:desc].nil?
|
|
512
|
+
save_field(swagger_def, :default, param_desc.options, :default_value)
|
|
513
|
+
|
|
514
|
+
if param_desc.respond_to?(:_gen_added_from_path) && !param_desc.required
|
|
515
|
+
warn_optional_param_in_path(param_desc.name)
|
|
516
|
+
swagger_def[:required] = true
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
if !swagger_def[:required] && !swagger_def.key?(:default)
|
|
520
|
+
warn_optional_without_default_value(param_desc.name) unless @disable_default_value_warning
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
swagger_def
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
#--------------------------------------------------------------------------
|
|
528
|
+
# JSON schema and referenced-object generation
|
|
529
|
+
#--------------------------------------------------------------------------
|
|
530
|
+
|
|
531
|
+
def ref_to(name)
|
|
532
|
+
"#/definitions/#{name}"
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def json_schema_obj_from_params_array(params_array, allow_nulls = false)
|
|
537
|
+
(param_defs, required_params) = json_schema_param_defs_from_params_array(params_array, allow_nulls)
|
|
538
|
+
|
|
539
|
+
result = {type: "object"}
|
|
540
|
+
result[:properties] = param_defs
|
|
541
|
+
result[:additionalProperties] = false unless Apipie.configuration.swagger_allow_additional_properties_in_response
|
|
542
|
+
result[:required] = required_params if required_params.length > 0
|
|
543
|
+
|
|
544
|
+
param_defs.length > 0 ? result : nil
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
def gen_referenced_block_from_params_array(name, params_array, allow_nulls=false)
|
|
548
|
+
return ref_to(:name) if @definitions.key(:name)
|
|
549
|
+
|
|
550
|
+
schema_obj = json_schema_obj_from_params_array(params_array, allow_nulls)
|
|
551
|
+
return nil if schema_obj.nil?
|
|
552
|
+
|
|
553
|
+
@definitions[name.to_sym] = schema_obj
|
|
554
|
+
ref_to(name.to_sym)
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
def json_schema_param_defs_from_params_array(params_array, allow_nulls = false)
|
|
558
|
+
param_defs = {}
|
|
559
|
+
required_params = []
|
|
560
|
+
|
|
561
|
+
params_array ||= []
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
for param_desc in params_array
|
|
565
|
+
if !param_desc.respond_to?(:required)
|
|
566
|
+
# pp param_desc
|
|
567
|
+
raise ("unexpected param_desc format")
|
|
568
|
+
end
|
|
569
|
+
|
|
570
|
+
required_params.push(param_desc.name.to_sym) if param_desc.required
|
|
571
|
+
|
|
572
|
+
param_type = swagger_param_type(param_desc)
|
|
573
|
+
|
|
574
|
+
if param_type == "object" && param_desc.validator.params_ordered
|
|
575
|
+
schema = json_schema_obj_from_params_array(param_desc.validator.params_ordered, allow_nulls)
|
|
576
|
+
if param_desc.additional_properties
|
|
577
|
+
schema[:additionalProperties] = true
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
if param_desc.is_array?
|
|
581
|
+
new_schema = {
|
|
582
|
+
type: 'array',
|
|
583
|
+
items: schema
|
|
584
|
+
}
|
|
585
|
+
schema = new_schema
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
if allow_nulls
|
|
589
|
+
# ideally we would write schema[:type] = ["object", "null"]
|
|
590
|
+
# but due to a bug in the json-schema gem, we need to use anyOf
|
|
591
|
+
# see https://github.com/ruby-json-schema/json-schema/issues/404
|
|
592
|
+
new_schema = {
|
|
593
|
+
anyOf: [schema, {type: "null"}]
|
|
594
|
+
}
|
|
595
|
+
schema = new_schema
|
|
596
|
+
end
|
|
597
|
+
param_defs[param_desc.name.to_sym] = schema if !schema.nil?
|
|
598
|
+
else
|
|
599
|
+
param_defs[param_desc.name.to_sym] = swagger_atomic_param(param_desc, true, nil, allow_nulls)
|
|
600
|
+
end
|
|
601
|
+
end
|
|
602
|
+
|
|
603
|
+
[param_defs, required_params]
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
#--------------------------------------------------------------------------
|
|
609
|
+
# swagger "Params" block generation
|
|
610
|
+
#--------------------------------------------------------------------------
|
|
611
|
+
|
|
612
|
+
def body_allowed_for_current_method
|
|
613
|
+
!(['get', 'head'].include?(@current_http_method))
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
def swagger_params_array_for_method(method, path)
|
|
617
|
+
|
|
618
|
+
swagger_result = []
|
|
619
|
+
all_params_hash = add_missing_params(method, path)
|
|
620
|
+
|
|
621
|
+
body_param_defs_array = all_params_hash.map {|k, v| v if !param_names_from_path(path).include?(k)}.select{|v| !v.nil?}
|
|
622
|
+
body_param_defs_hash = all_params_hash.select {|k, v| v if !param_names_from_path(path).include?(k)}
|
|
623
|
+
path_param_defs_hash = all_params_hash.select {|k, v| v if param_names_from_path(path).include?(k)}
|
|
624
|
+
|
|
625
|
+
path_param_defs_hash.each{|name,desc| desc.required = true}
|
|
626
|
+
add_params_from_hash(swagger_result, path_param_defs_hash, nil, "path")
|
|
627
|
+
|
|
628
|
+
if params_in_body? && body_allowed_for_current_method
|
|
629
|
+
if params_in_body_use_reference?
|
|
630
|
+
swagger_schema_for_body = {"$ref" => gen_referenced_block_from_params_array("#{swagger_op_id_for_method(method)}_input", body_param_defs_array)}
|
|
631
|
+
else
|
|
632
|
+
swagger_schema_for_body = json_schema_obj_from_params_array(body_param_defs_array)
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
swagger_body_param = {
|
|
636
|
+
name: 'body',
|
|
637
|
+
in: 'body',
|
|
638
|
+
schema: swagger_schema_for_body
|
|
639
|
+
}
|
|
640
|
+
swagger_result.push(swagger_body_param) if !swagger_schema_for_body.nil?
|
|
641
|
+
|
|
642
|
+
else
|
|
643
|
+
add_params_from_hash(swagger_result, body_param_defs_hash)
|
|
644
|
+
end
|
|
645
|
+
|
|
646
|
+
add_headers_from_hash(swagger_result, method.headers) if method.headers.present?
|
|
647
|
+
|
|
648
|
+
swagger_result
|
|
649
|
+
end
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
def add_headers_from_hash(swagger_params_array, headers)
|
|
653
|
+
swagger_headers = headers.map do |header|
|
|
654
|
+
{
|
|
655
|
+
name: header[:name],
|
|
656
|
+
in: 'header',
|
|
657
|
+
required: header[:options][:required],
|
|
658
|
+
description: header[:description],
|
|
659
|
+
type: header[:options][:type] || 'string'
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
end
|
|
663
|
+
swagger_params_array.push(*swagger_headers)
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
def add_params_from_hash(swagger_params_array, param_defs, prefix=nil, default_value_for_in=nil)
|
|
668
|
+
|
|
669
|
+
if default_value_for_in
|
|
670
|
+
@default_value_for_param_in = default_value_for_in
|
|
671
|
+
else
|
|
672
|
+
if body_allowed_for_current_method
|
|
673
|
+
@default_value_for_param_in = "formData"
|
|
674
|
+
else
|
|
675
|
+
@default_value_for_param_in = "query"
|
|
676
|
+
end
|
|
677
|
+
end
|
|
678
|
+
|
|
679
|
+
|
|
680
|
+
param_defs.each do |name, desc|
|
|
681
|
+
|
|
682
|
+
if !prefix.nil?
|
|
683
|
+
name = "#{prefix}[#{name}]"
|
|
684
|
+
end
|
|
685
|
+
|
|
686
|
+
if swagger_param_type(desc) == "object"
|
|
687
|
+
if desc.validator.params_ordered
|
|
688
|
+
params_hash = Hash[desc.validator.params_ordered.map {|desc| [desc.name, desc]}]
|
|
689
|
+
add_params_from_hash(swagger_params_array, params_hash, name)
|
|
690
|
+
else
|
|
691
|
+
warn_param_ignored_in_form_data(desc.name)
|
|
692
|
+
end
|
|
693
|
+
else
|
|
694
|
+
param_entry = swagger_atomic_param(desc, false, name, false)
|
|
695
|
+
if param_entry[:required]
|
|
696
|
+
swagger_params_array.unshift(param_entry)
|
|
697
|
+
else
|
|
698
|
+
swagger_params_array.push(param_entry)
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
end
|
|
702
|
+
end
|
|
703
|
+
end
|
|
704
|
+
|
|
705
|
+
end
|
|
706
|
+
|
|
707
|
+
end
|