Skip to content

Commit 17ec92c

Browse files
committed
Added support for API descriptors.
1 parent 633985c commit 17ec92c

File tree

5 files changed

+118
-35
lines changed

5 files changed

+118
-35
lines changed

.gitignore

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ tmtags
1111
\#*
1212
.\#*
1313

14+
## REDCAR
15+
.redcar
16+
1417
## VIM
1518
*.swp
1619

@@ -23,7 +26,5 @@ pkg
2326
.yardoc/*
2427
dist
2528
Gemfile.lock
26-
*.orig
27-
*.rej
2829

2930
## PROJECT::SPECIFIC

README.markdown

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -248,32 +248,40 @@ describe Twitter::API do
248248
end
249249
```
250250

251-
## Inspecting an API
251+
## Describing and Inspecting an API
252252

253-
Grape exposes arrays of API versions and compiled routes. Each route contains a `route_prefix`, `route_version`, `route_namespace`, `route_method`, `route_path` and `route_params`.
253+
Grape lets you add a description to an API along with any other optional elements that can also be inspected at runtime.
254+
This can be useful for generating documentation.
254255

255256
```ruby
256257
class TwitterAPI < Grape::API
257258

258259
version 'v1'
260+
261+
desc "Retrieves the API version number."
259262
get "version" do
260263
api.version
261264
end
262265

263-
version 'v2'
264-
namespace "ns" do
265-
get "version" do
266-
api.version
267-
end
266+
desc "Reverses a string.", { :params => [
267+
{ "s" => { :desc => "string to reverse", :type => "string" }}
268+
]}
269+
get "reverse" do
270+
params[:s].reverse
268271
end
269272
end
273+
```
270274

275+
Grape then exposes arrays of API versions and compiled routes. Each route contains a `route_prefix`, `route_version`, `route_namespace`, `route_method`, `route_path` and `route_params`. The description and the optional hash that follows the API path may contain any number of keys and its values are also accessible via dynamically-generated `route_[name]` functions.
276+
277+
```ruby
271278
TwitterAPI::versions # yields [ 'v1', 'v2' ]
272279
TwitterAPI::routes # yields an array of Grape::Route objects
273280
TwitterAPI::routes[0].route_version # yields 'v1'
281+
TwitterAPI::routes[0].route_description # yields [ { "s" => { :desc => "string to reverse", :type => "string" }} ]
274282
```
275283

276-
Grape also supports storing additional parameters with the route information. This can be useful for generating documentation. The optional hash that follows the API path may contain any number of keys and its values are also accessible via a dynamically-generated `route_[name]` function.
284+
Parameters can also be tagged to the method declaration itself.
277285

278286
```ruby
279287
class StringAPI < Grape::API

lib/grape/api.rb

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ def reset!
3131
@route_set = Rack::Mount::RouteSet.new
3232
@endpoints = []
3333
@mountings = []
34+
@routes = nil
3435
end
3536

3637
def compile
@@ -103,6 +104,11 @@ def version(*args, &block)
103104
end
104105
end
105106
end
107+
108+
# Add a description to the next namespace or function.
109+
def desc(description, options = {})
110+
@last_description = options.merge({description: description})
111+
end
106112

107113
# Specify the default format for the API's
108114
# serializers. Currently only `:json` is
@@ -260,11 +266,13 @@ def mount(mounts)
260266
# end
261267
# end
262268
def route(methods, paths = ['/'], route_options = {}, &block)
263-
endpoints << Grape::Endpoint.new(settings.clone, {
269+
endpoint_options = {
264270
:method => methods,
265271
:path => paths,
266-
:route_options => (route_options || {})
267-
}, &block)
272+
:route_options => (route_options || {}).merge(@last_description || {})
273+
}
274+
endpoints << Grape::Endpoint.new(settings.clone, endpoint_options, &block)
275+
@last_description = nil
268276
end
269277

270278
def before(&block)
@@ -326,11 +334,11 @@ def middleware
326334
def routes
327335
@routes ||= prepare_routes
328336
end
329-
337+
330338
def versions
331339
@versions ||= []
332340
end
333-
341+
334342
protected
335343

336344
def prepare_routes

lib/grape/endpoint.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def initialize(settings, options = {}, &block)
2525

2626
options[:route_options] ||= {}
2727
end
28-
28+
2929
def routes
3030
@routes ||= prepare_routes
3131
end

spec/grape/api_spec.rb

Lines changed: 85 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def app; subject end
5858
end
5959

6060
it 'should route if any media type is allowed' do
61-
61+
6262
end
6363
end
6464

@@ -123,7 +123,7 @@ def app; subject end
123123
get '/members/23'
124124
last_response.body.should == "23"
125125
end
126-
126+
127127
it 'should be callable with nil just to push onto the stack' do
128128
subject.namespace do
129129
version 'v2', :using => :path
@@ -136,7 +136,7 @@ def app; subject end
136136
get '/hello'
137137
last_response.body.should == "outer"
138138
end
139-
139+
140140
%w(group resource resources segment).each do |als|
141141
it "`.#{als}` should be an alias" do
142142
subject.send(als, :awesome) do
@@ -220,7 +220,7 @@ def app; subject end
220220
subject.route([:get, :post], '/:id/first') do
221221
"first"
222222
end
223-
223+
224224
subject.route([:get, :post], '/:id') do
225225
"ola"
226226
end
@@ -331,7 +331,7 @@ def app; subject end
331331
last_response.headers['Content-Type'].should eql 'application/json'
332332
end
333333
end
334-
334+
335335
context 'custom middleware' do
336336
class PhonyMiddleware
337337
def initialize(app, *args)
@@ -363,7 +363,7 @@ def call(env)
363363
{:middleware => [[PhonyMiddleware, 'foo']]}
364364
]
365365
subject.stub!(:settings).and_return(settings)
366-
366+
367367
subject.middleware.should eql [
368368
[PhonyMiddleware, 123],
369369
[PhonyMiddleware, 'abc'],
@@ -568,7 +568,7 @@ def hello
568568
subject.get '/def' do
569569
'def'
570570
end
571-
571+
572572
get '/new/abc'
573573
last_response.status.should eql 404
574574
get '/legacy/abc'
@@ -747,26 +747,92 @@ class TwitterAPI < Grape::API
747747
end
748748
describe "api structure with additional parameters" do
749749
before(:each) do
750-
subject.get 'split/:string', { :params => [ "token" ], :optional_params => [ "limit" ] } do
751-
params[:string].split(params[:token], (params[:limit] || 0).to_i)
750+
subject.get 'split/:string', { :params => [ "token" ], :optional_params => [ "limit" ] } do
751+
params[:string].split(params[:token], (params[:limit] || 0).to_i)
752752
end
753753
end
754754
it "should split a string" do
755-
get "/split/a,b,c.json", :token => ','
756-
last_response.body.should == '["a","b","c"]'
755+
get "/split/a,b,c.json", :token => ','
756+
last_response.body.should == '["a","b","c"]'
757757
end
758758
it "should split a string with limit" do
759-
get "/split/a,b,c.json", :token => ',', :limit => '2'
760-
last_response.body.should == '["a","b,c"]'
759+
get "/split/a,b,c.json", :token => ',', :limit => '2'
760+
last_response.body.should == '["a","b,c"]'
761761
end
762762
it "should set route_params" do
763-
subject.routes.size.should == 1
764-
subject.routes[0].route_params.should == [ "string", "token" ]
765-
subject.routes[0].route_optional_params.should == [ "limit" ]
763+
subject.routes.size.should == 1
764+
subject.routes[0].route_params.should == [ "string", "token" ]
765+
subject.routes[0].route_optional_params.should == [ "limit" ]
766766
end
767767
end
768768
end
769769

770+
context "desc" do
771+
describe "empty api structure" do
772+
it "returns an empty array of routes" do
773+
subject.desc "grape api"
774+
subject.routes.should == []
775+
end
776+
end
777+
describe "single method with a desc" do
778+
before(:each) do
779+
subject.desc "ping method"
780+
subject.get :ping do
781+
'pong'
782+
end
783+
end
784+
it "returns route description" do
785+
subject.routes[0].route_description.should == "ping method"
786+
end
787+
end
788+
describe "api structure with multiple methods and descriptions" do
789+
before(:each) do
790+
class JitterAPI < Grape::API
791+
desc "first method"
792+
get "first" do; end
793+
get "second" do; end
794+
desc "third method"
795+
get "third" do; end
796+
end
797+
end
798+
it "should return a description for the first method" do
799+
JitterAPI::routes[0].route_description.should == "first method"
800+
JitterAPI::routes[1].route_description.should be_nil
801+
JitterAPI::routes[2].route_description.should == "third method"
802+
end
803+
end
804+
describe "api structure with multiple methods, namespaces, descriptions and options" do
805+
before(:each) do
806+
class LitterAPI < Grape::API
807+
desc "first method"
808+
get "first" do; end
809+
get "second" do; end
810+
namespace "ns" do
811+
desc "ns second", :foo => "bar"
812+
get "second" do; end
813+
end
814+
desc "third method", :details => "details of third method"
815+
get "third" do; end
816+
desc "Reverses a string.", { :params => [
817+
{ "s" => { :desc => "string to reverse", :type => "string" }}
818+
]}
819+
get "reverse" do
820+
params[:s].reverse
821+
end
822+
end
823+
end
824+
it "should return a description for the first method" do
825+
LitterAPI::routes[0].route_description.should == "first method"
826+
LitterAPI::routes[1].route_description.should be_nil
827+
LitterAPI::routes[2].route_description.should == "ns second"
828+
LitterAPI::routes[2].route_foo.should == "bar"
829+
LitterAPI::routes[3].route_description.should == "third method"
830+
LitterAPI::routes[4].route_description.should == "Reverses a string."
831+
LitterAPI::routes[4].route_params.should == [{ "s" => { :desc => "string to reverse", :type => "string" }}]
832+
end
833+
end
834+
end
835+
770836
describe ".rescue_from klass, block" do
771837
it 'should rescue Exception' do
772838
subject.rescue_from RuntimeError do |e|
@@ -839,12 +905,12 @@ class CommunicationError < RuntimeError; end
839905

840906
describe '.mount' do
841907
let(:mounted_app){ lambda{|env| [200, {}, ["MOUNTED"]]} }
842-
908+
843909
context 'with a bare rack app' do
844910
before do
845911
subject.mount mounted_app => '/mounty'
846912
end
847-
913+
848914
it 'should make a bare Rack app available at the endpoint' do
849915
get '/mounty'
850916
last_response.body.should == 'MOUNTED'
@@ -856,7 +922,7 @@ class CommunicationError < RuntimeError; end
856922
end
857923

858924
it 'should be able to cascade' do
859-
subject.mount lambda{ |env|
925+
subject.mount lambda{ |env|
860926
headers = {}
861927
headers['X-Cascade'] == 'pass' unless env['PATH_INFO'].include?('boo')
862928
[200, headers, ["Farfegnugen"]]

0 commit comments

Comments
 (0)