grape 0.1.4 → 0.1.5

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/.gitignore CHANGED
@@ -19,5 +19,6 @@ coverage
19
19
  pkg
20
20
  .rvmrc
21
21
  .bundle
22
+ dist
22
23
 
23
24
  ## PROJECT::SPECIFIC
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- grape (0.1.4)
4
+ grape (0.1.5)
5
5
  multi_json
6
6
  multi_xml
7
7
  rack
@@ -11,41 +11,42 @@ PATH
11
11
  GEM
12
12
  remote: http://rubygems.org/
13
13
  specs:
14
+ ZenTest (4.5.0)
14
15
  diff-lcs (1.1.2)
15
- json_pure (1.4.3)
16
+ json_pure (1.5.2)
16
17
  maruku (0.6.0)
17
18
  syntax (>= 1.0.0)
18
- mg (0.0.8)
19
- rake
20
- multi_json (0.0.5)
19
+ multi_json (1.0.3)
21
20
  multi_xml (0.2.2)
22
- rack (1.2.1)
23
- rack-jsonp (1.1.0)
21
+ rack (1.3.0)
22
+ rack-jsonp (1.2.0)
24
23
  rack
25
- rack-mount (0.7.1)
24
+ rack-mount (0.8.1)
26
25
  rack (>= 1.0.0)
27
- rack-test (0.5.4)
26
+ rack-test (0.6.0)
28
27
  rack (>= 1.0)
29
- rake (0.8.7)
30
- rspec (2.5.0)
31
- rspec-core (~> 2.5.0)
32
- rspec-expectations (~> 2.5.0)
33
- rspec-mocks (~> 2.5.0)
34
- rspec-core (2.5.1)
35
- rspec-expectations (2.5.0)
28
+ rake (0.9.2)
29
+ rspec (2.6.0)
30
+ rspec-core (~> 2.6.0)
31
+ rspec-expectations (~> 2.6.0)
32
+ rspec-mocks (~> 2.6.0)
33
+ rspec-core (2.6.4)
34
+ rspec-expectations (2.6.0)
36
35
  diff-lcs (~> 1.1.2)
37
- rspec-mocks (2.5.0)
36
+ rspec-mocks (2.6.0)
38
37
  syntax (1.0.0)
39
- yard (0.6.1)
38
+ yard (0.7.1)
40
39
 
41
40
  PLATFORMS
42
41
  ruby
43
42
 
44
43
  DEPENDENCIES
44
+ ZenTest
45
+ bundler
45
46
  grape!
46
47
  json_pure
47
48
  maruku
48
- mg
49
49
  rack-test
50
- rspec (~> 2.5.0)
50
+ rake
51
+ rspec (~> 2.6.0)
51
52
  yard
@@ -1,4 +1,5 @@
1
1
  # Grape
2
+ [![Build Status](http://travis-ci.org/intridea/grape.png)](http://travis-ci.org/intridea/grape)
2
3
 
3
4
  Grape is a REST-like API micro-framework for Ruby. It is built to complement existing web application frameworks such as Rails and Sinatra by providing a simple DSL to easily provide APIs. It has built-in support for common conventions such as multiple formats, subdomain/prefix restriction, and versioning.
4
5
 
@@ -62,6 +63,26 @@ And would respond to the following routes:
62
63
 
63
64
  Serialization takes place automatically. For more detailed usage information, please visit the [Grape Wiki](http://github.com/intridea/grape/wiki).
64
65
 
66
+ ## Raising Errors
67
+
68
+ You can raise errors explicitly.
69
+
70
+ error!("Access Denied", 401)
71
+
72
+ You can also return JSON formatted objects explicitly by raising error! and passing a hash instead of a message.
73
+
74
+ error!({ "error" => "unexpected error", "detail" => "missing widget" }, 500)
75
+
76
+ ## Exception Handling
77
+
78
+ Grape can be told to rescue certain (or all) exceptions in your
79
+ application and instead display them in text or json form. To do this,
80
+ you simply use the `rescue_from` method inside your API declaration:
81
+
82
+ class Twitter::API < Grape::API
83
+ rescue_from ArgumentError, NotImplementedError # :all for all errors
84
+ end
85
+
65
86
  ## Note on Patches/Pull Requests
66
87
 
67
88
  * Fork the project.
data/Rakefile CHANGED
@@ -2,8 +2,7 @@ require 'rubygems'
2
2
  require 'bundler'
3
3
  Bundler.setup :default, :test, :development
4
4
 
5
- require 'mg'
6
- MG.new('grape.gemspec')
5
+ Bundler::GemHelper.install_tasks
7
6
 
8
7
  require 'rspec/core/rake_task'
9
8
  RSpec::Core::RakeTask.new(:spec) do |spec|
@@ -19,12 +19,14 @@ Gem::Specification.new do |s|
19
19
  s.add_runtime_dependency 'multi_json'
20
20
  s.add_runtime_dependency 'multi_xml'
21
21
 
22
- s.add_development_dependency 'mg'
22
+ s.add_development_dependency 'rake'
23
23
  s.add_development_dependency 'maruku'
24
24
  s.add_development_dependency 'yard'
25
25
  s.add_development_dependency 'rack-test'
26
- s.add_development_dependency 'rspec', '~> 2.5.0'
26
+ s.add_development_dependency 'rspec', '~> 2.6.0'
27
27
  s.add_development_dependency 'json_pure'
28
+ s.add_development_dependency 'ZenTest'
29
+ s.add_development_dependency 'bundler'
28
30
 
29
31
  s.files = `git ls-files`.split("\n")
30
32
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -17,6 +17,7 @@ module Grape
17
17
  module Auth
18
18
  autoload :OAuth2, 'grape/middleware/auth/oauth2'
19
19
  autoload :Basic, 'grape/middleware/auth/basic'
20
+ autoload :Digest, 'grape/middleware/auth/digest'
20
21
  end
21
22
  end
22
23
  end
@@ -1,5 +1,6 @@
1
1
  require 'rack/mount'
2
2
  require 'rack/auth/basic'
3
+ require 'rack/auth/digest/md5'
3
4
  require 'logger'
4
5
 
5
6
  module Grape
@@ -50,7 +51,7 @@ module Grape
50
51
  def prefix(prefix = nil)
51
52
  prefix ? set(:root_prefix, prefix) : settings[:root_prefix]
52
53
  end
53
-
54
+
54
55
  # Specify an API version.
55
56
  #
56
57
  # @example API with legacy support.
@@ -79,6 +80,39 @@ module Grape
79
80
  new_format ? set(:default_format, new_format.to_sym) : settings[:default_format]
80
81
  end
81
82
 
83
+ # Specify the format for error messages.
84
+ # May be `:json` or `:txt` (default).
85
+ def error_format(new_format = nil)
86
+ new_format ? set(:error_format, new_format.to_sym) : settings[:error_format]
87
+ end
88
+
89
+ # Specify the default status code for errors.
90
+ def default_error_status(new_status = nil)
91
+ new_status ? set(:default_error_status, new_status) : settings[:default_error_status]
92
+ end
93
+
94
+ # Allows you to rescue certain exceptions that occur to return
95
+ # a grape error rather than raising all the way to the
96
+ # server level.
97
+ #
98
+ # @example Rescue from custom exceptions
99
+ # class ExampleAPI < Grape::API
100
+ # class CustomError < StandardError; end
101
+ #
102
+ # rescue_from CustomError
103
+ # end
104
+ #
105
+ # @overload rescue_from(*exception_classes, options = {})
106
+ # @param [Array] exception_classes A list of classes that you want to rescue, or
107
+ # the symbol :all to rescue from all exceptions.
108
+ # @param [Hash] options Options for the rescue usage.
109
+ # @option options [Boolean] :backtrace Include a backtrace in the rescue response.
110
+ def rescue_from(*args)
111
+ set(:rescue_options, args.pop) if args.last.is_a?(Hash)
112
+ set(:rescue_all, true) and return if args.include?(:all)
113
+ set(:rescued_errors, args)
114
+ end
115
+
82
116
  # Add helper methods that will be accessible from any
83
117
  # endpoint within this namespace (and child namespaces).
84
118
  #
@@ -105,7 +139,7 @@ module Grape
105
139
  end
106
140
 
107
141
  # Add an authentication type to the API. Currently
108
- # only `:http_basic` is supported.
142
+ # only `:http_basic`, `:http_digest` and `:oauth2` are supported.
109
143
  def auth(type = nil, options = {}, &block)
110
144
  if type
111
145
  set(:auth, {:type => type.to_sym, :proc => block}.merge(options))
@@ -122,6 +156,12 @@ module Grape
122
156
  options[:realm] ||= "API Authorization"
123
157
  auth :http_basic, options, &block
124
158
  end
159
+
160
+ def http_digest(options = {}, &block)
161
+ options[:realm] ||= "API Authorization"
162
+ options[:opaque] ||= "secret"
163
+ auth :http_digest, options, &block
164
+ end
125
165
 
126
166
  # Defines a route that will be recognized
127
167
  # by the Grape API.
@@ -217,8 +257,14 @@ module Grape
217
257
 
218
258
  def build_endpoint(&block)
219
259
  b = Rack::Builder.new
220
- b.use Grape::Middleware::Error
260
+ b.use Grape::Middleware::Error,
261
+ :default_status => settings[:default_error_status] || 403,
262
+ :rescue_all => settings[:rescue_all],
263
+ :rescued_errors => settings[:rescued_errors],
264
+ :format => settings[:error_format] || :txt,
265
+ :rescue_options => settings[:rescue_options]
221
266
  b.use Rack::Auth::Basic, settings[:auth][:realm], &settings[:auth][:proc] if settings[:auth] && settings[:auth][:type] == :http_basic
267
+ b.use Rack::Auth::Digest::MD5, settings[:auth][:realm], settings[:auth][:opaque], &settings[:auth][:proc] if settings[:auth] && settings[:auth][:type] == :http_digest
222
268
  b.use Grape::Middleware::Prefixer, :prefix => prefix if prefix
223
269
  b.use Grape::Middleware::Versioner, :versions => (version if version.is_a?(Array)) if version
224
270
  b.use Grape::Middleware::Formatter, :default_format => default_format || :json
@@ -0,0 +1,30 @@
1
+ require 'rack/auth/digest/md5'
2
+
3
+ module Grape
4
+ module Middleware
5
+ module Auth
6
+ class Digest < Grape::Middleware::Base
7
+ attr_reader :authenticator
8
+
9
+ def initialize(app, options = {}, &authenticator)
10
+ super(app, options)
11
+ @authenticator = authenticator
12
+ end
13
+
14
+ def digest_request
15
+ Rack::Auth::Digest::Request.new(env)
16
+ end
17
+
18
+ def credentials
19
+ digest_request.provided?? digest_request.credentials : [nil, nil]
20
+ end
21
+
22
+ def before
23
+ unless authenticator.call(*credentials)
24
+ throw :error, :status => 401, :message => "API Authorization Failed."
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,18 +1,42 @@
1
1
  module Grape::Middleware::Auth
2
+ # OAuth 2.0 authorization for Grape APIs.
2
3
  class OAuth2 < Grape::Middleware::Base
3
4
  def default_options
4
5
  {
5
6
  :token_class => 'AccessToken',
6
- :realm => 'OAuth API'
7
+ :realm => 'OAuth API',
8
+ :parameter => %w(bearer_token oauth_token),
9
+ :accepted_headers => %w(HTTP_AUTHORIZATION X_HTTP_AUTHORIZATION X-HTTP_AUTHORIZATION REDIRECT_X_HTTP_AUTHORIZATION),
10
+ :header => [/Bearer (.*)/i, /OAuth (.*)/i]
7
11
  }
8
12
  end
9
13
 
10
14
  def before
11
- if request['oauth_token']
12
- verify_token(request['oauth_token'])
13
- elsif env['Authorization'] && t = parse_authorization_header
14
- verify_token(t)
15
+ verify_token(token_parameter || token_header)
16
+ end
17
+
18
+ def token_parameter
19
+ Array(options[:parameter]).each do |p|
20
+ return request[p] if request[p]
21
+ end
22
+ nil
23
+ end
24
+
25
+ def token_header
26
+ return false unless authorization_header
27
+ Array(options[:header]).each do |regexp|
28
+ if authorization_header =~ regexp
29
+ return $1
30
+ end
31
+ end
32
+ nil
33
+ end
34
+
35
+ def authorization_header
36
+ options[:accepted_headers].each do |head|
37
+ return env[head] if env[head]
15
38
  end
39
+ nil
16
40
  end
17
41
 
18
42
  def token_class
@@ -21,10 +45,10 @@ module Grape::Middleware::Auth
21
45
 
22
46
  def verify_token(token)
23
47
  if token = token_class.verify(token)
24
- if token.expired?
48
+ if token.respond_to?(:expired?) && token.expired?
25
49
  error_out(401, 'expired_token')
26
50
  else
27
- if token.permission_for?(env)
51
+ if !token.respond_to?(:permission_for?) || token.permission_for?(env)
28
52
  env['api.token'] = token
29
53
  else
30
54
  error_out(403, 'insufficient_scope')
@@ -35,15 +59,9 @@ module Grape::Middleware::Auth
35
59
  end
36
60
  end
37
61
 
38
- def parse_authorization_header
39
- if env['Authorization'] =~ /oauth (.*)/i
40
- $1
41
- end
42
- end
43
-
44
62
  def error_out(status, error)
45
63
  throw :error, {
46
- :message => 'The token provided has expired.',
64
+ :message => error,
47
65
  :status => status,
48
66
  :headers => {
49
67
  'WWW-Authenticate' => "OAuth realm='#{options[:realm]}', error='#{error}'"
@@ -52,4 +70,4 @@ module Grape::Middleware::Auth
52
70
  end
53
71
  end
54
72
  end
55
-
73
+
@@ -2,7 +2,7 @@ module Grape
2
2
  module Middleware
3
3
  class Base
4
4
  attr_reader :app, :env, :options
5
-
5
+
6
6
  # @param [Rack Application] app The standard argument for a Rack middleware.
7
7
  # @param [Hash] options A hash of options, simply stored for use by subclasses.
8
8
  def initialize(app, options = {})
@@ -38,6 +38,52 @@ module Grape
38
38
  def response
39
39
  Rack::Response.new(@app_response)
40
40
  end
41
+
42
+
43
+ module Formats
44
+
45
+ CONTENT_TYPES = {
46
+ :xml => 'application/xml',
47
+ :json => 'application/json',
48
+ :atom => 'application/atom+xml',
49
+ :rss => 'application/rss+xml',
50
+ :txt => 'text/plain'
51
+ }
52
+ FORMATTERS = {
53
+ :json => :encode_json,
54
+ :txt => :encode_txt,
55
+ }
56
+
57
+ def formatters
58
+ FORMATTERS.merge(options[:formatters] || {})
59
+ end
60
+
61
+ def content_types
62
+ CONTENT_TYPES.merge(options[:content_types] || {})
63
+ end
64
+
65
+ def content_type
66
+ content_types[options[:format]] || 'text/html'
67
+ end
68
+
69
+ def mime_types
70
+ content_types.invert
71
+ end
72
+
73
+ def formatter_for(api_format)
74
+ spec = formatters[api_format]
75
+ case spec
76
+ when nil
77
+ lambda { |obj| obj }
78
+ when Symbol
79
+ method(spec)
80
+ else
81
+ spec
82
+ end
83
+ end
84
+
85
+ end
86
+
41
87
  end
42
88
  end
43
- end
89
+ end
@@ -1,21 +1,70 @@
1
1
  require 'grape/middleware/base'
2
+ require 'multi_json'
2
3
 
3
4
  module Grape
4
5
  module Middleware
5
6
  class Error < Base
7
+ include Formats
8
+
9
+ def default_options
10
+ {
11
+ :default_status => 403, # default status returned on error
12
+ :default_message => "",
13
+ :format => :txt,
14
+ :formatters => {},
15
+
16
+ :rescue_all => false, # true to rescue all exceptions
17
+ :rescue_options => {:backtrace => false}, # true to display backtrace
18
+ :rescued_errors => []
19
+ }
20
+ end
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.encode(result)
28
+ end
29
+
30
+ def encode_txt(message, backtrace)
31
+ result = message.is_a?(Hash) ? MultiJson.encode(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
+
6
39
  def call!(env)
7
40
  @env = env
8
- result = catch :error do
9
- @app.call(@env)
41
+
42
+ begin
43
+ error_response(catch(:error){
44
+ return @app.call(@env)
45
+ })
46
+ rescue Exception => e
47
+ raise unless options[:rescue_all] || (options[:rescued_errors] || []).include?(e.class)
48
+ error_response({ :message => e.message, :backtrace => e.backtrace })
10
49
  end
11
50
 
12
- result ||= {}
13
- result.is_a?(Hash) ? error_response(result) : result
14
51
  end
15
52
 
16
53
  def error_response(error = {})
17
- Rack::Response.new([(error[:message] || options[:default_message])], error[:status] || 403, error[:headers] || {}).finish
54
+ status = error[:status] || options[:default_status]
55
+ message = error[:message] || options[:default_message]
56
+ headers = {'Content-Type' => content_type}
57
+ headers.merge!(error[:headers]) if error[:headers].is_a?(Hash)
58
+ backtrace = error[:backtrace] || []
59
+ Rack::Response.new([format_message(message, backtrace, status)], status, headers).finish
18
60
  end
61
+
62
+ def format_message(message, backtrace, status)
63
+ formatter = formatter_for(options[:format])
64
+ throw :error, :status => 406, :message => "The requested format #{options[:format]} is not supported." unless formatter
65
+ formatter.call(message, backtrace)
66
+ end
67
+
19
68
  end
20
69
  end
21
70
  end
@@ -4,18 +4,8 @@ require 'multi_json'
4
4
  module Grape
5
5
  module Middleware
6
6
  class Formatter < Base
7
- CONTENT_TYPES = {
8
- :xml => 'application/xml',
9
- :json => 'application/json',
10
- :atom => 'application/atom+xml',
11
- :rss => 'application/rss+xml',
12
- :txt => 'text/plain'
13
- }
14
- FORMATTERS = {
15
- :json => :encode_json,
16
- :txt => :encode_txt,
17
- }
18
-
7
+ include Formats
8
+
19
9
  def default_options
20
10
  {
21
11
  :default_format => :txt,
@@ -24,18 +14,6 @@ module Grape
24
14
  }
25
15
  end
26
16
 
27
- def content_types
28
- CONTENT_TYPES.merge(options[:content_types])
29
- end
30
-
31
- def formatters
32
- FORMATTERS.merge(options[:formatters])
33
- end
34
-
35
- def mime_types
36
- content_types.invert
37
- end
38
-
39
17
  def headers
40
18
  env.dup.inject({}){|h,(k,v)| h[k.downcase] = v; h}
41
19
  end
@@ -92,18 +70,6 @@ module Grape
92
70
  Rack::Response.new(bodymap, status, headers).to_a
93
71
  end
94
72
 
95
- def formatter_for(api_format)
96
- spec = formatters[api_format]
97
- case spec
98
- when nil
99
- lambda { |obj| obj }
100
- when Symbol
101
- method(spec)
102
- else
103
- spec
104
- end
105
- end
106
-
107
73
  def encode_json(object)
108
74
  if object.respond_to? :serializable_hash
109
75
  MultiJson.encode(object.serializable_hash)
@@ -1,3 +1,3 @@
1
1
  module Grape
2
- VERSION = '0.1.4'
2
+ VERSION = '0.1.5'
3
3
  end
@@ -20,7 +20,7 @@ describe Grape::API do
20
20
  last_response.status.should eql 404
21
21
  end
22
22
  end
23
-
23
+
24
24
  describe '.version' do
25
25
  it 'should set the API version' do
26
26
  subject.version 'v1'
@@ -276,6 +276,35 @@ describe Grape::API do
276
276
  last_response.body.should eql 'Created'
277
277
  end
278
278
  end
279
+
280
+ context 'format' do
281
+ before do
282
+ subject.get("/foo") { "bar" }
283
+ end
284
+
285
+ it 'should set content type for txt format' do
286
+ get '/foo'
287
+ last_response.headers['Content-Type'].should eql 'text/plain'
288
+ end
289
+
290
+ it 'should set content type for json' do
291
+ get '/foo.json'
292
+ last_response.headers['Content-Type'].should eql 'application/json'
293
+ end
294
+
295
+ it 'should set content type for error' do
296
+ subject.get('/error') { error!('error in plain text', 500) }
297
+ get '/error'
298
+ last_response.headers['Content-Type'].should eql 'text/plain'
299
+ end
300
+
301
+ it 'should set content type for error' do
302
+ subject.error_format :json
303
+ subject.get('/error') { error!('error in json', 500) }
304
+ get '/error.json'
305
+ last_response.headers['Content-Type'].should eql 'application/json'
306
+ end
307
+ end
279
308
 
280
309
  context 'custom middleware' do
281
310
  class PhonyMiddleware
@@ -471,4 +500,112 @@ describe Grape::API do
471
500
  last_response.status.should eql 200
472
501
  end
473
502
  end
503
+
504
+ describe ".rescue_from" do
505
+ it 'should not rescue errors when rescue_from is not set' do
506
+ subject.get '/exception' do
507
+ raise "rain!"
508
+ end
509
+ lambda { get '/exception' }.should raise_error
510
+ end
511
+
512
+ it 'should rescue all errors if rescue_from :all is called' do
513
+ subject.rescue_from :all
514
+ subject.get '/exception' do
515
+ raise "rain!"
516
+ end
517
+ get '/exception'
518
+ last_response.status.should eql 403
519
+ end
520
+
521
+ it 'should rescue only certain errors if rescue_from is called with specific errors' do
522
+ subject.rescue_from ArgumentError
523
+ subject.get('/rescued'){ raise ArgumentError }
524
+ subject.get('/unrescued'){ raise "beefcake" }
525
+
526
+ get '/rescued'
527
+ last_response.status.should eql 403
528
+
529
+ lambda{ get '/unrescued' }.should raise_error
530
+ end
531
+ end
532
+
533
+ describe ".error_format" do
534
+ it 'should rescue all errors and return :txt' do
535
+ subject.rescue_from :all
536
+ subject.error_format :txt
537
+ subject.get '/exception' do
538
+ raise "rain!"
539
+ end
540
+ get '/exception'
541
+ last_response.body.should eql "rain!"
542
+ end
543
+
544
+ it 'should rescue all errros and return :txt with backtrace' do
545
+ subject.rescue_from :all, :backtrace => true
546
+ subject.error_format :txt
547
+ subject.get '/exception' do
548
+ raise "rain!"
549
+ end
550
+ get '/exception'
551
+ last_response.body.start_with?("rain!\r\n").should be_true
552
+ end
553
+
554
+ it 'should rescue all errors and return :json' do
555
+ subject.rescue_from :all
556
+ subject.error_format :json
557
+ subject.get '/exception' do
558
+ raise "rain!"
559
+ end
560
+ get '/exception'
561
+ last_response.body.should eql '{"error":"rain!"}'
562
+ end
563
+ it 'should rescue all errors and return :json with backtrace' do
564
+ subject.rescue_from :all, :backtrace => true
565
+ subject.error_format :json
566
+ subject.get '/exception' do
567
+ raise "rain!"
568
+ end
569
+ get '/exception'
570
+ json = JSON.parse(last_response.body)
571
+ json["error"].should eql 'rain!'
572
+ json["backtrace"].length.should > 0
573
+ end
574
+ it 'should rescue error! and return txt' do
575
+ subject.error_format :txt
576
+ subject.get '/error' do
577
+ error!("Access Denied", 401)
578
+ end
579
+ get '/error'
580
+ last_response.body.should eql "Access Denied"
581
+ end
582
+ it 'should rescue error! and return json' do
583
+ subject.error_format :json
584
+ subject.get '/error' do
585
+ error!("Access Denied", 401)
586
+ end
587
+ get '/error'
588
+ last_response.body.should eql '{"error":"Access Denied"}'
589
+ end
590
+ end
591
+
592
+ describe ".default_error_status" do
593
+ it 'should allow setting default_error_status' do
594
+ subject.rescue_from :all
595
+ subject.default_error_status 200
596
+ subject.get '/exception' do
597
+ raise "rain!"
598
+ end
599
+ get '/exception'
600
+ last_response.status.should eql 200
601
+ end
602
+ it 'should have a default error status' do
603
+ subject.rescue_from :all
604
+ subject.get '/exception' do
605
+ raise "rain!"
606
+ end
607
+ get '/exception'
608
+ last_response.status.should eql 403
609
+ end
610
+ end
474
611
  end
@@ -73,8 +73,6 @@ describe Grape::Endpoint do
73
73
  end
74
74
 
75
75
  it 'should accept an object and render it in format' do
76
- pending
77
-
78
76
  subject.get '/hey' do
79
77
  error!({'dude' => 'rad'}, 403)
80
78
  end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec::Matchers.define :be_challenge do
4
+ match do |actual_response|
5
+ actual_response.status == 401 &&
6
+ actual_response['WWW-Authenticate'] =~ /^Digest / &&
7
+ actual_response.body.empty?
8
+ end
9
+ end
10
+
11
+ class Test < Grape::API
12
+ version '1'
13
+
14
+ http_digest({:realm => 'Test Api', :opaque => 'secret'}) do |username|
15
+ {'foo' => 'bar'}[username]
16
+ end
17
+
18
+ get '/test' do
19
+ [{:hey => 'you'},{:there => 'bar'},{:foo => 'baz'}]
20
+ end
21
+ end
22
+
23
+ describe Grape::Middleware::Auth::Digest do
24
+ def app
25
+ Test
26
+ end
27
+
28
+ it 'should be a digest authentication challenge' do
29
+ get '/1/test'
30
+ last_response.should be_challenge
31
+ end
32
+
33
+ it 'should throw a 401 if no auth is given' do
34
+ get '/1/test'
35
+ last_response.status.should == 401
36
+ end
37
+
38
+ it 'should authenticate if given valid creds' do
39
+ digest_authorize "foo", "bar"
40
+ get '/1/test'
41
+ last_response.status.should == 200
42
+ end
43
+
44
+ it 'should throw a 401 if given invalid creds' do
45
+ digest_authorize "bar", "foo"
46
+ get '/1/test'
47
+ last_response.status.should == 401
48
+ end
49
+ end
@@ -65,9 +65,11 @@ describe Grape::Middleware::Auth::OAuth2 do
65
65
  it { @err[:headers]['WWW-Authenticate'].should == "OAuth realm='OAuth API', error='expired_token'" }
66
66
  end
67
67
 
68
- context 'with the token in the header' do
69
- before { get '/awesome', {}, 'Authorization' => 'OAuth g123' }
70
- it { last_response.body.should == 'g123' }
68
+ %w(HTTP_AUTHORIZATION X_HTTP_AUTHORIZATION X-HTTP_AUTHORIZATION REDIRECT_X_HTTP_AUTHORIZATION).each do |head|
69
+ context "with the token in the #{head} header" do
70
+ before { get '/awesome', {}, head => 'OAuth g123' }
71
+ it { last_response.body.should == 'g123' }
72
+ end
71
73
  end
72
74
 
73
75
  context 'with the token in the POST body' do
@@ -85,4 +87,4 @@ describe Grape::Middleware::Auth::OAuth2 do
85
87
  it { @err[:headers]['WWW-Authenticate'].should == "OAuth realm='OAuth API', error='insufficient_scope'" }
86
88
  it { @err[:status].should == 403 }
87
89
  end
88
- end
90
+ end
@@ -46,4 +46,4 @@ describe Grape::Middleware::Error do
46
46
  context 'with formatting' do
47
47
 
48
48
  end
49
- end
49
+ end
@@ -0,0 +1,120 @@
1
+ require 'spec_helper'
2
+
3
+ describe Grape::Middleware::Error do
4
+
5
+ # raises a text exception
6
+ class ExceptionApp
7
+ class << self
8
+ def call(env)
9
+ raise "rain!"
10
+ end
11
+ end
12
+ end
13
+
14
+ # raises a hash error
15
+ class ErrorHashApp
16
+ class << self
17
+ def error!(message, status=403)
18
+ throw :error, :message => { :error => message, :detail => "missing widget" }, :status => status
19
+ end
20
+ def call(env)
21
+ error!("rain!", 401)
22
+ end
23
+ end
24
+ end
25
+
26
+ # raises an error!
27
+ class AccessDeniedApp
28
+ class << self
29
+ def error!(message, status=403)
30
+ throw :error, :message => message, :status => status
31
+ end
32
+ def call(env)
33
+ error!("Access Denied", 401)
34
+ end
35
+ end
36
+ end
37
+
38
+ def app
39
+ @app
40
+ end
41
+
42
+ it 'should not trap errors by default' do
43
+ @app ||= Rack::Builder.app do
44
+ use Grape::Middleware::Error
45
+ run ExceptionApp
46
+ end
47
+ lambda { get '/' }.should raise_error
48
+ end
49
+
50
+ context 'with rescue_all set to true' do
51
+ it 'should set the message appropriately' do
52
+ @app ||= Rack::Builder.app do
53
+ use Grape::Middleware::Error, :rescue_all => true
54
+ run ExceptionApp
55
+ end
56
+ get '/'
57
+ last_response.body.should == "rain!"
58
+ end
59
+
60
+ it 'should default to a 403 status' do
61
+ @app ||= Rack::Builder.app do
62
+ use Grape::Middleware::Error, :rescue_all => true
63
+ run ExceptionApp
64
+ end
65
+ get '/'
66
+ last_response.status.should == 403
67
+ end
68
+
69
+ it 'should be possible to specify a different default status code' do
70
+ @app ||= Rack::Builder.app do
71
+ use Grape::Middleware::Error, :rescue_all => true, :default_status => 500
72
+ run ExceptionApp
73
+ end
74
+ get '/'
75
+ last_response.status.should == 500
76
+ end
77
+
78
+ it 'should be possible to return errors in json format' do
79
+ @app ||= Rack::Builder.app do
80
+ use Grape::Middleware::Error, :rescue_all => true, :format => :json
81
+ run ExceptionApp
82
+ end
83
+ get '/'
84
+ last_response.body.should == '{"error":"rain!"}'
85
+ end
86
+
87
+ it 'should be possible to return hash errors in json format' do
88
+ @app ||= Rack::Builder.app do
89
+ use Grape::Middleware::Error, :rescue_all => true, :format => :json
90
+ run ErrorHashApp
91
+ end
92
+ get '/'
93
+ ['{"error":"rain!","detail":"missing widget"}',
94
+ '{"detail":"missing widget","error":"rain!"}'].should be_include(last_response.body)
95
+ end
96
+
97
+ it 'should be possible to specify a custom formatter' do
98
+ @app ||= Rack::Builder.app do
99
+ use Grape::Middleware::Error,
100
+ :rescue_all => true,
101
+ :format => :custom,
102
+ :formatters => {
103
+ :custom => lambda { |message, backtrace| { :custom_formatter => message }.inspect }
104
+ }
105
+ run ExceptionApp
106
+ end
107
+ get '/'
108
+ last_response.body.should == '{:custom_formatter=>"rain!"}'
109
+ end
110
+
111
+ it 'should not trap regular error! codes' do
112
+ @app ||= Rack::Builder.app do
113
+ use Grape::Middleware::Error
114
+ run AccessDeniedApp
115
+ end
116
+ get '/'
117
+ last_response.status.should == 401
118
+ end
119
+ end
120
+ end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: grape
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.1.4
5
+ version: 0.1.5
6
6
  platform: ruby
7
7
  authors:
8
8
  - Michael Bleigh
@@ -10,11 +10,12 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-04-08 00:00:00 -05:00
13
+ date: 2011-07-14 00:00:00 -05:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: rack
18
+ prerelease: false
18
19
  requirement: &id001 !ruby/object:Gem::Requirement
19
20
  none: false
20
21
  requirements:
@@ -22,10 +23,10 @@ dependencies:
22
23
  - !ruby/object:Gem::Version
23
24
  version: "0"
24
25
  type: :runtime
25
- prerelease: false
26
26
  version_requirements: *id001
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rack-mount
29
+ prerelease: false
29
30
  requirement: &id002 !ruby/object:Gem::Requirement
30
31
  none: false
31
32
  requirements:
@@ -33,10 +34,10 @@ dependencies:
33
34
  - !ruby/object:Gem::Version
34
35
  version: "0"
35
36
  type: :runtime
36
- prerelease: false
37
37
  version_requirements: *id002
38
38
  - !ruby/object:Gem::Dependency
39
39
  name: rack-jsonp
40
+ prerelease: false
40
41
  requirement: &id003 !ruby/object:Gem::Requirement
41
42
  none: false
42
43
  requirements:
@@ -44,10 +45,10 @@ dependencies:
44
45
  - !ruby/object:Gem::Version
45
46
  version: "0"
46
47
  type: :runtime
47
- prerelease: false
48
48
  version_requirements: *id003
49
49
  - !ruby/object:Gem::Dependency
50
50
  name: multi_json
51
+ prerelease: false
51
52
  requirement: &id004 !ruby/object:Gem::Requirement
52
53
  none: false
53
54
  requirements:
@@ -55,10 +56,10 @@ dependencies:
55
56
  - !ruby/object:Gem::Version
56
57
  version: "0"
57
58
  type: :runtime
58
- prerelease: false
59
59
  version_requirements: *id004
60
60
  - !ruby/object:Gem::Dependency
61
61
  name: multi_xml
62
+ prerelease: false
62
63
  requirement: &id005 !ruby/object:Gem::Requirement
63
64
  none: false
64
65
  requirements:
@@ -66,10 +67,10 @@ dependencies:
66
67
  - !ruby/object:Gem::Version
67
68
  version: "0"
68
69
  type: :runtime
69
- prerelease: false
70
70
  version_requirements: *id005
71
71
  - !ruby/object:Gem::Dependency
72
- name: mg
72
+ name: rake
73
+ prerelease: false
73
74
  requirement: &id006 !ruby/object:Gem::Requirement
74
75
  none: false
75
76
  requirements:
@@ -77,10 +78,10 @@ dependencies:
77
78
  - !ruby/object:Gem::Version
78
79
  version: "0"
79
80
  type: :development
80
- prerelease: false
81
81
  version_requirements: *id006
82
82
  - !ruby/object:Gem::Dependency
83
83
  name: maruku
84
+ prerelease: false
84
85
  requirement: &id007 !ruby/object:Gem::Requirement
85
86
  none: false
86
87
  requirements:
@@ -88,10 +89,10 @@ dependencies:
88
89
  - !ruby/object:Gem::Version
89
90
  version: "0"
90
91
  type: :development
91
- prerelease: false
92
92
  version_requirements: *id007
93
93
  - !ruby/object:Gem::Dependency
94
94
  name: yard
95
+ prerelease: false
95
96
  requirement: &id008 !ruby/object:Gem::Requirement
96
97
  none: false
97
98
  requirements:
@@ -99,10 +100,10 @@ dependencies:
99
100
  - !ruby/object:Gem::Version
100
101
  version: "0"
101
102
  type: :development
102
- prerelease: false
103
103
  version_requirements: *id008
104
104
  - !ruby/object:Gem::Dependency
105
105
  name: rack-test
106
+ prerelease: false
106
107
  requirement: &id009 !ruby/object:Gem::Requirement
107
108
  none: false
108
109
  requirements:
@@ -110,21 +111,21 @@ dependencies:
110
111
  - !ruby/object:Gem::Version
111
112
  version: "0"
112
113
  type: :development
113
- prerelease: false
114
114
  version_requirements: *id009
115
115
  - !ruby/object:Gem::Dependency
116
116
  name: rspec
117
+ prerelease: false
117
118
  requirement: &id010 !ruby/object:Gem::Requirement
118
119
  none: false
119
120
  requirements:
120
121
  - - ~>
121
122
  - !ruby/object:Gem::Version
122
- version: 2.5.0
123
+ version: 2.6.0
123
124
  type: :development
124
- prerelease: false
125
125
  version_requirements: *id010
126
126
  - !ruby/object:Gem::Dependency
127
127
  name: json_pure
128
+ prerelease: false
128
129
  requirement: &id011 !ruby/object:Gem::Requirement
129
130
  none: false
130
131
  requirements:
@@ -132,8 +133,29 @@ dependencies:
132
133
  - !ruby/object:Gem::Version
133
134
  version: "0"
134
135
  type: :development
135
- prerelease: false
136
136
  version_requirements: *id011
137
+ - !ruby/object:Gem::Dependency
138
+ name: ZenTest
139
+ prerelease: false
140
+ requirement: &id012 !ruby/object:Gem::Requirement
141
+ none: false
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: "0"
146
+ type: :development
147
+ version_requirements: *id012
148
+ - !ruby/object:Gem::Dependency
149
+ name: bundler
150
+ prerelease: false
151
+ requirement: &id013 !ruby/object:Gem::Requirement
152
+ none: false
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ version: "0"
157
+ type: :development
158
+ version_requirements: *id013
137
159
  description: A Ruby framework for rapid API development with great conventions.
138
160
  email:
139
161
  - michael@intridea.com
@@ -259,6 +281,7 @@ files:
259
281
  - lib/grape/api.rb
260
282
  - lib/grape/endpoint.rb
261
283
  - lib/grape/middleware/auth/basic.rb
284
+ - lib/grape/middleware/auth/digest.rb
262
285
  - lib/grape/middleware/auth/oauth2.rb
263
286
  - lib/grape/middleware/base.rb
264
287
  - lib/grape/middleware/error.rb
@@ -269,9 +292,11 @@ files:
269
292
  - spec/grape/api_spec.rb
270
293
  - spec/grape/endpoint_spec.rb
271
294
  - spec/grape/middleware/auth/basic_spec.rb
295
+ - spec/grape/middleware/auth/digest_spec.rb
272
296
  - spec/grape/middleware/auth/oauth2_spec.rb
273
297
  - spec/grape/middleware/base_spec.rb
274
298
  - spec/grape/middleware/error_spec.rb
299
+ - spec/grape/middleware/exception_spec.rb
275
300
  - spec/grape/middleware/formatter_spec.rb
276
301
  - spec/grape/middleware/prefixer_spec.rb
277
302
  - spec/grape/middleware/versioner_spec.rb
@@ -291,18 +316,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
291
316
  requirements:
292
317
  - - ">="
293
318
  - !ruby/object:Gem::Version
294
- hash: -2709592695038958113
295
- segments:
296
- - 0
297
319
  version: "0"
298
320
  required_rubygems_version: !ruby/object:Gem::Requirement
299
321
  none: false
300
322
  requirements:
301
323
  - - ">="
302
324
  - !ruby/object:Gem::Version
303
- hash: -2709592695038958113
304
- segments:
305
- - 0
306
325
  version: "0"
307
326
  requirements: []
308
327
 
@@ -315,9 +334,11 @@ test_files:
315
334
  - spec/grape/api_spec.rb
316
335
  - spec/grape/endpoint_spec.rb
317
336
  - spec/grape/middleware/auth/basic_spec.rb
337
+ - spec/grape/middleware/auth/digest_spec.rb
318
338
  - spec/grape/middleware/auth/oauth2_spec.rb
319
339
  - spec/grape/middleware/base_spec.rb
320
340
  - spec/grape/middleware/error_spec.rb
341
+ - spec/grape/middleware/exception_spec.rb
321
342
  - spec/grape/middleware/formatter_spec.rb
322
343
  - spec/grape/middleware/prefixer_spec.rb
323
344
  - spec/grape/middleware/versioner_spec.rb