Skip to content

Commit

Permalink
Updates.
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael Bleigh committed Nov 13, 2010
1 parent 87b3d15 commit 98ca5a8
Show file tree
Hide file tree
Showing 10 changed files with 231 additions and 13 deletions.
3 changes: 2 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
gem 'rack'
gem 'rack-mount', '~> 0.6.13'
gem 'rack-jsonp'
gem 'rack-test'

gem 'json'
gem 'multi_json'
gem 'multi_xml'

Expand All @@ -12,6 +14,5 @@ end

group :test do
gem 'rspec', '>= 2.1.0'
gem 'rack-test'
gem 'cucumber', '>= 0.8.5'
end
2 changes: 2 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ GEM
gemcutter (>= 0.1.0)
git (>= 1.2.5)
rubyforge (>= 2.0.0)
json (1.4.6)
json_pure (1.4.3)
multi_json (0.0.4)
multi_xml (0.0.1)
Expand Down Expand Up @@ -45,6 +46,7 @@ PLATFORMS
DEPENDENCIES
cucumber (>= 0.8.5)
jeweler
json
multi_json
multi_xml
rack
Expand Down
1 change: 1 addition & 0 deletions lib/grape.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module Grape
autoload :API, 'grape/api'
autoload :Endpoint, 'grape/endpoint'
autoload :MiddlewareStack, 'grape/middleware_stack'
autoload :Client, 'grape/client'

module Middleware
autoload :Base, 'grape/middleware/base'
Expand Down
60 changes: 53 additions & 7 deletions lib/grape/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

module Grape
class API
module Helpers; end

class << self
attr_reader :route_set

Expand All @@ -13,6 +15,7 @@ def reset!
end

def call(env)
puts "#{env['REQUEST_METHOD']} #{env['PATH_INFO']}"
route_set.freeze.call(env)
end

Expand Down Expand Up @@ -41,13 +44,37 @@ def prefix(prefix = nil)
prefix ? set(:root_prefix, prefix) : settings[:root_prefix]
end

def version(new_version = nil)
new_version ? set(:version, new_version) : settings[:version]
def version(*new_versions, &block)
new_versions.any? ? nest(block){ set(:version, new_versions) } : settings[:version]
end

def default_format(new_format = nil)
new_format ? set(:default_format, new_format.to_sym) : settings[:default_format]
end

# Add helper methods that will be accessible from any
# endpoint within this namespace (and child namespaces).
#
# class ExampleAPI
# helpers do
# def current_user
# User.find_by_id(params[:token])
# end
# end
# end
def helpers(&block)
if block_given?
m = settings_stack.last[:helpers] || Module.new
m.class_eval &block
set(:helpers, m)
else
m = Module.new
settings_stack.each do |s|
m.send :include, s[:helpers] if s[:helpers]
end
m
end
end

def auth(type = nil, options = {}, &block)
if type
Expand All @@ -73,7 +100,7 @@ def route_set
def compile_path(path)
parts = []
parts << prefix if prefix
parts << version if version
parts << ':version' if version
parts << namespace if namespace
parts << path
Rack::Mount::Utils.normalize_path(parts.join('/'))
Expand All @@ -87,13 +114,18 @@ def route(method, path_info, &block)
end

def build_endpoint(&block)

b = Rack::Builder.new
b.use Grape::Middleware::Error
b.use Rack::Auth::Basic, settings[:auth][:realm], &settings[:auth][:proc] if settings[:auth] && settings[:auth][:type] == :http_basic
b.use Grape::Middleware::Prefixer, :prefix => prefix if prefix
b.use Grape::Middleware::Versioner if version
b.use Grape::Middleware::Versioner, :versions => (version if version.is_a?(Array)) if version
b.use Grape::Middleware::Formatter, :default_format => default_format || :json
b.run Grape::Endpoint.new(&block)

endpoint = Grape::Endpoint.new(&block)
endpoint.send :extend, helpers
b.run endpoint

b.to_app
end

Expand All @@ -105,12 +137,26 @@ def delete(path_info = '', &block); route('DELETE', path_info, &block) end

def namespace(space = nil, &block)
if space || block_given?
nest(block) do
set(:namespace, space.to_s) if space
end
else
Rack::Mount::Utils.normalize_path(settings_stack.map{|s| s[:namespace]}.join('/'))
end
end

# Execute first the provided block, then each of the
# block passed in. Allows for simple 'before' setups
# of settings stack pushes.
def nest(*blocks, &block)
blocks.reject!{|b| b.nil?}
if blocks.any?
settings_stack << {}
set(:namespace, space.to_s) if space
instance_eval &block
blocks.each{|b| instance_eval &b}
settings_stack.pop
else
Rack::Mount::Utils.normalize_path(settings_stack.map{|s| s[:namespace]}.join('/'))
instance_eval &block
end
end

Expand Down
6 changes: 6 additions & 0 deletions lib/grape/endpoint.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ def params
end
end

def version; env['api.version'] end

def error!(message, status=403)
throw :error, :message => message, :status => status
end

# Set or retrieve the HTTP status code.
def status(status = nil)
if status
Expand Down
18 changes: 16 additions & 2 deletions lib/grape/middleware/formatter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,28 @@ def after
bodies.each do |body|
bodymap << case env['api.format']
when :json
MultiJson.encode(body)
encode_json(body)
when :txt
body.to_s
encode_txt(body)
end
end
headers['Content-Type'] = 'application/json'
Rack::Response.new(bodymap, status, headers).to_a
end

def encode_json(object)
if object.respond_to? :serializable_hash
MultiJson.encode(object.serializable_hash)
elsif object.respond_to? :to_json
object.to_json
else
MultiJson.encode(object)
end
end

def encode_txt(object)
body.respond_to?(:to_txt) ? body.to_txt : body.to_s
end
end
end
end
2 changes: 1 addition & 1 deletion lib/grape/middleware/versioner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def before
potential_version = pieces[1]
if potential_version =~ options[:pattern]
if options[:versions] && !options[:versions].include?(potential_version)
throw :error, :status => 404, :message => "The specified version of the API does not exist."
throw :error, :status => 404, :message => "404 API Version Not Found"
end

truncated_path = "/#{pieces[2..-1].join('/')}"
Expand Down
106 changes: 104 additions & 2 deletions spec/grape/api_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,42 @@ def app; subject end
get '/api/v1/hello'
last_response.body.should == "Version: v1"
end

it 'should be able to specify version as a nesting' do
subject.version 'v2'
subject.get '/awesome' do
"Radical"
end

subject.version 'v1' do
get '/legacy' do
"Totally"
end
end

get '/v1/awesome'
last_response.status.should == 404
get '/v2/awesome'
last_response.status.should == 200
get '/v1/legacy'
last_response.status.should == 200
get '/v2/legacy'
last_response.status.should == 404
end

it 'should be able to specify multiple versions' do
subject.version 'v1', 'v2'
subject.get 'awesome' do
"I exist"
end

get '/v1/awesome'
last_response.status.should == 200
get '/v2/awesome'
last_response.status.should == 200
get '/v3/awesome'
last_response.status.should == 404
end
end

describe '.namespace' do
Expand All @@ -56,7 +92,7 @@ def app; subject end
subject.version :v1

subject.namespace :awesome do
compile_path('hello').should == '/rad/v1/awesome/hello'
compile_path('hello').should == '/rad/:version/awesome/hello'
end
end

Expand All @@ -81,7 +117,7 @@ def app; subject end
it 'should be callable with nil just to push onto the stack' do
subject.namespace do
version 'v2'
compile_path('hello').should == '/v2/hello'
compile_path('hello').should == '/:version/hello'
end
subject.compile_path('hello').should == '/hello'
end
Expand Down Expand Up @@ -178,4 +214,70 @@ def app; subject end
last_response.status.should == 200
end
end

describe '.helpers' do
it 'should be accessible from the endpoint' do
subject.helpers do
def hello
"Hello, world."
end
end

subject.get '/howdy' do
hello
end

get '/howdy'
last_response.body.should == '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 == 'always there:false'
get '/admin/secret'
last_response.body.should == '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
end
end
23 changes: 23 additions & 0 deletions spec/grape/endpoint_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,27 @@ def app; subject end
last_response.body.should == '12'
end
end

describe '#error!' do
it 'should accept a message' do
subject.get('/hey') do
error! "This is not valid."
"This is valid."
end

get '/hey'
last_response.status.should == 403
last_response.body.should == "This is not valid."
end

it 'should accept a code' do
subject.get('/hey') do
error! "Unauthorized.", 401
end

get '/hey'
last_response.status.should == 401
last_response.body.should == "Unauthorized."
end
end
end
Loading

0 comments on commit 98ca5a8

Please sign in to comment.