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 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.2.1
1
+ 0.3.0
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "grape-swagger"
8
- s.version = "0.2.1"
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-08-17"
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('\/(.*?)[\.\/\(]').captures.first || '/'
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 => parse_params(route.route_params, route.route_path, route.route_method)
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.map do |param, value|
109
- value[:type] = 'file' if value.is_a?(Hash) && value[:type] == 'Rack::Multipart::UploadedFile'
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
- dataType = value.is_a?(Hash) ? value[:type]||'String' : 'String'
112
- description = value.is_a?(Hash) ? value[:desc] : ''
113
- required = value.is_a?(Hash) ? !!value[:required] : false
114
- paramType = path.match(":#{param}") ? 'path' : (method == 'POST') ? 'body' : 'query'
115
- {
116
- paramType: paramType,
117
- name: param,
118
- description: description,
119
- dataType: dataType,
120
- required: required
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
- # adapt params to swagger format
129
- parsed_path.gsub(/:([a-z]+)/, '{\1}')
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.2.1
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-08-17 00:00:00.000000000 Z
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: -3173865515529886165
197
+ hash: 3307258424823840533
197
198
  required_rubygems_version: !ruby/object:Gem::Requirement
198
199
  none: false
199
200
  requirements: