Skip to content

Commit 98ca5a8

Browse files
author
Michael Bleigh
committed
Updates.
1 parent 87b3d15 commit 98ca5a8

File tree

10 files changed

+231
-13
lines changed

10 files changed

+231
-13
lines changed

Gemfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
gem 'rack'
22
gem 'rack-mount', '~> 0.6.13'
33
gem 'rack-jsonp'
4+
gem 'rack-test'
45

6+
gem 'json'
57
gem 'multi_json'
68
gem 'multi_xml'
79

@@ -12,6 +14,5 @@ end
1214

1315
group :test do
1416
gem 'rspec', '>= 2.1.0'
15-
gem 'rack-test'
1617
gem 'cucumber', '>= 0.8.5'
1718
end

Gemfile.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ GEM
1616
gemcutter (>= 0.1.0)
1717
git (>= 1.2.5)
1818
rubyforge (>= 2.0.0)
19+
json (1.4.6)
1920
json_pure (1.4.3)
2021
multi_json (0.0.4)
2122
multi_xml (0.0.1)
@@ -45,6 +46,7 @@ PLATFORMS
4546
DEPENDENCIES
4647
cucumber (>= 0.8.5)
4748
jeweler
49+
json
4850
multi_json
4951
multi_xml
5052
rack

lib/grape.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ module Grape
55
autoload :API, 'grape/api'
66
autoload :Endpoint, 'grape/endpoint'
77
autoload :MiddlewareStack, 'grape/middleware_stack'
8+
autoload :Client, 'grape/client'
89

910
module Middleware
1011
autoload :Base, 'grape/middleware/base'

lib/grape/api.rb

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
module Grape
55
class API
6+
module Helpers; end
7+
68
class << self
79
attr_reader :route_set
810

@@ -13,6 +15,7 @@ def reset!
1315
end
1416

1517
def call(env)
18+
puts "#{env['REQUEST_METHOD']} #{env['PATH_INFO']}"
1619
route_set.freeze.call(env)
1720
end
1821

@@ -41,13 +44,37 @@ def prefix(prefix = nil)
4144
prefix ? set(:root_prefix, prefix) : settings[:root_prefix]
4245
end
4346

44-
def version(new_version = nil)
45-
new_version ? set(:version, new_version) : settings[:version]
47+
def version(*new_versions, &block)
48+
new_versions.any? ? nest(block){ set(:version, new_versions) } : settings[:version]
4649
end
4750

4851
def default_format(new_format = nil)
4952
new_format ? set(:default_format, new_format.to_sym) : settings[:default_format]
5053
end
54+
55+
# Add helper methods that will be accessible from any
56+
# endpoint within this namespace (and child namespaces).
57+
#
58+
# class ExampleAPI
59+
# helpers do
60+
# def current_user
61+
# User.find_by_id(params[:token])
62+
# end
63+
# end
64+
# end
65+
def helpers(&block)
66+
if block_given?
67+
m = settings_stack.last[:helpers] || Module.new
68+
m.class_eval &block
69+
set(:helpers, m)
70+
else
71+
m = Module.new
72+
settings_stack.each do |s|
73+
m.send :include, s[:helpers] if s[:helpers]
74+
end
75+
m
76+
end
77+
end
5178

5279
def auth(type = nil, options = {}, &block)
5380
if type
@@ -73,7 +100,7 @@ def route_set
73100
def compile_path(path)
74101
parts = []
75102
parts << prefix if prefix
76-
parts << version if version
103+
parts << ':version' if version
77104
parts << namespace if namespace
78105
parts << path
79106
Rack::Mount::Utils.normalize_path(parts.join('/'))
@@ -87,13 +114,18 @@ def route(method, path_info, &block)
87114
end
88115

89116
def build_endpoint(&block)
117+
90118
b = Rack::Builder.new
91119
b.use Grape::Middleware::Error
92120
b.use Rack::Auth::Basic, settings[:auth][:realm], &settings[:auth][:proc] if settings[:auth] && settings[:auth][:type] == :http_basic
93121
b.use Grape::Middleware::Prefixer, :prefix => prefix if prefix
94-
b.use Grape::Middleware::Versioner if version
122+
b.use Grape::Middleware::Versioner, :versions => (version if version.is_a?(Array)) if version
95123
b.use Grape::Middleware::Formatter, :default_format => default_format || :json
96-
b.run Grape::Endpoint.new(&block)
124+
125+
endpoint = Grape::Endpoint.new(&block)
126+
endpoint.send :extend, helpers
127+
b.run endpoint
128+
97129
b.to_app
98130
end
99131

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

106138
def namespace(space = nil, &block)
107139
if space || block_given?
140+
nest(block) do
141+
set(:namespace, space.to_s) if space
142+
end
143+
else
144+
Rack::Mount::Utils.normalize_path(settings_stack.map{|s| s[:namespace]}.join('/'))
145+
end
146+
end
147+
148+
# Execute first the provided block, then each of the
149+
# block passed in. Allows for simple 'before' setups
150+
# of settings stack pushes.
151+
def nest(*blocks, &block)
152+
blocks.reject!{|b| b.nil?}
153+
if blocks.any?
108154
settings_stack << {}
109-
set(:namespace, space.to_s) if space
110155
instance_eval &block
156+
blocks.each{|b| instance_eval &b}
111157
settings_stack.pop
112158
else
113-
Rack::Mount::Utils.normalize_path(settings_stack.map{|s| s[:namespace]}.join('/'))
159+
instance_eval &block
114160
end
115161
end
116162

lib/grape/endpoint.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ def params
2121
end
2222
end
2323

24+
def version; env['api.version'] end
25+
26+
def error!(message, status=403)
27+
throw :error, :message => message, :status => status
28+
end
29+
2430
# Set or retrieve the HTTP status code.
2531
def status(status = nil)
2632
if status

lib/grape/middleware/formatter.rb

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,14 +79,28 @@ def after
7979
bodies.each do |body|
8080
bodymap << case env['api.format']
8181
when :json
82-
MultiJson.encode(body)
82+
encode_json(body)
8383
when :txt
84-
body.to_s
84+
encode_txt(body)
8585
end
8686
end
8787
headers['Content-Type'] = 'application/json'
8888
Rack::Response.new(bodymap, status, headers).to_a
8989
end
90+
91+
def encode_json(object)
92+
if object.respond_to? :serializable_hash
93+
MultiJson.encode(object.serializable_hash)
94+
elsif object.respond_to? :to_json
95+
object.to_json
96+
else
97+
MultiJson.encode(object)
98+
end
99+
end
100+
101+
def encode_txt(object)
102+
body.respond_to?(:to_txt) ? body.to_txt : body.to_s
103+
end
90104
end
91105
end
92106
end

lib/grape/middleware/versioner.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def before
1414
potential_version = pieces[1]
1515
if potential_version =~ options[:pattern]
1616
if options[:versions] && !options[:versions].include?(potential_version)
17-
throw :error, :status => 404, :message => "The specified version of the API does not exist."
17+
throw :error, :status => 404, :message => "404 API Version Not Found"
1818
end
1919

2020
truncated_path = "/#{pieces[2..-1].join('/')}"

spec/grape/api_spec.rb

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,42 @@ def app; subject end
4242
get '/api/v1/hello'
4343
last_response.body.should == "Version: v1"
4444
end
45+
46+
it 'should be able to specify version as a nesting' do
47+
subject.version 'v2'
48+
subject.get '/awesome' do
49+
"Radical"
50+
end
51+
52+
subject.version 'v1' do
53+
get '/legacy' do
54+
"Totally"
55+
end
56+
end
57+
58+
get '/v1/awesome'
59+
last_response.status.should == 404
60+
get '/v2/awesome'
61+
last_response.status.should == 200
62+
get '/v1/legacy'
63+
last_response.status.should == 200
64+
get '/v2/legacy'
65+
last_response.status.should == 404
66+
end
67+
68+
it 'should be able to specify multiple versions' do
69+
subject.version 'v1', 'v2'
70+
subject.get 'awesome' do
71+
"I exist"
72+
end
73+
74+
get '/v1/awesome'
75+
last_response.status.should == 200
76+
get '/v2/awesome'
77+
last_response.status.should == 200
78+
get '/v3/awesome'
79+
last_response.status.should == 404
80+
end
4581
end
4682

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

5894
subject.namespace :awesome do
59-
compile_path('hello').should == '/rad/v1/awesome/hello'
95+
compile_path('hello').should == '/rad/:version/awesome/hello'
6096
end
6197
end
6298

@@ -81,7 +117,7 @@ def app; subject end
81117
it 'should be callable with nil just to push onto the stack' do
82118
subject.namespace do
83119
version 'v2'
84-
compile_path('hello').should == '/v2/hello'
120+
compile_path('hello').should == '/:version/hello'
85121
end
86122
subject.compile_path('hello').should == '/hello'
87123
end
@@ -178,4 +214,70 @@ def app; subject end
178214
last_response.status.should == 200
179215
end
180216
end
217+
218+
describe '.helpers' do
219+
it 'should be accessible from the endpoint' do
220+
subject.helpers do
221+
def hello
222+
"Hello, world."
223+
end
224+
end
225+
226+
subject.get '/howdy' do
227+
hello
228+
end
229+
230+
get '/howdy'
231+
last_response.body.should == 'Hello, world.'
232+
end
233+
234+
it 'should be scopable' do
235+
subject.helpers do
236+
def generic
237+
'always there'
238+
end
239+
end
240+
241+
subject.namespace :admin do
242+
helpers do
243+
def secret
244+
'only in admin'
245+
end
246+
end
247+
248+
get '/secret' do
249+
[generic, secret].join ':'
250+
end
251+
end
252+
253+
subject.get '/generic' do
254+
[generic, respond_to?(:secret)].join ':'
255+
end
256+
257+
get '/generic'
258+
last_response.body.should == 'always there:false'
259+
get '/admin/secret'
260+
last_response.body.should == 'always there:only in admin'
261+
end
262+
263+
it 'should be reopenable' do
264+
subject.helpers do
265+
def one
266+
1
267+
end
268+
end
269+
270+
subject.helpers do
271+
def two
272+
2
273+
end
274+
end
275+
276+
subject.get 'howdy' do
277+
[one, two]
278+
end
279+
280+
lambda{get '/howdy'}.should_not raise_error
281+
end
282+
end
181283
end

spec/grape/endpoint_spec.rb

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,27 @@ def app; subject end
4949
last_response.body.should == '12'
5050
end
5151
end
52+
53+
describe '#error!' do
54+
it 'should accept a message' do
55+
subject.get('/hey') do
56+
error! "This is not valid."
57+
"This is valid."
58+
end
59+
60+
get '/hey'
61+
last_response.status.should == 403
62+
last_response.body.should == "This is not valid."
63+
end
64+
65+
it 'should accept a code' do
66+
subject.get('/hey') do
67+
error! "Unauthorized.", 401
68+
end
69+
70+
get '/hey'
71+
last_response.status.should == 401
72+
last_response.body.should == "Unauthorized."
73+
end
74+
end
5275
end

0 commit comments

Comments
 (0)