grape-swagger 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +21 -0
- data/VERSION +1 -1
- data/grape-swagger.gemspec +3 -2
- data/lib/grape-swagger.rb +61 -21
- data/spec/grape-swagger-helper_spec.rb +88 -0
- data/spec/non_default_api_spec.rb +50 -0
- data/spec/simple_mounted_api_spec.rb +16 -1
- metadata +4 -3
data/README.markdown
CHANGED
@@ -41,6 +41,27 @@ You can pass a hash with some configuration possibilities to ```add_swagger_docu
|
|
41
41
|
* ```:api_version``` Version of the API that's being exposed
|
42
42
|
* ```:base_path``` Basepath of the API that's being exposed
|
43
43
|
* ```:markdown``` Allow markdown in `notes`, default `false`
|
44
|
+
* ```:hide_documentation_path``` Don't show the '/swagger_doc' path in the generated swagger documentation
|
45
|
+
|
46
|
+
## Swagger Header Parameters
|
47
|
+
|
48
|
+
Swagger also supports the documentation of parameters passed in the header. Since grape's ```params[]``` doesn't return header parameters we can
|
49
|
+
to specify header parameters seperately in a block after the description.
|
50
|
+
|
51
|
+
``` ruby
|
52
|
+
desc "Return super-secret information", {
|
53
|
+
headers: {
|
54
|
+
"XAuthToken" => {
|
55
|
+
description: "Valdates your identity",
|
56
|
+
required: true
|
57
|
+
},
|
58
|
+
"XOptionalHeader" => {
|
59
|
+
description: "Not reallly needed",
|
60
|
+
required: false
|
61
|
+
}
|
62
|
+
}
|
63
|
+
}
|
64
|
+
```
|
44
65
|
|
45
66
|
## Swagger additions
|
46
67
|
grape-swagger allows you to add an explanation in markdown in the notes field. Which would result in proper formatted markdown in Swagger UI. The default Swagger UI doesn't allow HTML in the notes field, so you need to use an adapted version of Swagger UI (you can find one at https://github.com/tim-vandecasteele/swagger-ui/tree/vasco).
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.3.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.3.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 = "2012-
|
12
|
+
s.date = "2012-10-19"
|
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 = [
|
@@ -27,6 +27,7 @@ Gem::Specification.new do |s|
|
|
27
27
|
"VERSION",
|
28
28
|
"grape-swagger.gemspec",
|
29
29
|
"lib/grape-swagger.rb",
|
30
|
+
"spec/grape-swagger-helper_spec.rb",
|
30
31
|
"spec/grape-swagger_spec.rb",
|
31
32
|
"spec/non_default_api_spec.rb",
|
32
33
|
"spec/simple_mounted_api_spec.rb",
|
data/lib/grape-swagger.rb
CHANGED
@@ -11,7 +11,7 @@ module Grape
|
|
11
11
|
original_mount mounts
|
12
12
|
@combined_routes ||= {}
|
13
13
|
mounts::routes.each do |route|
|
14
|
-
resource = route.route_path.match('\/(
|
14
|
+
resource = route.route_path.match('\/(\w*?)[\.\/\(]').captures.first || '/'
|
15
15
|
@combined_routes[resource.downcase] ||= []
|
16
16
|
@combined_routes[resource.downcase] << route
|
17
17
|
end
|
@@ -41,7 +41,8 @@ module Grape
|
|
41
41
|
:mount_path => '/swagger_doc',
|
42
42
|
:base_path => nil,
|
43
43
|
:api_version => '0.1',
|
44
|
-
:markdown => false
|
44
|
+
:markdown => false,
|
45
|
+
:hide_documentation_path => false
|
45
46
|
}
|
46
47
|
options = defaults.merge(options)
|
47
48
|
|
@@ -49,6 +50,7 @@ module Grape
|
|
49
50
|
@@mount_path = options[:mount_path]
|
50
51
|
@@class_name = options[:class_name] || options[:mount_path].gsub('/','')
|
51
52
|
@@markdown = options[:markdown]
|
53
|
+
@@hide_documentation_path = options[:hide_documentation_path]
|
52
54
|
api_version = options[:api_version]
|
53
55
|
base_path = options[:base_path]
|
54
56
|
|
@@ -58,6 +60,10 @@ module Grape
|
|
58
60
|
header['Access-Control-Request-Method'] = '*'
|
59
61
|
routes = @@target_class::combined_routes
|
60
62
|
|
63
|
+
if @@hide_documentation_path
|
64
|
+
routes.reject!{ |route, value| "/#{route}/".index(parse_path(@@mount_path, nil) << '/') == 0 }
|
65
|
+
end
|
66
|
+
|
61
67
|
routes_array = routes.keys.map do |route|
|
62
68
|
{ :path => "#{@@mount_path}/#{route}.{format}" }
|
63
69
|
end
|
@@ -81,13 +87,14 @@ module Grape
|
|
81
87
|
routes_array = routes.map do |route|
|
82
88
|
notes = route.route_notes && @@markdown ? Kramdown::Document.new(route.route_notes.strip_heredoc).to_html : route.route_notes
|
83
89
|
{
|
84
|
-
:path => parse_path(route.route_path),
|
90
|
+
:path => parse_path(route.route_path, api_version),
|
85
91
|
:operations => [{
|
86
92
|
:notes => notes,
|
87
93
|
:summary => route.route_description || '',
|
88
94
|
:nickname => route.route_method + route.route_path.gsub(/[\/:\(\)\.]/,'-'),
|
89
95
|
:httpMethod => route.route_method,
|
90
|
-
:parameters =>
|
96
|
+
:parameters => parse_header_params(route.route_headers) +
|
97
|
+
parse_params(route.route_params, route.route_path, route.route_method)
|
91
98
|
}]
|
92
99
|
}
|
93
100
|
end
|
@@ -105,28 +112,61 @@ module Grape
|
|
105
112
|
|
106
113
|
helpers do
|
107
114
|
def parse_params(params, path, method)
|
108
|
-
params
|
109
|
-
|
115
|
+
if params
|
116
|
+
params.map do |param, value|
|
117
|
+
value[:type] = 'file' if value.is_a?(Hash) && value[:type] == 'Rack::Multipart::UploadedFile'
|
118
|
+
|
119
|
+
dataType = value.is_a?(Hash) ? value[:type]||'String' : 'String'
|
120
|
+
description = value.is_a?(Hash) ? value[:desc] : ''
|
121
|
+
required = value.is_a?(Hash) ? !!value[:required] : false
|
122
|
+
paramType = path.match(":#{param}") ? 'path' : (method == 'POST') ? 'body' : 'query'
|
123
|
+
name = (value.is_a?(Hash) && value[:full_name]) || param
|
124
|
+
{
|
125
|
+
paramType: paramType,
|
126
|
+
name: name,
|
127
|
+
description: description,
|
128
|
+
dataType: dataType,
|
129
|
+
required: required
|
130
|
+
}
|
131
|
+
end
|
132
|
+
else
|
133
|
+
[]
|
134
|
+
end
|
135
|
+
end
|
110
136
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
137
|
+
|
138
|
+
def parse_header_params(params)
|
139
|
+
if params
|
140
|
+
params.map do |param, value|
|
141
|
+
dataType = 'String'
|
142
|
+
description = value.is_a?(Hash) ? value[:description] : ''
|
143
|
+
required = value.is_a?(Hash) ? !!value[:required] : false
|
144
|
+
paramType = "header"
|
145
|
+
{
|
146
|
+
paramType: paramType,
|
147
|
+
name: param,
|
148
|
+
description: description,
|
149
|
+
dataType: dataType,
|
150
|
+
required: required
|
151
|
+
}
|
152
|
+
end
|
153
|
+
else
|
154
|
+
[]
|
122
155
|
end
|
123
156
|
end
|
124
157
|
|
125
|
-
def parse_path(path)
|
158
|
+
def parse_path(path, version)
|
126
159
|
# adapt format to swagger format
|
127
160
|
parsed_path = path.gsub('(.:format)', '.{format}')
|
128
|
-
#
|
129
|
-
|
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
|
+
# parsed path.
|
166
|
+
parsed_path = parsed_path.gsub(/:([a-zA-Z_]\w*)/, '{\1}')
|
167
|
+
# add the version
|
168
|
+
parsed_path = parsed_path.gsub('{version}', version) if version
|
169
|
+
parsed_path
|
130
170
|
end
|
131
171
|
end
|
132
172
|
end
|
@@ -140,7 +180,7 @@ class Object
|
|
140
180
|
# @person ? @person.name : nil
|
141
181
|
# vs
|
142
182
|
# @person.try(:name)
|
143
|
-
#
|
183
|
+
#
|
144
184
|
# File activesupport/lib/active_support/core_ext/object/try.rb#L32
|
145
185
|
def try(*a, &b)
|
146
186
|
if a.empty? && block_given?
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "helpers" do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
class HelperTestAPI < Grape::API
|
7
|
+
add_swagger_documentation
|
8
|
+
end
|
9
|
+
|
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
|
+
|
16
|
+
end
|
17
|
+
|
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
|
+
|
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
|
+
|
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
|
+
|
54
|
+
it "should parse a path that has vars with underscores in the name" do
|
55
|
+
path = "abc/:def_g(.:format)"
|
56
|
+
@api.parse_path(path, nil).should == "abc/{def_g}.{format}"
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should parse a path that has vars with numbers in the name" do
|
61
|
+
path = "abc/:sha1(.:format)"
|
62
|
+
@api.parse_path(path, nil).should == "abc/{sha1}.{format}"
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should parse a path that has multiple variables" do
|
66
|
+
path1 = "abc/:def/:geh(.:format)"
|
67
|
+
path2 = "abc/:def:geh(.:format)"
|
68
|
+
@api.parse_path(path1, nil).should == "abc/{def}/{geh}.{format}"
|
69
|
+
@api.parse_path(path2, nil).should == "abc/{def}{geh}.{format}"
|
70
|
+
end
|
71
|
+
|
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
|
+
|
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
|
+
|
88
|
+
end
|
@@ -61,6 +61,29 @@ describe "options: " do
|
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
64
|
+
context "overruling hiding the documentation paths" do
|
65
|
+
before(:all) do
|
66
|
+
class HideDocumentationPathMountedApi < Grape::API
|
67
|
+
desc 'this gets something'
|
68
|
+
get '/something' do
|
69
|
+
{:bla => 'something'}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class SimpleApiWithHiddenDocumentation < Grape::API
|
74
|
+
mount HideDocumentationPathMountedApi
|
75
|
+
add_swagger_documentation :hide_documentation_path => true
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def app; SimpleApiWithHiddenDocumentation end
|
80
|
+
|
81
|
+
it "it doesn't show the documentation path on /swagger_doc" do
|
82
|
+
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}\"}]}"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
64
87
|
context "overruling the mount-path" do
|
65
88
|
before(:all) do
|
66
89
|
class DifferentMountMountedApi < Grape::API
|
@@ -120,5 +143,32 @@ describe "options: " do
|
|
120
143
|
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=>[]}]}]}"
|
121
144
|
end
|
122
145
|
end
|
146
|
+
|
147
|
+
context "versioned API" do
|
148
|
+
before(:all) do
|
149
|
+
class VersionedMountedApi < Grape::API
|
150
|
+
prefix 'api'
|
151
|
+
version 'v1'
|
152
|
+
|
153
|
+
desc 'this gets something'
|
154
|
+
get '/something' do
|
155
|
+
{:bla => 'something'}
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
class SimpleApiWithVersion < Grape::API
|
160
|
+
mount VersionedMountedApi
|
161
|
+
add_swagger_documentation :api_version => "v1"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def app; SimpleApiWithVersion end
|
166
|
+
|
167
|
+
it "parses version and places it in the path" do
|
168
|
+
get '/swagger_doc/api'
|
169
|
+
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
|
+
end
|
171
|
+
end
|
172
|
+
|
123
173
|
|
124
174
|
end
|
@@ -9,6 +9,16 @@ describe "a simple mounted api" do
|
|
9
9
|
get '/simple' do
|
10
10
|
{:bla => 'something'}
|
11
11
|
end
|
12
|
+
|
13
|
+
desc 'this gets something else', {
|
14
|
+
:headers => {
|
15
|
+
"XAuthToken" => {description: "A required header.", required: true},
|
16
|
+
"XOtherHeader" => {description: "An optional header.", required: false}
|
17
|
+
}
|
18
|
+
}
|
19
|
+
get '/simple_with_headers' do
|
20
|
+
{:bla => 'something_else'}
|
21
|
+
end
|
12
22
|
end
|
13
23
|
|
14
24
|
class SimpleApi < Grape::API
|
@@ -21,11 +31,16 @@ describe "a simple mounted api" do
|
|
21
31
|
|
22
32
|
it "retrieves swagger-documentation on /swagger_doc" do
|
23
33
|
get '/swagger_doc'
|
24
|
-
last_response.body.should == "{:apiVersion=>\"0.1\", :swaggerVersion=>\"1.1\", :basePath=>\"http://example.org\", :operations=>[], :apis=>[{:path=>\"/swagger_doc/simple.{format}\"}, {:path=>\"/swagger_doc/swagger_doc.{format}\"}]}"
|
34
|
+
last_response.body.should == "{:apiVersion=>\"0.1\", :swaggerVersion=>\"1.1\", :basePath=>\"http://example.org\", :operations=>[], :apis=>[{:path=>\"/swagger_doc/simple.{format}\"}, {:path=>\"/swagger_doc/simple_with_headers.{format}\"}, {:path=>\"/swagger_doc/swagger_doc.{format}\"}]}"
|
25
35
|
end
|
26
36
|
|
27
37
|
it "retrieves the documentation for mounted-api" do
|
28
38
|
get '/swagger_doc/simple'
|
29
39
|
last_response.body.should == "{:apiVersion=>\"0.1\", :swaggerVersion=>\"1.1\", :basePath=>\"http://example.org\", :resourcePath=>\"\", :apis=>[{:path=>\"/simple.{format}\", :operations=>[{:notes=>\"_test_\", :summary=>\"this gets something\", :nickname=>\"GET-simple---format-\", :httpMethod=>\"GET\", :parameters=>[]}]}]}"
|
30
40
|
end
|
41
|
+
|
42
|
+
it "retrieves the documentation for mounted-api that includes headers" do
|
43
|
+
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}]}]}]}"
|
45
|
+
end
|
31
46
|
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.3.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: 2012-
|
12
|
+
date: 2012-10-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: grape
|
@@ -174,6 +174,7 @@ files:
|
|
174
174
|
- VERSION
|
175
175
|
- grape-swagger.gemspec
|
176
176
|
- lib/grape-swagger.rb
|
177
|
+
- spec/grape-swagger-helper_spec.rb
|
177
178
|
- spec/grape-swagger_spec.rb
|
178
179
|
- spec/non_default_api_spec.rb
|
179
180
|
- spec/simple_mounted_api_spec.rb
|
@@ -193,7 +194,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
193
194
|
version: '0'
|
194
195
|
segments:
|
195
196
|
- 0
|
196
|
-
hash:
|
197
|
+
hash: 3307258424823840533
|
197
198
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
198
199
|
none: false
|
199
200
|
requirements:
|