apipierails3 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|