Skip to content

Commit 66332e0

Browse files
author
peter scholz
committed
Merge pull request #354 from LeFnord/master
some improvements, see PR comment
2 parents 2585f60 + a739055 commit 66332e0

15 files changed

+301
-180
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,4 @@ ToDo.md
3737
.ruby-version
3838
spec/params_entity_spec.rb
3939
vendor/bundle/
40+
spec/swagger_v2/x-dummy.rb

README.md

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,11 @@ end
109109
<a name="configure" />
110110
## Configure
111111
112-
[target_class](#target_class)
112+
[host](#host)
113+
[mount_path](#mount_path)
114+
[add_base_path](#add_base_path)
115+
[add_version](#add_version)
113116
[markdown](#markdown)
114-
[hide_format](#hide_format)
115117
[api_version](#api_version)
116118
[models](#models)
117119
[hide_documentation_path](#hide_documentation_path)
@@ -123,16 +125,45 @@ You can pass a hash with optional configuration settings to ```add_swagger_docum
123125
*not all configuration options supported yet*, but is WIP
124126
125127
126-
#### target_class: <a name="target_class" />
127-
The API class to document, default `self`.
128+
`host` and `base_path` are also accepting a `proc` to evaluate.
128129
130+
<a name="host" />
131+
#### host:
132+
Sets explicit the `host`
133+
```ruby
134+
add_swagger_documentation \
135+
host: 'www.no-example.com'
136+
```
137+
138+
<a name="base_path" />
139+
#### base_path:
140+
Base path of the API that's being exposed.
141+
```ruby
142+
add_swagger_documentation \
143+
base_path: '/super/api'
144+
```
129145
130-
#### *mount_path*:
146+
<a name="mount_path" />
147+
#### mount_path:
131148
The path where the API documentation is loaded, default is `/swagger_doc`.
149+
```ruby
150+
add_swagger_documentation \
151+
mount_path: '/docu'
152+
```
132153
154+
#### add_base_path:
155+
Add `basePath` key to the JSON documentation, default is `false`.
156+
```ruby
157+
add_swagger_documentation \
158+
add_base_path: true
159+
```
133160
134-
#### *class_name*:
135-
API class name.
161+
#### add_version:
162+
Add `version` key to the JSON documentation, default is `true`.
163+
```ruby
164+
add_swagger_documentation \
165+
add_version: false
166+
```
136167
137168
138169
<a name="markdown" />
@@ -149,14 +180,6 @@ add_swagger_documentation \
149180
markdown: GrapeSwagger::Markdown::RedcarpetAdapter.new
150181
```
151182
152-
153-
<a name="hide_format" />
154-
#### hide_format:
155-
156-
~~Don't add `.(format)` to the end of URLs, default is `false`.~~
157-
`.(format)` would always be removed.
158-
159-
160183
<a name="api_version" />
161184
#### api_version:
162185
```ruby
@@ -167,17 +190,10 @@ add_swagger_documentation \
167190
Version of the API that's being exposed.
168191

169192

170-
#### *base_path*:
171-
Base path of the API that's being exposed. This configuration parameter accepts a `proc` to evaluate `base_path`, useful when you need to use request attributes to determine its value.
172-
173-
174193
#### *authorizations*:
175194
This value is added to the `authorizations` key in the JSON documentation.
176195

177196

178-
#### *root_base_path*:
179-
Add `basePath` key to the JSON documentation, default is `true`.
180-
181197

182198
<a name="models" />
183199
#### models:
@@ -202,11 +218,6 @@ add_swagger_documentation \
202218

203219
Don't show the `/swagger_doc` path in the generated swagger documentation.
204220
205-
206-
#### *format*:
207-
Documentation response format, default is `:json`.
208-
209-
210221
<a name="info" />
211222
#### info:
212223
```ruby
@@ -226,7 +237,7 @@ add_swagger_documentation \
226237
A hash merged into the `info` key of the JSON documentation.
227238
228239
229-
#### *api_documentation*:
240+
<!-- #### *api_documentation*:
230241
Customize the Swagger API documentation route, typically contains a `desc` field. The default description is "Swagger compatible API description".
231242
232243
```ruby
@@ -241,7 +252,7 @@ Customize the Swagger API specific documentation route, typically contains a `de
241252
```ruby
242253
add_swagger_documentation \
243254
specific_api_documentation: { desc: 'Reticulated splines API swagger-compatible endpoint documentation.' }
244-
```
255+
``` -->
245256
246257
247258
<a name="routes" />

lib/grape-swagger/doc_methods.rb

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@ def setup(options)
1818
# options could be set on #add_swagger_documentation call,
1919
# for available options see #defaults
2020
target_class = options[:target_class]
21-
api_version = options[:api_version]
22-
extra_info = options[:info]
2321
api_doc = options[:api_documentation].dup
2422
specific_api_doc = options[:specific_api_documentation].dup
2523

@@ -35,7 +33,6 @@ def setup(options)
3533
header['Access-Control-Request-Method'] = '*'
3634

3735
output = swagger_object(
38-
info_object(extra_info.merge(version: api_version)),
3936
target_class,
4037
request,
4138
options
@@ -63,7 +60,6 @@ def setup(options)
6360
error!({ error: 'named resource not exist' }, 400) if combined_routes.nil?
6461

6562
output = swagger_object(
66-
info_object(extra_info.merge(version: api_version)),
6763
target_class,
6864
request,
6965
options
@@ -80,19 +76,20 @@ def setup(options)
8076

8177
def defaults
8278
{
79+
info: {},
80+
models: [],
81+
scheme: %w( https http ),
8382
api_version: 'v1',
8483
target_class: nil,
8584
mount_path: '/swagger_doc',
8685
host: nil,
8786
base_path: nil,
87+
add_base_path: false,
88+
add_version: true,
8889
markdown: false,
8990
hide_documentation_path: true,
9091
format: :json,
91-
models: [],
92-
info: {},
93-
scheme: %w( https http ),
9492
authorizations: nil,
95-
root_base_path: true,
9693
api_documentation: { desc: 'Swagger compatible API description' },
9794
specific_api_documentation: { desc: 'Swagger compatible API description for specific API' }
9895
}

lib/grape-swagger/endpoint.rb

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,29 @@ def content_types_for(target_class)
2828
# swagger spec2.0 related parts
2929
#
3030
# required keys for SwaggerObject
31-
def swagger_object(info, target_class, request, options)
31+
def swagger_object(target_class, request, options)
3232
{
33-
info: info,
33+
info: info_object(options[:info].merge(version: options[:api_version])),
3434
swagger: '2.0',
3535
produces: content_types_for(target_class),
3636
authorizations: options[:authorizations],
37-
host: request.env['HTTP_HOST'] || options[:host],
38-
basePath: request.env['SCRIPT_NAME'] || options[:base_path],
37+
host: optional_objects(:host, options, request.env['HTTP_HOST']),
38+
basePath: optional_objects(:base_path, options, request.env['SCRIPT_NAME']),
3939
tags: tag_name_description(options),
4040
schemes: options[:scheme]
4141
}.delete_if { |_, value| value.blank? }
4242
end
4343

44+
# helper for swagger object
45+
# gets host and base_path
46+
def optional_objects(key, options, request = nil)
47+
if options[key]
48+
options[key].is_a?(Proc) ? options[key].call : options[key]
49+
else
50+
request
51+
end
52+
end
53+
4454
# building info object
4555
def info_object(infos)
4656
{
@@ -96,26 +106,13 @@ def path_item(routes, options)
96106
routes.each do |route|
97107
next if hidden?(route)
98108

99-
path = route.route_path
100-
# always removing format
101-
path.sub!(/\(\.\w+?\)$/, '')
102-
path.sub!('(.:format)', '')
103-
# ... format params
104-
path.gsub!(/:(\w+)/, '{\1}')
105-
106-
# set item from path, this could be used for the definitions object
107-
@item = path.gsub(%r{/{(.+?)}}, '').split('/').last.singularize.underscore.camelize || 'Item'
109+
path = path_string(route, options)
108110
@entity = route.route_entity || route.route_success
109111

110112
# ... replacing version params through submitted version
111-
if options[:version]
112-
path.sub!('{version}', options[:version])
113-
else
114-
path.sub!('{version}', '')
115-
end
116113

117114
method = route.route_method.downcase.to_sym
118-
request_params = method_object(route, options)
115+
request_params = method_object(route, options, path)
119116

120117
if @paths.key?(path.to_sym)
121118
@paths[path.to_sym][method] = request_params
@@ -127,18 +124,54 @@ def path_item(routes, options)
127124
end
128125
end
129126

130-
def method_object(route, options)
127+
def path_string(route, options)
128+
path = route.route_path
129+
# always removing format
130+
path.sub!(/\(\.\w+?\)$/, '')
131+
path.sub!('(.:format)', '')
132+
# ... format params
133+
path.gsub!(/:(\w+)/, '{\1}')
134+
135+
# set item from path, this could be used for the definitions object
136+
@item = path.gsub(%r{/{(.+?)}}, '').split('/').last.singularize.underscore.camelize || 'Item'
137+
138+
if options[:version] && options[:add_version]
139+
path.sub!('{version}', options[:version])
140+
else
141+
path.sub!('/{version}', '')
142+
end
143+
144+
path = "/#{optional_objects(:base_path, options)}#{path}" if options[:add_base_path]
145+
146+
path
147+
end
148+
149+
def method_object(route, options, path)
131150
method = {}
132151
method[:description] = description_object(route, options[:markdown])
133152
method[:headers] = route.route_headers if route.route_headers
134153
method[:produces] = produces_object(route, options)
135154
method[:parameters] = params_object(route)
136155
method[:responses] = response_object(route)
137156
method[:tags] = tag_object(route, options[:version])
138-
157+
method[:operationId] = operation_id_object(route.route_method, path)
139158
method.delete_if { |_, value| value.blank? }
140159
end
141160

161+
def operation_id_object(method, path = nil)
162+
verb = method.to_s.downcase
163+
unless path.nil?
164+
operation = path.split('/').map(&:capitalize).join
165+
operation.gsub!(/\-(\w)/, &:upcase).delete!('-') if operation.include?('-')
166+
operation.gsub!(/\_(\w)/, &:upcase).delete!('_') if operation.include?('_')
167+
if path.include?('{')
168+
operation.gsub!(/\{(\w)/, &:upcase)
169+
operation.delete!('{').delete!('}')
170+
end
171+
end
172+
"#{verb}#{operation}"
173+
end
174+
142175
def description_object(route, markdown)
143176
description = route.route_desc if route.route_desc.present?
144177
description = route.route_detail if route.route_detail.present?

spec/lib/endpoint_spec.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,19 @@
33
describe Grape::Endpoint do
44
subject { described_class.new(Grape::Util::InheritableSetting.new, {path: '/', method: :get}) }
55

6+
describe 'operation_id_object' do
7+
specify do
8+
expect(subject.operation_id_object('GET')).to eql 'get'
9+
expect(subject.operation_id_object('get')).to eql 'get'
10+
expect(subject.operation_id_object(:get)).to eql 'get'
11+
expect(subject.operation_id_object('GET', 'foo')).to eql 'getFoo'
12+
expect(subject.operation_id_object('GET', '/foo')).to eql 'getFoo'
13+
expect(subject.operation_id_object('GET', 'bar/foo')).to eql 'getBarFoo'
14+
expect(subject.operation_id_object('GET', 'bar/foo{id}')).to eql 'getBarFooId'
15+
expect(subject.operation_id_object('GET', '/bar_foo{id}')).to eql 'getBarFooId'
16+
expect(subject.operation_id_object('GET', '/bar-foo{id}')).to eql 'getBarFooId'
17+
expect(subject.operation_id_object('GET', '/simple_test/bar-foo{id}')).to eql 'getSimpleTestBarFooId'
18+
end
19+
end
20+
621
end

spec/support/api_swagger_v2_result.rb

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class ApiError < Grape::Entity
7272
"swagger"=>"2.0",
7373
"produces"=>["application/json"],
7474
"host"=>"example.org",
75+
"basePath"=>"/api",
7576
"tags"=>[{"name"=>"other_thing", "description"=>"Operations about other_things"},
7677
{"name"=>"thing", "description"=>"Operations about things"},
7778
{"name"=>"thing2", "description"=>"Operations about thing2s"},
@@ -84,6 +85,7 @@ class ApiError < Grape::Entity
8485
"parameters"=>[
8586
{"in"=>"array", "name"=>"elements", "description"=>"Set of configuration", "type"=>"string", "required"=>true, "allowMultiple"=>true, "items"=>{"type"=>"string"}}],
8687
"tags"=>["other_thing"],
88+
"operationId"=>"getV3OtherThingElements",
8789
"responses"=>{"200"=>{"description"=>"nested route inside namespace", "schema"=>{"$ref"=>"#/definitions/QueryInput"}}},
8890
"x-amazon-apigateway-auth"=>{"type"=>"none"},
8991
"x-amazon-apigateway-integration"=>{"type"=>"aws", "uri"=>"foo_bar_uri", "httpMethod"=>"get"}}},
@@ -95,7 +97,8 @@ class ApiError < Grape::Entity
9597
{"in"=>"query", "name"=>"text", "description"=>"Content of something.", "type"=>"string", "required"=>false, "allowMultiple"=>false},
9698
{"in"=>"query", "name"=>"links", "description"=>nil, "type"=>"link", "required"=>false, "allowMultiple"=>true},
9799
{"in"=>"query", "name"=>"others", "description"=>nil, "type"=>"text", "required"=>false, "allowMultiple"=>false}],
98-
"tags"=>["thing"],
100+
"tags"=>["thing"],
101+
"operationId"=>"getThing",
99102
"responses"=>{
100103
"200"=>{"description"=>"This gets Things.", "schema"=>{"$ref"=>"#/definitions/Thing"}},
101104
"401"=>{"description"=>"Unauthorized", "schema"=>{"$ref"=>"#/definitions/ApiError"}}}},
@@ -105,6 +108,7 @@ class ApiError < Grape::Entity
105108
{"in"=>"formData", "name"=>"text", "description"=>"Content of something.", "type"=>"string", "required"=>true, "allowMultiple"=>false},
106109
{"in"=>"body", "name"=>"links", "description"=>nil, "type"=>"Array", "required"=>true, "allowMultiple"=>true}],
107110
"tags"=>["thing"],
111+
"operationId"=>"postThing",
108112
"responses"=>{
109113
"201"=>{"description"=>"This creates Thing.", "schema"=>{"$ref"=>"#/definitions/Something"}},
110114
"422"=>{"description"=>"Unprocessible Entity"}}
@@ -115,6 +119,7 @@ class ApiError < Grape::Entity
115119
"parameters"=>[
116120
{"in"=>"path", "name"=>"id", "description"=>nil, "type"=>"integer", "required"=>true, "allowMultiple"=>false, "format"=>"int32"}],
117121
"tags"=>["thing"],
122+
"operationId"=>"getThingId",
118123
"responses"=>{
119124
"200"=>{"description"=>"getting a single thing", "schema"=>{"$ref"=>"#/definitions/Thing"}},
120125
"401"=>{"description"=>"Unauthorized"}}},
@@ -125,17 +130,20 @@ class ApiError < Grape::Entity
125130
{"in"=>"formData", "name"=>"text", "description"=>"Content of something.", "type"=>"string", "required"=>false, "allowMultiple"=>false},
126131
{"in"=>"body", "name"=>"links", "description"=>nil, "type"=>"Array", "required"=>false, "allowMultiple"=>true}],
127132
"tags"=>["thing"],
133+
"operationId"=>"putThingId",
128134
"responses"=>{"200"=>{"description"=>"This updates Thing.", "schema"=>{"$ref"=>"#/definitions/Something"}}}},
129135
"delete"=>{
130136
"produces"=>["application/json"],
131137
"parameters"=>[{"in"=>"path", "name"=>"id", "description"=>nil, "type"=>"integer", "required"=>true, "allowMultiple"=>false, "format"=>"int32"}],
132138
"tags"=>["thing"],
139+
"operationId"=>"deleteThingId",
133140
"responses"=>{"200"=>{"description"=>"This deletes Thing.", "schema"=>{"$ref"=>"#/definitions/Something"}}}
134141
}},
135142
"/thing2"=>{
136143
"get"=>{
137144
"produces"=>["application/json"],
138145
"tags"=>["thing2"],
146+
"operationId"=>"getThing2",
139147
"responses"=>{
140148
"200"=>{"description"=>"get Horses", "schema"=>{"$ref"=>"#/definitions/Something"}},
141149
"401"=>{"description"=>"HorsesOutError", "schema"=>{"$ref"=>"#/definitions/ApiError"}}}
@@ -145,6 +153,7 @@ class ApiError < Grape::Entity
145153
"produces"=>["application/json"],
146154
"parameters"=>[{"in"=>"path", "name"=>"id", "description"=>nil, "type"=>"integer", "required"=>true, "allowMultiple"=>false, "format"=>"int32"}],
147155
"tags"=>["dummy"],
156+
"operationId"=>"deleteDummyId",
148157
"responses"=>{"200"=>{"description"=>"dummy route."}, "401"=>{"description"=>"Unauthorized"}}
149158
}}},
150159
"definitions"=>{

0 commit comments

Comments
 (0)