Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge remote branch 'intridea/master' into master2

  • Loading branch information...
commit 7f21d8ecf6e08da4beb691ffd4b63a496494b98e 2 parents b2d1225 + 9aade11
@jch jch authored
Showing with 118 additions and 102 deletions.
  1. +30 −30 lib/grape/api.rb
  2. +88 −72 spec/grape/api_spec.rb
View
60 lib/grape/api.rb
@@ -49,7 +49,7 @@ def set(key, value)
def imbue(key, value)
settings.imbue(key, value)
end
-
+
# Define a root URL prefix for your entire
# API.
def prefix(prefix = nil)
@@ -61,11 +61,11 @@ def prefix(prefix = nil)
# @example API with legacy support.
# class MyAPI < Grape::API
# version 'v2'
- #
+ #
# get '/main' do
# {:some => 'data'}
# end
- #
+ #
# version 'v1' do
# get '/main' do
# {:legacy => 'data'}
@@ -85,8 +85,8 @@ def version(*args, &block)
end
end
end
-
- # Specify the default format for the API's
+
+ # Specify the default format for the API's
# serializers. Currently only `:json` is
# supported.
def default_format(new_format = nil)
@@ -162,7 +162,7 @@ def represent(model_class, options)
#
# When called without a block, all known helpers within this scope
# are included.
- #
+ #
# @example Define some helpers.
# class ExampleAPI < Grape::API
# helpers do
@@ -171,17 +171,17 @@ def represent(model_class, options)
# end
# end
# end
- def helpers(&block)
- if block_given?
- m = settings.peek[:helpers] || Module.new
- m.class_eval &block
- set(:helpers, m)
+ def helpers(mod = nil, &block)
+ if block_given? || mod
+ mod ||= settings.peek[:helpers] || Module.new
+ mod.class_eval &block if block_given?
+ set(:helpers, mod)
else
- m = Module.new
+ mod = Module.new
settings.stack.each do |s|
- m.send :include, s[:helpers] if s[:helpers]
+ mod.send :include, s[:helpers] if s[:helpers]
end
- m
+ mod
end
end
@@ -215,7 +215,7 @@ def mount(mounts)
mounts.each_pair do |app, path|
next unless app.respond_to?(:call)
- route_set.add_route(app,
+ route_set.add_route(app,
:path_info => compile_path(path, false)
)
end
@@ -271,8 +271,8 @@ def route(methods, paths = ['/'], route_options = {}, &block)
def before(&block)
settings.imbue(:befores, [block])
end
-
- def after(&block)
+
+ def after(&block)
settings.imbue(:afters, [block])
end
@@ -291,14 +291,14 @@ def namespace(space = nil, &block)
Rack::Mount::Utils.normalize_path(settings.stack.map{|s| s[:namespace]}.join('/'))
end
end
-
+
alias_method :group, :namespace
alias_method :resource, :namespace
alias_method :resources, :namespace
alias_method :segment, :namespace
-
+
# Create a scope without affecting the URL.
- #
+ #
# @param name [Symbol] Purely placebo, just allows to to name the scope to make the code more readable.
def scope(name = nil, &block)
nest(block)
@@ -325,13 +325,13 @@ def middleware
def routes
@routes ||= []
end
-
+
def versions
@versions ||= []
end
-
+
protected
-
+
# Execute first the provided block, then each of the
# block passed in. Allows for simple 'before' setups
# of settings stack pushes.
@@ -355,11 +355,11 @@ def aggregate_setting(key)
def build_endpoint(&block)
b = Rack::Builder.new
- b.use Grape::Middleware::Error,
- :default_status => settings[:default_error_status] || 403,
- :rescue_all => settings[:rescue_all],
- :rescued_errors => settings[:rescued_errors],
- :format => settings[:error_format] || :txt,
+ b.use Grape::Middleware::Error,
+ :default_status => settings[:default_error_status] || 403,
+ :rescue_all => settings[:rescue_all],
+ :rescued_errors => settings[:rescued_errors],
+ :format => settings[:error_format] || :txt,
:rescue_options => settings[:rescue_options],
:rescue_handlers => settings[:rescue_handlers] || {}
@@ -382,7 +382,7 @@ def build_endpoint(&block)
representations = settings[:representations] || {}
endpoint = Grape::Endpoint.generate({
- :befores => befores,
+ :befores => befores,
:afters => afters,
:representations => representations
}, &block)
@@ -390,7 +390,7 @@ def build_endpoint(&block)
b.run endpoint
b.to_app
end
-
+
def inherited(subclass)
subclass.reset!
subclass.logger = logger.clone
View
160 spec/grape/api_spec.rb
@@ -4,19 +4,19 @@
describe Grape::API do
subject { Class.new(Grape::API) }
before { subject.default_format :txt }
-
+
def app; subject end
-
+
describe '.prefix' do
it 'should route through with the prefix' do
subject.prefix 'awesome/sauce'
subject.get :hello do
"Hello there."
end
-
+
get 'awesome/sauce/hello'
last_response.body.should eql "Hello there."
-
+
get '/hello'
last_response.status.should eql 404
end
@@ -58,7 +58,7 @@ def app; subject end
end
it 'should route if any media type is allowed' do
-
+
end
end
@@ -80,24 +80,24 @@ def app; subject end
namespace.should == '/awesome'
end
end
-
+
it 'should come after the prefix and version' do
subject.prefix :rad
subject.version :v1, :using => :path
-
+
subject.namespace :awesome do
prepare_path('hello').should == '/rad/:version/awesome/hello(.:format)'
end
end
-
+
it 'should cancel itself after the block is over' do
subject.namespace :awesome do
namespace.should == '/awesome'
end
-
+
subject.namespace.should == '/'
end
-
+
it 'should be stackable' do
subject.namespace :awesome do
namespace :rad do
@@ -120,7 +120,7 @@ def app; subject end
get '/members/23'
last_response.body.should == "23"
end
-
+
it 'should be callable with nil just to push onto the stack' do
subject.namespace do
version 'v2', :using => :path
@@ -128,7 +128,7 @@ def app; subject end
end
subject.send(:prepare_path, 'hello').should == '/hello(.:format)'
end
-
+
%w(group resource resources segment).each do |als|
it "`.#{als}` should be an alias" do
subject.send(als, :awesome) do
@@ -137,30 +137,30 @@ def app; subject end
end
end
end
-
+
describe '.route' do
it 'should allow for no path' do
subject.namespace :votes do
get do
"Votes"
end
-
+
post do
"Created a Vote"
end
end
-
+
get '/votes'
last_response.body.should eql 'Votes'
post '/votes'
last_response.body.should eql 'Created a Vote'
end
-
+
it 'should allow for multiple paths' do
subject.get(["/abc", "/def"]) do
"foo"
end
-
+
get '/abc'
last_response.body.should eql 'foo'
get '/def'
@@ -171,7 +171,7 @@ def app; subject end
subject.get("/abc") do
"json"
end
-
+
get '/abc.json'
last_response.body.should eql '"json"'
end
@@ -191,16 +191,16 @@ def app; subject end
"json"
end
end
-
+
get '/abc.json'
last_response.body.should eql '"json"'
end
-
+
it 'should allow for multiple verbs' do
subject.route([:get, :post], '/abc') do
"hiya"
end
-
+
get '/abc'
last_response.body.should eql 'hiya'
post '/abc'
@@ -212,7 +212,7 @@ def app; subject end
subject.route([:get, :post], '/:id/first') do
"first"
end
-
+
subject.route([:get, :post], '/:id') do
"ola"
end
@@ -232,18 +232,18 @@ def app; subject end
last_response.body.should eql 'second'
end
-
+
it 'should allow for :any as a verb' do
subject.route(:any, '/abc') do
"lol"
end
-
+
%w(get post put delete).each do |m|
send(m, '/abc')
last_response.body.should eql 'lol'
end
end
-
+
verbs = %w(post get head delete put)
verbs.each do |verb|
it "should allow and properly constrain a #{verb.upcase} method" do
@@ -257,12 +257,12 @@ def app; subject end
last_response.status.should eql 404
end
end
-
+
it 'should return a 201 response code for POST by default' do
subject.post('example') do
"Created"
end
-
+
post '/example'
last_response.status.should eql 201
last_response.body.should eql 'Created'
@@ -323,7 +323,7 @@ def app; subject end
last_response.headers['Content-Type'].should eql 'application/json'
end
end
-
+
context 'custom middleware' do
class PhonyMiddleware
def initialize(app, *args)
@@ -353,7 +353,7 @@ def call(env)
{:middleware => [[PhonyMiddleware, 'foo']]}
]
subject.stub!(:settings).and_return(settings)
-
+
subject.middleware.should eql [
[PhonyMiddleware, 123],
[PhonyMiddleware, 'abc'],
@@ -375,7 +375,7 @@ def call(env)
middleware.should == [[PhonyMiddleware, 123],[PhonyMiddleware, 'abc']]
end
- subject.middleware.should eql [[PhonyMiddleware, 123]]
+ subject.middleware.should eql [[PhonyMiddleware, 123]]
end
it 'should actually call the middleware' do
@@ -400,28 +400,28 @@ def call(env)
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic('allow','whatever')
last_response.status.should eql 200
end
-
+
it 'should be scopable' do
subject.get(:hello){ "Hello, world."}
subject.namespace :admin do
http_basic do |u,p|
u == 'allow'
end
-
+
get(:hello){ "Hello, world." }
end
-
+
get '/hello'
last_response.status.should eql 200
get '/admin/hello'
last_response.status.should eql 401
end
-
+
it 'should be callable via .auth as well' do
subject.auth :http_basic do |u,p|
u == 'allow'
end
-
+
subject.get(:hello){ "Hello, world."}
get '/hello'
last_response.status.should eql 401
@@ -429,7 +429,7 @@ def call(env)
last_response.status.should eql 200
end
end
-
+
describe '.helpers' do
it 'should be accessible from the endpoint' do
subject.helpers do
@@ -437,65 +437,81 @@ def hello
"Hello, world."
end
end
-
+
subject.get '/howdy' do
hello
end
-
+
get '/howdy'
last_response.body.should eql 'Hello, world.'
end
-
+
it 'should be scopable' do
subject.helpers do
def generic
'always there'
end
end
-
+
subject.namespace :admin do
helpers do
def secret
'only in admin'
end
end
-
+
get '/secret' do
[generic, secret].join ':'
end
end
-
+
subject.get '/generic' do
[generic, respond_to?(:secret)].join ':'
end
-
+
get '/generic'
last_response.body.should eql 'always there:false'
get '/admin/secret'
last_response.body.should eql 'always there:only in admin'
end
-
+
it 'should be reopenable' do
subject.helpers do
def one
1
end
end
-
+
subject.helpers do
def two
2
end
end
-
+
subject.get 'howdy' do
[one, two]
end
-
+
lambda{get '/howdy'}.should_not raise_error
end
+
+ it 'should allow for modules' do
+ mod = Module.new do
+ def hello
+ "Hello, world."
+ end
+ end
+ subject.helpers mod
+
+ subject.get '/howdy' do
+ hello
+ end
+
+ get '/howdy'
+ last_response.body.should eql 'Hello, world.'
+ end
end
-
+
describe '.scope' do
# TODO: refactor this to not be tied to versioning. How about a generic
# .setting macro?
@@ -508,11 +524,11 @@ def two
'abc'
end
end
-
+
subject.get '/def' do
'def'
end
-
+
get '/new/abc'
last_response.status.should eql 404
get '/legacy/abc'
@@ -523,12 +539,12 @@ def two
last_response.status.should eql 200
end
end
-
+
describe ".rescue_from" do
it 'should not rescue errors when rescue_from is not set' do
subject.get '/exception' do
raise "rain!"
- end
+ end
lambda { get '/exception' }.should raise_error
end
@@ -548,18 +564,18 @@ def two
get '/rescued'
last_response.status.should eql 403
-
+
lambda{ get '/unrescued' }.should raise_error
end
end
-
+
describe ".error_format" do
it 'should rescue all errors and return :txt' do
subject.rescue_from :all
subject.error_format :txt
subject.get '/exception' do
raise "rain!"
- end
+ end
get '/exception'
last_response.body.should eql "rain!"
end
@@ -569,7 +585,7 @@ def two
subject.error_format :txt
subject.get '/exception' do
raise "rain!"
- end
+ end
get '/exception'
last_response.body.start_with?("rain!\r\n").should be_true
end
@@ -579,7 +595,7 @@ def two
subject.error_format :json
subject.get '/exception' do
raise "rain!"
- end
+ end
get '/exception'
last_response.body.should eql '{"error":"rain!"}'
end
@@ -588,7 +604,7 @@ def two
subject.error_format :json
subject.get '/exception' do
raise "rain!"
- end
+ end
get '/exception'
json = MultiJson.decode(last_response.body)
json["error"].should eql 'rain!'
@@ -598,7 +614,7 @@ def two
subject.error_format :txt
subject.get '/error' do
error!("Access Denied", 401)
- end
+ end
get '/error'
last_response.body.should eql "Access Denied"
end
@@ -606,19 +622,19 @@ def two
subject.error_format :json
subject.get '/error' do
error!("Access Denied", 401)
- end
+ end
get '/error'
last_response.body.should eql '{"error":"Access Denied"}'
end
end
-
+
describe ".default_error_status" do
it 'should allow setting default_error_status' do
subject.rescue_from :all
subject.default_error_status 200
subject.get '/exception' do
raise "rain!"
- end
+ end
get '/exception'
last_response.status.should eql 200
end
@@ -626,7 +642,7 @@ def two
subject.rescue_from :all
subject.get '/exception' do
raise "rain!"
- end
+ end
get '/exception'
last_response.status.should eql 403
end
@@ -637,10 +653,10 @@ def two
it "returns an empty array of routes" do
subject.routes.should == []
end
- end
+ end
describe "single method api structure" do
before(:each) do
- subject.get :ping do
+ subject.get :ping do
'pong'
end
end
@@ -651,12 +667,12 @@ def two
route.route_path.should == "/ping(.:format)"
route.route_method.should == "GET"
end
- end
+ end
describe "api structure with two versions and a namespace" do
class TwitterAPI < Grape::API
# version v1
version 'v1', :using => :path
- get "version" do
+ get "version" do
api.version
end
# version v2
@@ -691,7 +707,7 @@ class TwitterAPI < Grape::API
end
describe "api structure with additional parameters" do
before(:each) do
- subject.get 'split/:string', { :params => [ "token" ], :optional_params => [ "limit" ] } do
+ subject.get 'split/:string', { :params => [ "token" ], :optional_params => [ "limit" ] } do
params[:string].split(params[:token], (params[:limit] || 0).to_i)
end
end
@@ -710,7 +726,7 @@ class TwitterAPI < Grape::API
end
end
end
-
+
describe ".rescue_from klass, block" do
it 'should rescue Exception' do
subject.rescue_from RuntimeError do |e|
@@ -783,12 +799,12 @@ class CommunicationError < RuntimeError; end
describe '.mount.' do
let(:mounted_app){ lambda{|env| [200, {}, ["MOUNTED"]]} }
-
+
context 'with a bare rack app' do
before do
subject.mount mounted_app => '/mounty'
end
-
+
it 'should make a bare Rack app available at the endpoint' do
get '/mounty'
last_response.body.should == 'MOUNTED'
@@ -800,7 +816,7 @@ class CommunicationError < RuntimeError; end
end
it 'should be able to cascade' do
- subject.mount lambda{ |env|
+ subject.mount lambda{ |env|
headers = {}
headers['X-Cascade'] == 'pass' unless env['PATH_INFO'].include?('boo')
[200, headers, ["Farfegnugen"]]
Please sign in to comment.
Something went wrong with that request. Please try again.