grape 0.2.2 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of grape might be problematic. Click here for more details.
- data/CHANGELOG.markdown +17 -3
- data/Gemfile +1 -1
- data/README.markdown +269 -171
- data/grape.gemspec +1 -0
- data/lib/grape.rb +43 -22
- data/lib/grape/api.rb +7 -4
- data/lib/grape/endpoint.rb +48 -9
- data/lib/grape/entity.rb +1 -1
- data/lib/grape/error_formatter/base.rb +32 -0
- data/lib/grape/error_formatter/json.rb +17 -0
- data/lib/grape/error_formatter/txt.rb +18 -0
- data/lib/grape/error_formatter/xml.rb +17 -0
- data/lib/grape/exceptions/validation_error.rb +9 -5
- data/lib/grape/formatter/base.rb +33 -0
- data/lib/grape/formatter/json.rb +15 -0
- data/lib/grape/formatter/serializable_hash.rb +35 -0
- data/lib/grape/formatter/txt.rb +13 -0
- data/lib/grape/formatter/xml.rb +13 -0
- data/lib/grape/middleware/base.rb +18 -103
- data/lib/grape/middleware/error.rb +16 -32
- data/lib/grape/middleware/formatter.rb +8 -9
- data/lib/grape/middleware/versioner.rb +3 -2
- data/lib/grape/middleware/versioner/header.rb +1 -1
- data/lib/grape/parser/base.rb +31 -0
- data/lib/grape/parser/json.rb +13 -0
- data/lib/grape/parser/xml.rb +13 -0
- data/lib/grape/validations.rb +1 -1
- data/lib/grape/validations/coerce.rb +1 -1
- data/lib/grape/validations/presence.rb +1 -1
- data/lib/grape/validations/regexp.rb +1 -1
- data/lib/grape/version.rb +1 -1
- data/spec/grape/api_spec.rb +183 -9
- data/spec/grape/endpoint_spec.rb +27 -1
- data/spec/grape/entity_spec.rb +21 -21
- data/spec/grape/middleware/base_spec.rb +15 -15
- data/spec/grape/middleware/exception_spec.rb +38 -16
- data/spec/grape/middleware/formatter_spec.rb +6 -40
- data/spec/grape/validations/presence_spec.rb +20 -20
- data/spec/spec_helper.rb +1 -1
- metadata +132 -58
@@ -5,6 +5,16 @@ require 'multi_xml'
|
|
5
5
|
module Grape
|
6
6
|
module Middleware
|
7
7
|
class Base
|
8
|
+
# Content types are listed in order of preference.
|
9
|
+
CONTENT_TYPES = ActiveSupport::OrderedHash[
|
10
|
+
:xml, 'application/xml',
|
11
|
+
:serializable_hash, 'application/json',
|
12
|
+
:json, 'application/json',
|
13
|
+
:atom, 'application/atom+xml',
|
14
|
+
:rss, 'application/rss+xml',
|
15
|
+
:txt, 'text/plain',
|
16
|
+
]
|
17
|
+
|
8
18
|
attr_reader :app, :env, :options
|
9
19
|
|
10
20
|
# @param [Rack Application] app The standard argument for a Rack middleware.
|
@@ -43,111 +53,16 @@ module Grape
|
|
43
53
|
Rack::Response.new(@app_response)
|
44
54
|
end
|
45
55
|
|
56
|
+
def content_types
|
57
|
+
options[:content_types] || CONTENT_TYPES
|
58
|
+
end
|
46
59
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
:xml, 'application/xml',
|
51
|
-
:json, 'application/json',
|
52
|
-
:atom, 'application/atom+xml',
|
53
|
-
:rss, 'application/rss+xml',
|
54
|
-
:txt, 'text/plain',
|
55
|
-
]
|
56
|
-
FORMATTERS = {
|
57
|
-
:json => :encode_json,
|
58
|
-
:txt => :encode_txt,
|
59
|
-
:xml => :encode_xml
|
60
|
-
}
|
61
|
-
PARSERS = {
|
62
|
-
:json => :decode_json,
|
63
|
-
:xml => :decode_xml
|
64
|
-
}
|
65
|
-
|
66
|
-
def formatters
|
67
|
-
FORMATTERS.merge(options[:formatters] || {})
|
68
|
-
end
|
69
|
-
|
70
|
-
def parsers
|
71
|
-
PARSERS.merge(options[:parsers] || {})
|
72
|
-
end
|
73
|
-
|
74
|
-
def content_types
|
75
|
-
CONTENT_TYPES.merge(options[:content_types] || {})
|
76
|
-
end
|
77
|
-
|
78
|
-
def content_type
|
79
|
-
content_types[options[:format]] || 'text/html'
|
80
|
-
end
|
81
|
-
|
82
|
-
def mime_types
|
83
|
-
content_types.invert
|
84
|
-
end
|
85
|
-
|
86
|
-
def formatter_for(api_format)
|
87
|
-
spec = formatters[api_format]
|
88
|
-
case spec
|
89
|
-
when nil
|
90
|
-
lambda { |obj| obj }
|
91
|
-
when Symbol
|
92
|
-
method(spec)
|
93
|
-
else
|
94
|
-
spec
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
def parser_for(api_format)
|
99
|
-
spec = parsers[api_format]
|
100
|
-
case spec
|
101
|
-
when nil
|
102
|
-
nil
|
103
|
-
when Symbol
|
104
|
-
method(spec)
|
105
|
-
else
|
106
|
-
spec
|
107
|
-
end
|
108
|
-
end
|
109
|
-
|
110
|
-
def decode_json(object)
|
111
|
-
MultiJson.load(object)
|
112
|
-
end
|
113
|
-
|
114
|
-
def serializable?(object)
|
115
|
-
object.respond_to?(:serializable_hash) ||
|
116
|
-
object.kind_of?(Array) && !object.map {|o| o.respond_to? :serializable_hash }.include?(false) ||
|
117
|
-
object.kind_of?(Hash)
|
118
|
-
end
|
119
|
-
|
120
|
-
def serialize(object)
|
121
|
-
if object.respond_to? :serializable_hash
|
122
|
-
object.serializable_hash
|
123
|
-
elsif object.kind_of?(Array) && !object.map {|o| o.respond_to? :serializable_hash }.include?(false)
|
124
|
-
object.map {|o| o.serializable_hash }
|
125
|
-
elsif object.kind_of?(Hash)
|
126
|
-
object.inject({}) { |h,(k,v)| h[k] = serialize(v); h }
|
127
|
-
else
|
128
|
-
object
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
def encode_json(object)
|
133
|
-
return object if object.is_a?(String)
|
134
|
-
return MultiJson.dump(serialize(object)) if serializable?(object)
|
135
|
-
return object.to_json if object.respond_to?(:to_json)
|
136
|
-
|
137
|
-
MultiJson.dump(object)
|
138
|
-
end
|
139
|
-
|
140
|
-
def encode_txt(object)
|
141
|
-
object.respond_to?(:to_txt) ? object.to_txt : object.to_s
|
142
|
-
end
|
143
|
-
|
144
|
-
def decode_xml(object)
|
145
|
-
MultiXml.parse(object)
|
146
|
-
end
|
60
|
+
def content_type
|
61
|
+
content_types[env['api.format'] || options[:format]] || 'text/html'
|
62
|
+
end
|
147
63
|
|
148
|
-
|
149
|
-
|
150
|
-
end
|
64
|
+
def mime_types
|
65
|
+
content_types.invert
|
151
66
|
end
|
152
67
|
|
153
68
|
end
|
@@ -4,44 +4,27 @@ require 'multi_json'
|
|
4
4
|
module Grape
|
5
5
|
module Middleware
|
6
6
|
class Error < Base
|
7
|
-
include Formats
|
8
7
|
|
9
8
|
def default_options
|
10
|
-
{
|
9
|
+
{
|
11
10
|
:default_status => 403, # default status returned on error
|
12
11
|
:default_message => "",
|
13
12
|
:format => :txt,
|
14
13
|
:formatters => {},
|
15
|
-
:
|
14
|
+
:error_formatters => {},
|
15
|
+
:rescue_all => false, # true to rescue all exceptions
|
16
16
|
:rescue_options => { :backtrace => false }, # true to display backtrace
|
17
17
|
:rescue_handlers => {}, # rescue handler blocks
|
18
18
|
:rescued_errors => []
|
19
19
|
}
|
20
20
|
end
|
21
21
|
|
22
|
-
def encode_json(message, backtrace)
|
23
|
-
result = message.is_a?(Hash) ? message : { :error => message }
|
24
|
-
if (options[:rescue_options] || {})[:backtrace] && backtrace && ! backtrace.empty?
|
25
|
-
result = result.merge({ :backtrace => backtrace })
|
26
|
-
end
|
27
|
-
MultiJson.dump(result)
|
28
|
-
end
|
29
|
-
|
30
|
-
def encode_txt(message, backtrace)
|
31
|
-
result = message.is_a?(Hash) ? MultiJson.dump(message) : message
|
32
|
-
if (options[:rescue_options] || {})[:backtrace] && backtrace && ! backtrace.empty?
|
33
|
-
result += "\r\n "
|
34
|
-
result += backtrace.join("\r\n ")
|
35
|
-
end
|
36
|
-
result
|
37
|
-
end
|
38
|
-
|
39
22
|
def call!(env)
|
40
23
|
@env = env
|
41
|
-
|
24
|
+
|
42
25
|
begin
|
43
|
-
error_response(catch(:error){
|
44
|
-
return @app.call(@env)
|
26
|
+
error_response(catch(:error){
|
27
|
+
return @app.call(@env)
|
45
28
|
})
|
46
29
|
rescue Exception => e
|
47
30
|
is_rescuable = rescuable?(e.class)
|
@@ -58,30 +41,31 @@ module Grape
|
|
58
41
|
def rescuable?(klass)
|
59
42
|
options[:rescue_all] || (options[:rescued_errors] || []).include?(klass)
|
60
43
|
end
|
61
|
-
|
44
|
+
|
62
45
|
def handle_error(e)
|
63
46
|
error_response({ :message => e.message, :backtrace => e.backtrace })
|
64
47
|
end
|
65
|
-
|
48
|
+
|
66
49
|
def error_response(error = {})
|
67
50
|
status = error[:status] || options[:default_status]
|
68
51
|
message = error[:message] || options[:default_message]
|
69
52
|
headers = {'Content-Type' => content_type}
|
70
53
|
headers.merge!(error[:headers]) if error[:headers].is_a?(Hash)
|
71
54
|
backtrace = error[:backtrace] || []
|
72
|
-
rack_response(format_message(message, backtrace
|
55
|
+
rack_response(format_message(message, backtrace), status, headers)
|
73
56
|
end
|
74
57
|
|
75
58
|
def rack_response(message, status = options[:default_status], headers = { 'Content-Type' => content_type })
|
76
59
|
Rack::Response.new([ message ], status, headers).finish
|
77
60
|
end
|
78
|
-
|
79
|
-
def format_message(message, backtrace
|
80
|
-
|
81
|
-
|
82
|
-
|
61
|
+
|
62
|
+
def format_message(message, backtrace)
|
63
|
+
format = env['api.format'] || options[:format]
|
64
|
+
formatter = Grape::ErrorFormatter::Base.formatter_for(format, options)
|
65
|
+
throw :error, :status => 406, :message => "The requested format #{format} is not supported." unless formatter
|
66
|
+
formatter.call(message, backtrace, options, env)
|
83
67
|
end
|
84
|
-
|
68
|
+
|
85
69
|
end
|
86
70
|
end
|
87
71
|
end
|
@@ -3,13 +3,11 @@ require 'grape/middleware/base'
|
|
3
3
|
module Grape
|
4
4
|
module Middleware
|
5
5
|
class Formatter < Base
|
6
|
-
include Formats
|
7
6
|
|
8
7
|
def default_options
|
9
8
|
{
|
10
9
|
:default_format => :txt,
|
11
10
|
:formatters => {},
|
12
|
-
:content_types => {},
|
13
11
|
:parsers => {}
|
14
12
|
}
|
15
13
|
end
|
@@ -22,10 +20,10 @@ module Grape
|
|
22
20
|
fmt = format_from_extension || format_from_params || options[:format] || format_from_header || options[:default_format]
|
23
21
|
if content_types.key?(fmt)
|
24
22
|
if !env['rack.input'].nil? and (body = env['rack.input'].read).strip.length != 0
|
25
|
-
parser = parser_for fmt
|
23
|
+
parser = Grape::Parser::Base.parser_for fmt, options
|
26
24
|
unless parser.nil?
|
27
25
|
begin
|
28
|
-
body = parser.call
|
26
|
+
body = parser.call body, env
|
29
27
|
env['rack.request.form_hash'] = !env['rack.request.form_hash'].nil? ? env['rack.request.form_hash'].merge(body) : body
|
30
28
|
env['rack.request.form_input'] = env['rack.input']
|
31
29
|
rescue
|
@@ -42,10 +40,10 @@ module Grape
|
|
42
40
|
|
43
41
|
def format_from_extension
|
44
42
|
parts = request.path.split('.')
|
45
|
-
extension = parts.last.to_sym
|
46
43
|
|
47
|
-
if parts.size > 1
|
48
|
-
|
44
|
+
if parts.size > 1
|
45
|
+
extension = parts.last.to_sym
|
46
|
+
return extension if content_types.key?(extension)
|
49
47
|
end
|
50
48
|
nil
|
51
49
|
end
|
@@ -74,13 +72,14 @@ module Grape
|
|
74
72
|
|
75
73
|
def after
|
76
74
|
status, headers, bodies = *@app_response
|
77
|
-
formatter = formatter_for env['api.format']
|
75
|
+
formatter = Grape::Formatter::Base.formatter_for env['api.format'], options
|
78
76
|
bodymap = bodies.collect do |body|
|
79
|
-
formatter.call
|
77
|
+
formatter.call body, env
|
80
78
|
end
|
81
79
|
headers['Content-Type'] = content_types[env['api.format']] unless headers['Content-Type']
|
82
80
|
Rack::Response.new(bodymap, status, headers).to_a
|
83
81
|
end
|
82
|
+
|
84
83
|
end
|
85
84
|
end
|
86
85
|
end
|
@@ -3,6 +3,7 @@
|
|
3
3
|
#
|
4
4
|
# :header - version from HTTP Accept header.
|
5
5
|
# :path - version from uri. e.g. /v1/resource
|
6
|
+
# :param - version from uri query string, e.g. /v1/resource?apiver=v1
|
6
7
|
#
|
7
8
|
# See individual classes for details.
|
8
9
|
module Grape
|
@@ -10,7 +11,7 @@ module Grape
|
|
10
11
|
module Versioner
|
11
12
|
extend self
|
12
13
|
|
13
|
-
# @param strategy [Symbol] :path or :
|
14
|
+
# @param strategy [Symbol] :path, :header or :param
|
14
15
|
# @return a middleware class based on strategy
|
15
16
|
def using(strategy)
|
16
17
|
case strategy
|
@@ -26,4 +27,4 @@ module Grape
|
|
26
27
|
end
|
27
28
|
end
|
28
29
|
end
|
29
|
-
end
|
30
|
+
end
|
@@ -25,7 +25,6 @@ module Grape
|
|
25
25
|
#
|
26
26
|
# @throws [RuntimeError] if Accept header is invalid
|
27
27
|
class Header < Base
|
28
|
-
include Formats
|
29
28
|
|
30
29
|
def before
|
31
30
|
header = Rack::Accept::MediaType.new env['HTTP_ACCEPT']
|
@@ -112,6 +111,7 @@ module Grape
|
|
112
111
|
type, subtype = Rack::Accept::Header.parse_media_type media_type
|
113
112
|
subtype[/\Avnd\.[a-z0-9*.]+-[a-z0-9*\-.]+/]
|
114
113
|
end
|
114
|
+
|
115
115
|
end
|
116
116
|
end
|
117
117
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Grape
|
2
|
+
module Parser
|
3
|
+
module Base
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
PARSERS = {
|
8
|
+
:json => Grape::Parser::Json,
|
9
|
+
:xml => Grape::Parser::Xml
|
10
|
+
}
|
11
|
+
|
12
|
+
def parsers(options)
|
13
|
+
PARSERS.merge(options[:parsers] || {})
|
14
|
+
end
|
15
|
+
|
16
|
+
def parser_for(api_format, options = {})
|
17
|
+
spec = parsers(options)[api_format]
|
18
|
+
case spec
|
19
|
+
when nil
|
20
|
+
nil
|
21
|
+
when Symbol
|
22
|
+
method(spec)
|
23
|
+
else
|
24
|
+
spec
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/grape/validations.rb
CHANGED
@@ -12,7 +12,7 @@ module Grape
|
|
12
12
|
if valid_type?(new_value)
|
13
13
|
params[attr_name] = new_value
|
14
14
|
else
|
15
|
-
raise ValidationError, :status => 400, :param => attr_name, :message => "invalid parameter: #{attr_name}"
|
15
|
+
raise Grape::Exceptions::ValidationError, :status => 400, :param => attr_name, :message => "invalid parameter: #{attr_name}"
|
16
16
|
end
|
17
17
|
end
|
18
18
|
|
@@ -3,7 +3,7 @@ module Grape
|
|
3
3
|
class PresenceValidator < Validator
|
4
4
|
def validate_param!(attr_name, params)
|
5
5
|
unless params.has_key?(attr_name)
|
6
|
-
raise ValidationError, :status => 400, :param => attr_name, :message => "missing parameter: #{attr_name}"
|
6
|
+
raise Grape::Exceptions::ValidationError, :status => 400, :param => attr_name, :message => "missing parameter: #{attr_name}"
|
7
7
|
end
|
8
8
|
end
|
9
9
|
end
|
@@ -4,7 +4,7 @@ module Grape
|
|
4
4
|
class RegexpValidator < SingleOptionValidator
|
5
5
|
def validate_param!(attr_name, params)
|
6
6
|
if params[attr_name] && !( params[attr_name].to_s =~ @option )
|
7
|
-
raise ValidationError, :status => 400, :param => attr_name, :message => "invalid parameter: #{attr_name}"
|
7
|
+
raise Grape::Exceptions::ValidationError, :status => 400, :param => attr_name, :message => "invalid parameter: #{attr_name}"
|
8
8
|
end
|
9
9
|
end
|
10
10
|
end
|
data/lib/grape/version.rb
CHANGED
data/spec/grape/api_spec.rb
CHANGED
@@ -189,6 +189,53 @@ describe Grape::API do
|
|
189
189
|
last_response.body.should eql 'Created a Vote'
|
190
190
|
end
|
191
191
|
|
192
|
+
describe "root routes should work with" do
|
193
|
+
before do
|
194
|
+
def subject.enable_root_route!
|
195
|
+
self.get("/") {"root"}
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
after do
|
200
|
+
last_response.body.should eql 'root'
|
201
|
+
end
|
202
|
+
|
203
|
+
describe "path versioned APIs" do
|
204
|
+
before do
|
205
|
+
subject.version 'v1', :using => :path
|
206
|
+
subject.enable_root_route!
|
207
|
+
end
|
208
|
+
|
209
|
+
it "without a format" do
|
210
|
+
versioned_get "/", "v1", :using => :path
|
211
|
+
end
|
212
|
+
|
213
|
+
it "with a format" do
|
214
|
+
get "/v1/.json"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
it "header versioned APIs" do
|
219
|
+
subject.version 'v1', :using => :header, :vendor => 'test'
|
220
|
+
subject.enable_root_route!
|
221
|
+
|
222
|
+
versioned_get "/", "v1", :using => :header
|
223
|
+
end
|
224
|
+
|
225
|
+
it "param versioned APIs" do
|
226
|
+
subject.version 'v1', :using => :param
|
227
|
+
subject.enable_root_route!
|
228
|
+
|
229
|
+
versioned_get "/", "v1", :using => :param
|
230
|
+
end
|
231
|
+
|
232
|
+
it "unversioned APIs" do
|
233
|
+
subject.enable_root_route!
|
234
|
+
|
235
|
+
get "/"
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
192
239
|
it 'should allow for multiple paths' do
|
193
240
|
subject.get(["/abc", "/def"]) do
|
194
241
|
"foo"
|
@@ -409,11 +456,18 @@ describe Grape::API do
|
|
409
456
|
end
|
410
457
|
|
411
458
|
it 'should set content type for error' do
|
412
|
-
subject.
|
459
|
+
subject.format :json
|
413
460
|
subject.get('/error') { error!('error in json', 500) }
|
414
461
|
get '/error.json'
|
415
462
|
last_response.headers['Content-Type'].should eql 'application/json'
|
416
463
|
end
|
464
|
+
|
465
|
+
it 'should set content type for xml' do
|
466
|
+
subject.format :xml
|
467
|
+
subject.get('/error') { error!('error in xml', 500) }
|
468
|
+
get '/error.xml'
|
469
|
+
last_response.headers['Content-Type'].should eql 'application/xml'
|
470
|
+
end
|
417
471
|
end
|
418
472
|
|
419
473
|
context 'custom middleware' do
|
@@ -821,10 +875,10 @@ describe Grape::API do
|
|
821
875
|
end
|
822
876
|
end
|
823
877
|
|
824
|
-
describe ".
|
878
|
+
describe ".format for error" do
|
825
879
|
it 'should rescue all errors and return :txt' do
|
826
880
|
subject.rescue_from :all
|
827
|
-
subject.
|
881
|
+
subject.format :txt
|
828
882
|
subject.get '/exception' do
|
829
883
|
raise "rain!"
|
830
884
|
end
|
@@ -832,9 +886,9 @@ describe Grape::API do
|
|
832
886
|
last_response.body.should eql "rain!"
|
833
887
|
end
|
834
888
|
|
835
|
-
it 'should rescue all
|
889
|
+
it 'should rescue all errors and return :txt with backtrace' do
|
836
890
|
subject.rescue_from :all, :backtrace => true
|
837
|
-
subject.
|
891
|
+
subject.format :txt
|
838
892
|
subject.get '/exception' do
|
839
893
|
raise "rain!"
|
840
894
|
end
|
@@ -842,9 +896,28 @@ describe Grape::API do
|
|
842
896
|
last_response.body.start_with?("rain!\r\n").should be_true
|
843
897
|
end
|
844
898
|
|
899
|
+
context "class" do
|
900
|
+
before :each do
|
901
|
+
class CustomErrorFormatter
|
902
|
+
def self.call(message, backtrace, options, env)
|
903
|
+
"message: #{message} @backtrace"
|
904
|
+
end
|
905
|
+
end
|
906
|
+
end
|
907
|
+
it 'should return a custom error format' do
|
908
|
+
subject.rescue_from :all, :backtrace => true
|
909
|
+
subject.error_formatter :txt, CustomErrorFormatter
|
910
|
+
subject.get '/exception' do
|
911
|
+
raise "rain!"
|
912
|
+
end
|
913
|
+
get '/exception'
|
914
|
+
last_response.body.should == "message: rain! @backtrace"
|
915
|
+
end
|
916
|
+
end
|
917
|
+
|
845
918
|
it 'should rescue all errors and return :json' do
|
846
919
|
subject.rescue_from :all
|
847
|
-
subject.
|
920
|
+
subject.format :json
|
848
921
|
subject.get '/exception' do
|
849
922
|
raise "rain!"
|
850
923
|
end
|
@@ -853,7 +926,7 @@ describe Grape::API do
|
|
853
926
|
end
|
854
927
|
it 'should rescue all errors and return :json with backtrace' do
|
855
928
|
subject.rescue_from :all, :backtrace => true
|
856
|
-
subject.
|
929
|
+
subject.format :json
|
857
930
|
subject.get '/exception' do
|
858
931
|
raise "rain!"
|
859
932
|
end
|
@@ -863,7 +936,7 @@ describe Grape::API do
|
|
863
936
|
json["backtrace"].length.should > 0
|
864
937
|
end
|
865
938
|
it 'should rescue error! and return txt' do
|
866
|
-
subject.
|
939
|
+
subject.format :txt
|
867
940
|
subject.get '/error' do
|
868
941
|
error!("Access Denied", 401)
|
869
942
|
end
|
@@ -871,7 +944,7 @@ describe Grape::API do
|
|
871
944
|
last_response.body.should eql "Access Denied"
|
872
945
|
end
|
873
946
|
it 'should rescue error! and return json' do
|
874
|
-
subject.
|
947
|
+
subject.format :json
|
875
948
|
subject.get '/error' do
|
876
949
|
error!("Access Denied", 401)
|
877
950
|
end
|
@@ -897,6 +970,76 @@ describe Grape::API do
|
|
897
970
|
get '/content'
|
898
971
|
last_response.content_type.should == "text/javascript"
|
899
972
|
end
|
973
|
+
it "removes existing content types" do
|
974
|
+
subject.content_type :xls, "application/vnd.ms-excel"
|
975
|
+
subject.get :excel do
|
976
|
+
"some binary content"
|
977
|
+
end
|
978
|
+
get '/excel.json'
|
979
|
+
last_response.status.should == 406
|
980
|
+
last_response.body.should == "The requested format is not supported."
|
981
|
+
end
|
982
|
+
end
|
983
|
+
|
984
|
+
describe ".formatter" do
|
985
|
+
context "multiple formatters" do
|
986
|
+
before :each do
|
987
|
+
subject.formatter :json, lambda { |object, env| "{\"custom_formatter\":\"#{object[:some]}\"}" }
|
988
|
+
subject.formatter :txt, lambda { |object, env| "custom_formatter: #{object[:some]}" }
|
989
|
+
subject.get :simple do
|
990
|
+
{:some => 'hash'}
|
991
|
+
end
|
992
|
+
end
|
993
|
+
it 'sets one formatter' do
|
994
|
+
get '/simple.json'
|
995
|
+
last_response.body.should eql '{"custom_formatter":"hash"}'
|
996
|
+
end
|
997
|
+
it 'sets another formatter' do
|
998
|
+
get '/simple.txt'
|
999
|
+
last_response.body.should eql 'custom_formatter: hash'
|
1000
|
+
end
|
1001
|
+
end
|
1002
|
+
context "custom formatter" do
|
1003
|
+
before :each do
|
1004
|
+
subject.content_type :json, 'application/json'
|
1005
|
+
subject.content_type :custom, 'application/custom'
|
1006
|
+
subject.formatter :custom, lambda { |object, env| "{\"custom_formatter\":\"#{object[:some]}\"}" }
|
1007
|
+
subject.get :simple do
|
1008
|
+
{:some => 'hash'}
|
1009
|
+
end
|
1010
|
+
end
|
1011
|
+
it 'uses json' do
|
1012
|
+
get '/simple.json'
|
1013
|
+
last_response.body.should eql '{"some":"hash"}'
|
1014
|
+
end
|
1015
|
+
it 'uses custom formatter' do
|
1016
|
+
get '/simple.custom', { 'HTTP_ACCEPT' => 'application/custom' }
|
1017
|
+
last_response.body.should eql '{"custom_formatter":"hash"}'
|
1018
|
+
end
|
1019
|
+
end
|
1020
|
+
context "custom formatter class" do
|
1021
|
+
module CustomFormatter
|
1022
|
+
def self.call(object, env)
|
1023
|
+
"{\"custom_formatter\":\"#{object[:some]}\"}"
|
1024
|
+
end
|
1025
|
+
end
|
1026
|
+
before :each do
|
1027
|
+
subject.content_type :json, 'application/json'
|
1028
|
+
subject.content_type :custom, 'application/custom'
|
1029
|
+
subject.formatter :custom, CustomFormatter
|
1030
|
+
subject.get :simple do
|
1031
|
+
{:some => 'hash'}
|
1032
|
+
end
|
1033
|
+
end
|
1034
|
+
it 'uses json' do
|
1035
|
+
get '/simple.json'
|
1036
|
+
last_response.body.should eql '{"some":"hash"}'
|
1037
|
+
end
|
1038
|
+
it 'uses custom formatter' do
|
1039
|
+
get '/simple.custom', { 'HTTP_ACCEPT' => 'application/custom' }
|
1040
|
+
last_response.body.should eql '{"custom_formatter":"hash"}'
|
1041
|
+
end
|
1042
|
+
end
|
900
1043
|
end
|
901
1044
|
|
902
1045
|
describe ".default_error_status" do
|
@@ -1337,5 +1480,36 @@ describe Grape::API do
|
|
1337
1480
|
last_response.body.should == { :meaning_of_life => 42 }.to_json
|
1338
1481
|
end
|
1339
1482
|
end
|
1483
|
+
context ":serializable_hash" do
|
1484
|
+
before(:each) do
|
1485
|
+
class SimpleExample
|
1486
|
+
def serializable_hash
|
1487
|
+
{:abc => 'def'}
|
1488
|
+
end
|
1489
|
+
end
|
1490
|
+
subject.format :serializable_hash
|
1491
|
+
end
|
1492
|
+
it "instance" do
|
1493
|
+
subject.get '/example' do
|
1494
|
+
SimpleExample.new
|
1495
|
+
end
|
1496
|
+
get '/example'
|
1497
|
+
last_response.body.should == '{"abc":"def"}'
|
1498
|
+
end
|
1499
|
+
it "root" do
|
1500
|
+
subject.get '/example' do
|
1501
|
+
{ "root" => SimpleExample.new }
|
1502
|
+
end
|
1503
|
+
get '/example'
|
1504
|
+
last_response.body.should == '{"root":{"abc":"def"}}'
|
1505
|
+
end
|
1506
|
+
it "array" do
|
1507
|
+
subject.get '/examples' do
|
1508
|
+
[ SimpleExample.new, SimpleExample.new ]
|
1509
|
+
end
|
1510
|
+
get '/examples'
|
1511
|
+
last_response.body.should == '[{"abc":"def"},{"abc":"def"}]'
|
1512
|
+
end
|
1513
|
+
end
|
1340
1514
|
end
|
1341
1515
|
end
|