grape-swagger 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +9 -1
- data/VERSION +1 -1
- data/grape-swagger.gemspec +2 -2
- data/lib/grape-swagger.rb +23 -13
- data/spec/grape-swagger-helper_spec.rb +62 -62
- data/spec/non_default_api_spec.rb +64 -6
- data/spec/simple_mounted_api_spec.rb +13 -4
- metadata +3 -3
data/README.markdown
CHANGED
@@ -33,7 +33,15 @@ module API
|
|
33
33
|
end
|
34
34
|
```
|
35
35
|
|
36
|
-
To explore your API, either download [Swagger UI](https://github.com/wordnik/swagger-ui) and set it up yourself or go to the [online swagger demo](http://petstore.swagger.wordnik.com/) and enter your localhost url documentation root in the url field (probably something in the line of http://localhost:3000/swagger_doc.json)
|
36
|
+
To explore your API, either download [Swagger UI](https://github.com/wordnik/swagger-ui) and set it up yourself or go to the [online swagger demo](http://petstore.swagger.wordnik.com/) and enter your localhost url documentation root in the url field (probably something in the line of http://localhost:3000/swagger_doc.json).
|
37
|
+
If you use the online demo, make sure your API supports foreign requests by enabling CORS in grape, otherwise you'll see the API description, but requests on the API won't return. You can do this by putting below code in your Grape API definition:
|
38
|
+
|
39
|
+
```` ruby
|
40
|
+
before do
|
41
|
+
header['Access-Control-Allow-Origin'] = '*'
|
42
|
+
header['Access-Control-Request-Method'] = '*'
|
43
|
+
end
|
44
|
+
````
|
37
45
|
|
38
46
|
## Configure
|
39
47
|
You can pass a hash with some configuration possibilities to ```add_swagger_documentation```, all of these are optional:
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
data/grape-swagger.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = "grape-swagger"
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.4.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Tim Vandecasteele"]
|
12
|
-
s.date = "
|
12
|
+
s.date = "2013-03-28"
|
13
13
|
s.description = "A simple way to add proper auto generated documentation - that can be displayed with swagger - to your inline described grape API"
|
14
14
|
s.email = "tim.vandecasteele@gmail.com"
|
15
15
|
s.extra_rdoc_files = [
|
data/lib/grape-swagger.rb
CHANGED
@@ -11,7 +11,8 @@ module Grape
|
|
11
11
|
original_mount mounts
|
12
12
|
@combined_routes ||= {}
|
13
13
|
mounts::routes.each do |route|
|
14
|
-
resource = route.route_path.match('\/(\w*?)[\.\/\(]').captures.first
|
14
|
+
resource = route.route_path.match('\/(\w*?)[\.\/\(]').captures.first
|
15
|
+
next if resource.empty?
|
15
16
|
@combined_routes[resource.downcase] ||= []
|
16
17
|
@combined_routes[resource.downcase] << route
|
17
18
|
end
|
@@ -64,13 +65,13 @@ module Grape
|
|
64
65
|
routes.reject!{ |route, value| "/#{route}/".index(parse_path(@@mount_path, nil) << '/') == 0 }
|
65
66
|
end
|
66
67
|
|
67
|
-
routes_array = routes.keys.map do |
|
68
|
-
{ :path => "#{
|
68
|
+
routes_array = routes.keys.map do |local_route|
|
69
|
+
{ :path => "#{parse_path(route.route_path.gsub('(.:format)', ''),route.route_version)}/#{local_route}.{format}" }
|
69
70
|
end
|
70
71
|
{
|
71
72
|
apiVersion: api_version,
|
72
73
|
swaggerVersion: "1.1",
|
73
|
-
basePath: base_path ||
|
74
|
+
basePath: base_path || request.base_url,
|
74
75
|
operations:[],
|
75
76
|
apis: routes_array
|
76
77
|
}
|
@@ -86,23 +87,26 @@ module Grape
|
|
86
87
|
routes = @@target_class::combined_routes[params[:name]]
|
87
88
|
routes_array = routes.map do |route|
|
88
89
|
notes = route.route_notes && @@markdown ? Kramdown::Document.new(route.route_notes.strip_heredoc).to_html : route.route_notes
|
89
|
-
|
90
|
-
|
91
|
-
:operations => [{
|
90
|
+
http_codes = parse_http_codes route.route_http_codes
|
91
|
+
operations = {
|
92
92
|
:notes => notes,
|
93
93
|
:summary => route.route_description || '',
|
94
94
|
:nickname => route.route_method + route.route_path.gsub(/[\/:\(\)\.]/,'-'),
|
95
95
|
:httpMethod => route.route_method,
|
96
96
|
:parameters => parse_header_params(route.route_headers) +
|
97
97
|
parse_params(route.route_params, route.route_path, route.route_method)
|
98
|
-
|
98
|
+
}
|
99
|
+
operations.merge!({:errorResponses => http_codes}) unless http_codes.empty?
|
100
|
+
{
|
101
|
+
:path => parse_path(route.route_path, api_version),
|
102
|
+
:operations => [operations]
|
99
103
|
}
|
100
104
|
end
|
101
105
|
|
102
106
|
{
|
103
107
|
apiVersion: api_version,
|
104
108
|
swaggerVersion: "1.1",
|
105
|
-
basePath: base_path ||
|
109
|
+
basePath: base_path || request.base_url,
|
106
110
|
resourcePath: "",
|
107
111
|
apis: routes_array
|
108
112
|
}
|
@@ -158,16 +162,22 @@ module Grape
|
|
158
162
|
def parse_path(path, version)
|
159
163
|
# adapt format to swagger format
|
160
164
|
parsed_path = path.gsub('(.:format)', '.{format}')
|
161
|
-
# This is attempting to emulate the behavior of
|
162
|
-
# Rack::Mount::Strexp. We cannot use Strexp directly because
|
163
|
-
# all it does is generate regular expressions for parsing URLs.
|
164
|
-
# TODO: Implement a Racc tokenizer to properly generate the
|
165
|
+
# This is attempting to emulate the behavior of
|
166
|
+
# Rack::Mount::Strexp. We cannot use Strexp directly because
|
167
|
+
# all it does is generate regular expressions for parsing URLs.
|
168
|
+
# TODO: Implement a Racc tokenizer to properly generate the
|
165
169
|
# parsed path.
|
166
170
|
parsed_path = parsed_path.gsub(/:([a-zA-Z_]\w*)/, '{\1}')
|
167
171
|
# add the version
|
168
172
|
parsed_path = parsed_path.gsub('{version}', version) if version
|
169
173
|
parsed_path
|
170
174
|
end
|
175
|
+
def parse_http_codes codes
|
176
|
+
codes ||= {}
|
177
|
+
codes.collect do |k, v|
|
178
|
+
{:code => k, :reason => v}
|
179
|
+
end
|
180
|
+
end
|
171
181
|
end
|
172
182
|
end
|
173
183
|
end
|
@@ -2,87 +2,87 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe "helpers" do
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
before(:all) do
|
6
|
+
class HelperTestAPI < Grape::API
|
7
|
+
add_swagger_documentation
|
8
|
+
end
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
10
|
+
@api = Object.new
|
11
|
+
# after injecting grape-swagger into the Test API we get the helper methods
|
12
|
+
# back from the first endpoint's class (the API mounted by grape-swagger
|
13
|
+
# to serve the swagger_doc
|
14
|
+
@api.extend HelperTestAPI.endpoints.first.options[:app].helpers
|
15
15
|
|
16
|
-
|
16
|
+
end
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
18
|
+
context "parsing parameters" do
|
19
|
+
it "should parse params as query strings for a GET" do
|
20
|
+
params = {
|
21
|
+
name: {type: 'String', :desc => "A name", required: true },
|
22
|
+
level: 'max'
|
23
|
+
}
|
24
|
+
path = "/coolness"
|
25
|
+
method = "GET"
|
26
|
+
@api.parse_params(params, path, method).should ==
|
27
|
+
[
|
28
|
+
{paramType: "query", name: :name, description:"A name", dataType: "String", required: true},
|
29
|
+
{paramType: "query", name: :level, description:"", dataType: "String", required: false}
|
30
|
+
]
|
31
|
+
end
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
33
|
+
it "should parse params as body for a POST" do
|
34
|
+
params = {
|
35
|
+
name: {type: 'String', :desc =>"A name", required: true },
|
36
|
+
level: 'max'
|
37
|
+
}
|
38
|
+
path = "/coolness"
|
39
|
+
method = "POST"
|
40
|
+
@api.parse_params(params, path, method).should ==
|
41
|
+
[
|
42
|
+
{paramType: "body", name: :name, description:"A name", dataType: "String", required: true},
|
43
|
+
{paramType: "body", name: :level, description:"", dataType: "String", required: false}
|
44
|
+
]
|
45
|
+
end
|
46
|
+
end
|
47
47
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
48
|
+
context "parsing the path" do
|
49
|
+
it "should parse the path" do
|
50
|
+
path = ":abc/def(.:format)"
|
51
|
+
@api.parse_path(path, nil).should == "{abc}/def.{format}"
|
52
|
+
end
|
53
53
|
|
54
54
|
it "should parse a path that has vars with underscores in the name" do
|
55
55
|
path = "abc/:def_g(.:format)"
|
56
|
-
|
57
|
-
|
56
|
+
@api.parse_path(path, nil).should == "abc/{def_g}.{format}"
|
57
|
+
|
58
58
|
end
|
59
59
|
|
60
60
|
it "should parse a path that has vars with numbers in the name" do
|
61
61
|
path = "abc/:sha1(.:format)"
|
62
|
-
|
62
|
+
@api.parse_path(path, nil).should == "abc/{sha1}.{format}"
|
63
63
|
end
|
64
64
|
|
65
65
|
it "should parse a path that has multiple variables" do
|
66
66
|
path1 = "abc/:def/:geh(.:format)"
|
67
67
|
path2 = "abc/:def:geh(.:format)"
|
68
|
-
|
69
|
-
|
68
|
+
@api.parse_path(path1, nil).should == "abc/{def}/{geh}.{format}"
|
69
|
+
@api.parse_path(path2, nil).should == "abc/{def}{geh}.{format}"
|
70
70
|
end
|
71
71
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
72
|
+
it "should parse the path with a specified version" do
|
73
|
+
path = ":abc/{version}/def(.:format)"
|
74
|
+
@api.parse_path(path, 'v1').should == "{abc}/v1/def.{format}"
|
75
|
+
end
|
76
|
+
end
|
77
77
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
78
|
+
context "parsing header parameters" do
|
79
|
+
it "should parse params for the header" do
|
80
|
+
params = {"XAuthToken" => { description: "A required header.", required: true}}
|
81
|
+
@api.parse_header_params(params).should ==
|
82
|
+
[
|
83
|
+
{paramType: "header", name: "XAuthToken", description:"A required header.", dataType: "String", required: true}
|
84
|
+
]
|
85
|
+
end
|
86
|
+
end
|
87
87
|
|
88
88
|
end
|
@@ -61,6 +61,38 @@ describe "options: " do
|
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
64
|
+
context "mounting in a versioned api" do
|
65
|
+
before(:all) do
|
66
|
+
class SimpleApiToMountInVersionedApi < Grape::API
|
67
|
+
desc 'this gets something'
|
68
|
+
get '/something' do
|
69
|
+
{:bla => 'something'}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class SimpleApiWithVersionInPath < Grape::API
|
74
|
+
version 'v1', :using => :path
|
75
|
+
|
76
|
+
mount SimpleApiToMountInVersionedApi
|
77
|
+
add_swagger_documentation
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def app; SimpleApiWithVersionInPath end
|
82
|
+
|
83
|
+
it "should get the documentation on a versioned path /v1/swagger_doc" do
|
84
|
+
get '/v1/swagger_doc'
|
85
|
+
last_response.body.should == "{:apiVersion=>\"0.1\", :swaggerVersion=>\"1.1\", :basePath=>\"http://example.org\", :operations=>[], :apis=>[{:path=>\"/v1/swagger_doc/something.{format}\"}, {:path=>\"/v1/swagger_doc/swagger_doc.{format}\"}]}"
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should get the resource specific documentation on a versioned path /v1/swagger_doc/something" do
|
89
|
+
get '/v1/swagger_doc/something'
|
90
|
+
last_response.status.should == 200
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
|
95
|
+
|
64
96
|
context "overruling hiding the documentation paths" do
|
65
97
|
before(:all) do
|
66
98
|
class HideDocumentationPathMountedApi < Grape::API
|
@@ -80,7 +112,7 @@ describe "options: " do
|
|
80
112
|
|
81
113
|
it "it doesn't show the documentation path on /swagger_doc" do
|
82
114
|
get '/swagger_doc'
|
83
|
-
last_response.body.should == "{:apiVersion=>\"0.1\", :swaggerVersion=>\"1.1\", :basePath=>\"http://example.org\", :operations=>[], :apis=>[{:path=>\"/swagger_doc/something.{format}\"}]}"
|
115
|
+
last_response.body.should == "{:apiVersion=>\"0.1\", :swaggerVersion=>\"1.1\", :basePath=>\"http://example.org\", :operations=>[], :apis=>[{:path=>\"/swagger_doc/something.{format}\"}]}"
|
84
116
|
end
|
85
117
|
end
|
86
118
|
|
@@ -143,32 +175,58 @@ describe "options: " do
|
|
143
175
|
last_response.body.should == "{:apiVersion=>\"0.1\", :swaggerVersion=>\"1.1\", :basePath=>\"http://example.org\", :resourcePath=>\"\", :apis=>[{:path=>\"/something.{format}\", :operations=>[{:notes=>\"<p><em>test</em></p>\\n\", :summary=>\"this gets something\", :nickname=>\"GET-something---format-\", :httpMethod=>\"GET\", :parameters=>[]}]}]}"
|
144
176
|
end
|
145
177
|
end
|
146
|
-
|
178
|
+
|
147
179
|
context "versioned API" do
|
148
180
|
before(:all) do
|
149
181
|
class VersionedMountedApi < Grape::API
|
150
182
|
prefix 'api'
|
151
183
|
version 'v1'
|
152
|
-
|
184
|
+
|
153
185
|
desc 'this gets something'
|
154
186
|
get '/something' do
|
155
187
|
{:bla => 'something'}
|
156
188
|
end
|
157
189
|
end
|
158
|
-
|
190
|
+
|
159
191
|
class SimpleApiWithVersion < Grape::API
|
160
192
|
mount VersionedMountedApi
|
161
193
|
add_swagger_documentation :api_version => "v1"
|
162
194
|
end
|
163
195
|
end
|
164
|
-
|
196
|
+
|
165
197
|
def app; SimpleApiWithVersion end
|
166
|
-
|
198
|
+
|
167
199
|
it "parses version and places it in the path" do
|
168
200
|
get '/swagger_doc/api'
|
169
201
|
last_response.body.should == "{:apiVersion=>\"v1\", :swaggerVersion=>\"1.1\", :basePath=>\"http://example.org\", :resourcePath=>\"\", :apis=>[{:path=>\"/api/v1/something.{format}\", :operations=>[{:notes=>nil, :summary=>\"this gets something\", :nickname=>\"GET-api--version-something---format-\", :httpMethod=>\"GET\", :parameters=>[]}]}]}"
|
170
202
|
end
|
171
203
|
end
|
172
204
|
|
205
|
+
context "protected API" do
|
206
|
+
before(:all) do
|
207
|
+
class ProtectedApi < Grape::API
|
208
|
+
desc 'this gets something'
|
209
|
+
get '/something' do
|
210
|
+
{:bla => 'something'}
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
class SimpleApiWithProtection < Grape::API
|
215
|
+
mount ProtectedApi
|
216
|
+
add_swagger_documentation
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def app; SimpleApiWithProtection; end
|
221
|
+
|
222
|
+
it "should use https schema in mount point" do
|
223
|
+
get '/swagger_doc', {}, 'rack.url_scheme' => 'https'
|
224
|
+
last_response.body.should == "{:apiVersion=>\"0.1\", :swaggerVersion=>\"1.1\", :basePath=>\"https://example.org\", :operations=>[], :apis=>[{:path=>\"/swagger_doc/something.{format}\"}, {:path=>\"/swagger_doc/swagger_doc.{format}\"}]}"
|
225
|
+
end
|
173
226
|
|
227
|
+
it "should use https schema in endpoint doc" do
|
228
|
+
get '/swagger_doc/something', {}, 'rack.url_scheme' => 'https'
|
229
|
+
last_response.body.should == "{:apiVersion=>\"0.1\", :swaggerVersion=>\"1.1\", :basePath=>\"https://example.org\", :resourcePath=>\"\", :apis=>[{:path=>\"/something.{format}\", :operations=>[{:notes=>nil, :summary=>\"this gets something\", :nickname=>\"GET-something---format-\", :httpMethod=>\"GET\", :parameters=>[]}]}]}"
|
230
|
+
end
|
231
|
+
end
|
174
232
|
end
|
@@ -3,18 +3,27 @@ require 'spec_helper'
|
|
3
3
|
describe "a simple mounted api" do
|
4
4
|
before(:all) do
|
5
5
|
class SimpleMountedApi < Grape::API
|
6
|
+
desc "Document root"
|
7
|
+
get do
|
8
|
+
end
|
9
|
+
|
6
10
|
desc 'this gets something', {
|
7
11
|
:notes => '_test_'
|
8
12
|
}
|
13
|
+
|
9
14
|
get '/simple' do
|
10
15
|
{:bla => 'something'}
|
11
16
|
end
|
12
|
-
|
17
|
+
|
13
18
|
desc 'this gets something else', {
|
14
19
|
:headers => {
|
15
|
-
"XAuthToken" => {description: "A required header.", required: true},
|
20
|
+
"XAuthToken" => {description: "A required header.", required: true},
|
16
21
|
"XOtherHeader" => {description: "An optional header.", required: false}
|
17
|
-
}
|
22
|
+
},
|
23
|
+
:http_codes => {
|
24
|
+
403 => "invalid pony",
|
25
|
+
405 => "no ponies left!"
|
26
|
+
}
|
18
27
|
}
|
19
28
|
get '/simple_with_headers' do
|
20
29
|
{:bla => 'something_else'}
|
@@ -41,6 +50,6 @@ describe "a simple mounted api" do
|
|
41
50
|
|
42
51
|
it "retrieves the documentation for mounted-api that includes headers" do
|
43
52
|
get '/swagger_doc/simple_with_headers'
|
44
|
-
last_response.body.should == "{:apiVersion=>\"0.1\", :swaggerVersion=>\"1.1\", :basePath=>\"http://example.org\", :resourcePath=>\"\", :apis=>[{:path=>\"/simple_with_headers.{format}\", :operations=>[{:notes=>nil, :summary=>\"this gets something else\", :nickname=>\"GET-simple_with_headers---format-\", :httpMethod=>\"GET\", :parameters=>[{:paramType=>\"header\", :name=>\"XAuthToken\", :description=>\"A required header.\", :dataType=>\"String\", :required=>true}, {:paramType=>\"header\", :name=>\"XOtherHeader\", :description=>\"An optional header.\", :dataType=>\"String\", :required=>false}]}]}]}"
|
53
|
+
last_response.body.should == "{:apiVersion=>\"0.1\", :swaggerVersion=>\"1.1\", :basePath=>\"http://example.org\", :resourcePath=>\"\", :apis=>[{:path=>\"/simple_with_headers.{format}\", :operations=>[{:notes=>nil, :summary=>\"this gets something else\", :nickname=>\"GET-simple_with_headers---format-\", :httpMethod=>\"GET\", :parameters=>[{:paramType=>\"header\", :name=>\"XAuthToken\", :description=>\"A required header.\", :dataType=>\"String\", :required=>true}, {:paramType=>\"header\", :name=>\"XOtherHeader\", :description=>\"An optional header.\", :dataType=>\"String\", :required=>false}], :errorResponses=>[{:code=>403, :reason=>\"invalid pony\"}, {:code=>405, :reason=>\"no ponies left!\"}]}]}]}"
|
45
54
|
end
|
46
55
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: grape-swagger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2013-03-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: grape
|
@@ -194,7 +194,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
194
194
|
version: '0'
|
195
195
|
segments:
|
196
196
|
- 0
|
197
|
-
hash:
|
197
|
+
hash: -2543404597308803068
|
198
198
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
199
199
|
none: false
|
200
200
|
requirements:
|