goliath 1.0.4 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of goliath might be problematic. Click here for more details.

Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -4
  3. data/goliath.gemspec +10 -7
  4. data/lib/goliath/api.rb +1 -1
  5. data/lib/goliath/rack/jsonp.rb +7 -2
  6. data/lib/goliath/rack/params.rb +3 -2
  7. data/lib/goliath/rack/validator.rb +1 -1
  8. data/lib/goliath/request.rb +2 -2
  9. data/lib/goliath/runner.rb +4 -3
  10. data/lib/goliath/server.rb +17 -2
  11. data/lib/goliath/test_helper_sse.rb +76 -0
  12. data/lib/goliath/validation/error.rb +4 -1
  13. data/lib/goliath/version.rb +1 -1
  14. data/spec/integration/early_abort_spec.rb +3 -3
  15. data/spec/integration/event_stream_spec.rb +50 -0
  16. data/spec/integration/exception_handling_spec.rb +202 -0
  17. data/spec/integration/jsonp_spec.rb +61 -10
  18. data/spec/integration/test_helper_spec.rb +1 -1
  19. data/spec/integration/valid_spec.rb +21 -1
  20. data/spec/unit/api_spec.rb +1 -1
  21. data/spec/unit/env_spec.rb +5 -5
  22. data/spec/unit/headers_spec.rb +2 -2
  23. data/spec/unit/rack/formatters/json_spec.rb +2 -2
  24. data/spec/unit/rack/formatters/xml_spec.rb +2 -2
  25. data/spec/unit/rack/formatters/yaml_spec.rb +2 -2
  26. data/spec/unit/rack/params_spec.rb +41 -6
  27. data/spec/unit/rack/validation/default_params_spec.rb +2 -2
  28. data/spec/unit/rack/validation/numeric_range_spec.rb +2 -2
  29. data/spec/unit/rack/validation/param_spec.rb +18 -18
  30. data/spec/unit/rack/validation/required_param_spec.rb +25 -25
  31. data/spec/unit/rack/validation/required_value_spec.rb +10 -10
  32. data/spec/unit/request_spec.rb +3 -3
  33. data/spec/unit/runner_spec.rb +1 -1
  34. data/spec/unit/server_spec.rb +4 -4
  35. metadata +135 -94
  36. data/examples/around.rb +0 -38
  37. data/examples/clone.rb +0 -26
  38. data/examples/router.rb +0 -15
  39. data/examples/test.rb +0 -31
  40. data/examples/upload.rb +0 -17
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c49ef49ca7b81daf949084e54013fe33751d9461
4
- data.tar.gz: c9bd1ae49086f0fa8ff01dd8617994156ff1b448
3
+ metadata.gz: 2657a958a80151a0eab64c006eb13a4905156cc3
4
+ data.tar.gz: b140e7bdc85173bb06adeef69b504d7ab39d5197
5
5
  SHA512:
6
- metadata.gz: 4d9e9698db573be7495f89394bae42ad613397489a5e979994b29ee1518238c73884444f8be2bdef7b7bea936a1bced59b07e7d85eb5cec5dc1e2fb75758de8a
7
- data.tar.gz: 4a800250911ccb181214f88c43442539b18cb96676b960ffa8e7a3acddc1aa3413d83c58bab1b389280d09f4822ac97f815be9db80cd8dde52483a006e991062
6
+ metadata.gz: bdf5b587bcc2edefd5242b4c447e45787bd9affff51010c47484b9644ba005bc194d68b939c7117f78d375e3ebc16e05abc1292ff3b34626d9329fd140551957
7
+ data.tar.gz: 1cec64325f506e5838ec7893055c5e15cd52375c0e435b8cab69057d17424aa535f02537eab764a551064a161baeab27245d3a00a451ca0ca8215e720c3d0f1e
data/README.md CHANGED
@@ -64,13 +64,14 @@ Goliath has been used in production environments for 2+ years, across many diffe
64
64
  * [Middleware](https://github.com/postrank-labs/goliath/wiki/Middleware)
65
65
  * [Configuration](https://github.com/postrank-labs/goliath/wiki/Configuration)
66
66
  * [Plugins](https://github.com/postrank-labs/goliath/wiki/Plugins)
67
+ * [Zero Downtime Restart](https://github.com/postrank-labs/goliath/wiki/Zero-downtime-restart)
68
+
67
69
 
68
70
  ### Hands-on applications:
69
71
 
70
- If you are you new to EventMachine, or want a detailed walk-through of building a Goliath powered API? You're in luck, we have two super-awesome Peepcode screencasts which will teach you all you need to know:
72
+ If you are you new to EventMachine, or want a detailed walk-through of building a Goliath powered API? You're in luck, a super-awesome Pluralsight screencast which will teach you all you need to know:
71
73
 
72
- * [Meet EventMachine: Part 1](http://peepcode.com/products/eventmachine) - introduction to EM, Fibers, etc.
73
- * [Meet EventMachine: Part 2](http://peepcode.com/products/eventmachine-ii) - building an API with Goliath
74
+ * [Meet EventMachine](http://www.pluralsight.com/courses/meet-eventmachine) - introduction to EM, Fibers, building an API with Goliath
74
75
 
75
76
  Additionally, you can also watch this presentation from GoGaRuCo 2011, which describes the design and motivation behind Goliath:
76
77
 
@@ -90,4 +91,4 @@ Other resources:
90
91
 
91
92
  ## License & Acknowledgments
92
93
 
93
- Goliath is distributed under the MIT license, for full details please see the LICENSE file.
94
+ Goliath is distributed under the MIT license, for full details please see the LICENSE file.
@@ -12,13 +12,14 @@ Gem::Specification.new do |s|
12
12
  s.summary = 'Async framework for writing API servers'
13
13
  s.description = s.summary
14
14
 
15
- s.required_ruby_version = '>=1.9.2'
15
+ s.required_ruby_version = '>=2.1.0'
16
16
 
17
17
  s.add_dependency 'eventmachine', '>= 1.0.0.beta.4'
18
18
  s.add_dependency 'em-synchrony', '>= 1.0.0'
19
- s.add_dependency 'em-websocket', "0.3.8"
20
- s.add_dependency 'http_parser.rb', '0.6.0'
19
+ s.add_dependency 'em-websocket', '0.3.8'
20
+ s.add_dependency 'http_parser.rb', '>= 0.6.0'
21
21
  s.add_dependency 'log4r'
22
+ s.add_dependency 'einhorn'
22
23
 
23
24
  s.add_dependency 'rack', '>=1.2.2'
24
25
  s.add_dependency 'rack-contrib'
@@ -27,7 +28,8 @@ Gem::Specification.new do |s|
27
28
  s.add_dependency 'multi_json'
28
29
 
29
30
  s.add_development_dependency 'rake', '>=0.8.7'
30
- s.add_development_dependency 'rspec', '>2.0'
31
+ s.add_development_dependency 'rspec', '~> 3.0'
32
+ s.add_development_dependency 'test-unit'
31
33
  s.add_development_dependency 'nokogiri'
32
34
  s.add_development_dependency 'em-http-request', '>=1.0.0'
33
35
  s.add_development_dependency 'em-mongo', '~> 0.4.0'
@@ -35,14 +37,15 @@ Gem::Specification.new do |s|
35
37
  s.add_development_dependency 'multipart_body'
36
38
  s.add_development_dependency 'amqp', '>=0.7.1'
37
39
  s.add_development_dependency 'em-websocket-client'
40
+ s.add_development_dependency 'em-eventsource'
38
41
 
39
42
  s.add_development_dependency 'tilt', '>=1.2.2'
40
43
  s.add_development_dependency 'haml', '>=3.0.25'
41
44
  s.add_development_dependency 'yard'
42
45
 
43
- s.add_development_dependency 'guard', '~> 1.8.3'
44
- s.add_development_dependency 'guard-rspec', '~> 3.1.0'
45
- s.add_development_dependency 'listen', '~> 1.3.1'
46
+ s.add_development_dependency 'guard', '~> 2.0'
47
+ s.add_development_dependency 'guard-rspec', '~> 4.0'
48
+ s.add_development_dependency 'listen', '~> 2.0'
46
49
 
47
50
  if RUBY_PLATFORM != 'java'
48
51
  s.add_development_dependency 'yajl-ruby'
@@ -172,7 +172,7 @@ module Goliath
172
172
 
173
173
  rescue Goliath::Validation::Error => e
174
174
  env[RACK_EXCEPTION] = e
175
- env[ASYNC_CALLBACK].call(validation_error(e.status_code, e.message))
175
+ env[ASYNC_CALLBACK].call(validation_error(e.status_code, e.message, e.headers))
176
176
 
177
177
  rescue Exception => e
178
178
  logthis = "#{e.backtrace[0]}: #{e.message} (#{e.class})\n"
@@ -18,10 +18,15 @@ module Goliath
18
18
  response = body
19
19
  end
20
20
 
21
+ response = "#{env.params['callback']}(#{response})"
22
+
21
23
  headers[Goliath::Constants::CONTENT_TYPE] = 'application/javascript'
22
- [status, headers, ["#{env.params['callback']}(#{response})"]]
24
+ if headers['Content-Length']
25
+ headers['Content-Length'] = response.bytesize.to_s
26
+ end
27
+
28
+ [status, headers, [response]]
23
29
  end
24
30
  end
25
31
  end
26
32
  end
27
-
@@ -60,10 +60,11 @@ module Goliath
60
60
  end
61
61
 
62
62
  def call(env)
63
- Goliath::Rack::Validator.safely(env) do
63
+ error_response = Goliath::Rack::Validator.safely(env) do
64
64
  env['params'] = retrieve_params(env)
65
- @app.call(env)
65
+ nil
66
66
  end
67
+ error_response || @app.call(env)
67
68
  end
68
69
  end
69
70
  end
@@ -39,7 +39,7 @@ module Goliath
39
39
  begin
40
40
  yield
41
41
  rescue Goliath::Validation::Error => e
42
- validation_error(e.status_code, e.message, headers)
42
+ validation_error(e.status_code, e.message, e.headers.merge(headers))
43
43
  rescue Exception => e
44
44
  env.logger.error(e.message)
45
45
  env.logger.error(e.backtrace.join("\n"))
@@ -92,7 +92,7 @@ module Goliath
92
92
 
93
93
  @env[REQUEST_METHOD] = parser.http_method
94
94
  @env[REQUEST_URI] = parser.request_url
95
- @env[QUERY_STRING] = uri.query
95
+ @env[QUERY_STRING] = uri.query || ''.freeze
96
96
  @env[HTTP_VERSION] = parser.http_version.join('.')
97
97
  @env[SCRIPT_NAME] = ""
98
98
  @env[REQUEST_PATH] = uri.path
@@ -226,7 +226,7 @@ module Goliath
226
226
  # @return [Nil]
227
227
  def server_exception(e)
228
228
  if e.is_a?(Goliath::Validation::Error)
229
- status, headers, body = [e.status_code, {}, ('{"error":"%s"}' % e.message)]
229
+ status, headers, body = [e.status_code, e.headers, ('{"error":"%s"}' % e.message)]
230
230
  else
231
231
  @env[RACK_LOGGER].error("#{e.message}\n#{e.backtrace.join("\n")}")
232
232
  message = Goliath.env?(:production) ? 'An error happened' : e.message
@@ -150,6 +150,7 @@ module Goliath
150
150
  opts.on('-a', '--address HOST', "Bind to HOST address (default: #{@options[:address]})") { |addr| @options[:address] = addr }
151
151
  opts.on('-p', '--port PORT', "Use PORT (default: #{@options[:port]})") { |port| @options[:port] = port.to_i }
152
152
  opts.on('-S', '--socket FILE', "Bind to unix domain socket") { |v| @options[:address] = v; @options[:port] = nil }
153
+ opts.on('-E', '--einhorn', "Use Einhorn socket manager") { |v| @options[:einhorn] = true }
153
154
 
154
155
  opts.separator ""
155
156
  opts.separator "Daemon options:"
@@ -211,7 +212,8 @@ module Goliath
211
212
 
212
213
  File.umask(0000)
213
214
 
214
- stdout_log_file = "#{File.dirname(@log_file)}/#{File.basename(@log_file)}_stdout.log"
215
+ log_extension = File.extname(@log_file)
216
+ stdout_log_file = "#{File.dirname(@log_file)}/#{File.basename(@log_file, log_extension)}_stdout#{log_extension}"
215
217
 
216
218
  STDIN.reopen("/dev/null")
217
219
  STDOUT.reopen(stdout_log_file, "a")
@@ -296,8 +298,7 @@ module Goliath
296
298
  # @return [Nil]
297
299
  def run_server
298
300
  log = setup_logger
299
-
300
- log.info("Starting server on #{@address}:#{@port} in #{Goliath.env} mode. Watch out for stones.")
301
+ log.info("Starting server on http#{ @server_options[:ssl] ? 's' : nil }://#{@address}:#{@port} in #{Goliath.env} mode. Watch out for stones.")
301
302
 
302
303
  server = setup_server(log)
303
304
  server.api.setup if server.api.respond_to?(:setup)
@@ -1,4 +1,5 @@
1
1
  require 'em-synchrony'
2
+ require 'einhorn'
2
3
  require 'goliath/connection'
3
4
  require 'goliath/goliath'
4
5
 
@@ -83,7 +84,7 @@ module Goliath
83
84
 
84
85
  EM.set_effective_user(options[:user]) if options[:user]
85
86
 
86
- config[Goliath::Constants::GOLIATH_SIGNATURE] = EM.start_server(address, port, Goliath::Connection) do |conn|
87
+ config[Goliath::Constants::GOLIATH_SIGNATURE] = start_server(options) do |conn|
87
88
  if options[:ssl]
88
89
  conn.start_tls(
89
90
  :private_key_file => options[:ssl_key],
@@ -105,9 +106,23 @@ module Goliath
105
106
  end
106
107
  end
107
108
 
109
+ def start_server(options, &blk)
110
+ if options[:einhorn]
111
+ fd_num = Einhorn::Worker.socket!
112
+ socket = Socket.for_fd(fd_num)
113
+
114
+ EM.attach_server(socket, Goliath::Connection, &blk)
115
+ else
116
+ EM.start_server(address, port, Goliath::Connection, &blk)
117
+ end
118
+ end
119
+
108
120
  # Stops the server running.
109
121
  def stop
110
- EM.stop
122
+ EM.add_timer(0) do
123
+ logger.info('Stopping server...')
124
+ EM.stop
125
+ end
111
126
  end
112
127
 
113
128
  # Loads a configuration file
@@ -0,0 +1,76 @@
1
+ require 'em-eventsource'
2
+
3
+ module Goliath
4
+ module TestHelper
5
+ class SSEHelper
6
+ attr_reader :connection
7
+ def initialize(url)
8
+ @message_queue = EM::Queue.new
9
+ @named_queues = {}
10
+ @connection = EM::EventSource.new(url)
11
+ end
12
+
13
+ def listen
14
+ @connection.message do |message|
15
+ @message_queue.push(message)
16
+ end
17
+ end
18
+
19
+ def listen_to(name)
20
+ queue = (@named_queues[name] ||= [])
21
+ @connection.on(name) do |message|
22
+ queue.push(message)
23
+ end
24
+ end
25
+
26
+ def receive
27
+ pop_queue(@message_queue)
28
+ end
29
+
30
+ def receive_on(name)
31
+ queue = @named_queues.fetch(name) do
32
+ raise ArgumentError, "You have to call listen_to('#{name}') first"
33
+ end
34
+
35
+ pop_queue(queue)
36
+ end
37
+
38
+ def with_async_http
39
+ klass = EM::HttpConnection
40
+ if klass.instance_methods.include?(:aget)
41
+ begin
42
+ klass.send(:class_eval) do
43
+ alias :sget :get
44
+ alias :get :aget
45
+ end
46
+ yield if block_given?
47
+ ensure
48
+ klass.send(:class_eval) do
49
+ alias :get :sget
50
+ remove_method :sget
51
+ end
52
+ end
53
+ else
54
+ yield if block_given?
55
+ end
56
+ end
57
+
58
+ protected
59
+
60
+ def pop_queue(queue)
61
+ fiber = Fiber.current
62
+ queue.pop { |m| fiber.resume(m) }
63
+ Fiber.yield
64
+ end
65
+ end
66
+
67
+ def sse_client_connect(path,&blk)
68
+ url = "http://localhost:#{@test_server_port}#{path}"
69
+ client = SSEHelper.new(url)
70
+ client.with_async_http { client.connection.start }
71
+ client.listen
72
+ Fiber.new { blk.call(client) }.resume if blk
73
+ stop
74
+ end
75
+ end
76
+ end
@@ -4,6 +4,8 @@ module Goliath
4
4
  class Error < StandardError
5
5
  # The status code to return from the error handler
6
6
  attr_accessor :status_code
7
+ # The headers to return from the error handler
8
+ attr_accessor :headers
7
9
 
8
10
  # Create a new Goliath::Validation::Error.
9
11
  #
@@ -13,9 +15,10 @@ module Goliath
13
15
  # @param status_code [Integer] The status code to return
14
16
  # @param message [String] The error message to return
15
17
  # @return [Goliath::Validation::Error] The Goliath::Validation::Error
16
- def initialize(status_code, message)
18
+ def initialize(status_code, message, headers = {})
17
19
  super(message)
18
20
  @status_code = status_code
21
+ @headers = headers
19
22
  end
20
23
  end
21
24
  end
@@ -1,4 +1,4 @@
1
1
  module Goliath
2
2
  # The current version of Goliath
3
- VERSION = '1.0.4'
3
+ VERSION = '1.0.5'
4
4
  end
@@ -25,7 +25,7 @@ describe EarlyAbort do
25
25
 
26
26
  post_request(request_data, err) do |c|
27
27
  c.response.should == "{\"error\":\"Can't handle requests with X-Crash: true.\"}"
28
- File.exist?("/tmp/goliath-test-error.log").should be_false
28
+ File.exist?("/tmp/goliath-test-error.log").should be false
29
29
  end
30
30
  end
31
31
  end
@@ -36,8 +36,8 @@ describe EarlyAbort do
36
36
 
37
37
  post_request(request_data, err) do |c|
38
38
  c.response.should =~ /Payload size can't exceed 10 bytes/
39
- File.exist?("/tmp/goliath-test-error.log").should be_false
39
+ File.exist?("/tmp/goliath-test-error.log").should be false
40
40
  end
41
41
  end
42
42
  end
43
- end
43
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+ require File.join(File.dirname(__FILE__), '../../', 'lib/goliath/test_helper_sse')
3
+ require File.join(File.dirname(__FILE__), '../../', 'lib/goliath')
4
+ require File.join(File.dirname(__FILE__), '../../', 'lib/goliath/api')
5
+
6
+ class EventStreamEndpoint < Goliath::API
7
+ def self.events
8
+ @events ||= EM::Queue.new
9
+ end
10
+
11
+ def response(env)
12
+ self.class.events.pop do |event|
13
+ payload = if event.key?(:name)
14
+ "event: #{event[:name]}\ndata: #{event[:data]}\n\n"
15
+ else
16
+ "data: #{event[:data]}\n\n"
17
+ end
18
+ env.stream_send(payload)
19
+ end
20
+ streaming_response(200, 'Content-Type' => 'text/event-stream')
21
+ end
22
+ end
23
+
24
+ describe 'EventStream' do
25
+ include Goliath::TestHelper
26
+
27
+ context 'named events' do
28
+ it 'should accept stream' do
29
+ with_api(EventStreamEndpoint, {:verbose => true, :log_stdout => true}) do |server|
30
+ sse_client_connect('/stream') do |client|
31
+ client.listen_to('custom_event')
32
+ EventStreamEndpoint.events.push(name: 'custom_event', data: 'content')
33
+ client.receive_on('custom_event').should == ['content']
34
+ client.receive.should == []
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ context 'unnamed events' do
41
+ it 'should accept stream' do
42
+ with_api(EventStreamEndpoint, {:verbose => true, :log_stdout => true}) do |server|
43
+ sse_client_connect('/stream') do |client|
44
+ EventStreamEndpoint.events.push(data: 'content')
45
+ client.receive.should == ['content']
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,202 @@
1
+ # This integration spec isn't *quite* about exception handling. It's more of a
2
+ # regression test, since Goliath::Rack::Params used to swallow any exception
3
+ # generated downstream from it in the middleware chain (even though
4
+ # Goliath::API#call handles exceptions already).
5
+
6
+ require 'spec_helper'
7
+
8
+ class ExceptionHandlingMiddleware
9
+ include Goliath::Rack::AsyncMiddleware
10
+ include Goliath::Constants
11
+
12
+ def call(env)
13
+ # This kind of relies on downstream middleware raising their own exceptions
14
+ # if something goes wrong. Alternatively, they could rescue the exception
15
+ # and set env[RACK_EXCEPTION]. See #post_process.
16
+ super
17
+ rescue Exception => e
18
+ handle_exception(e)
19
+ end
20
+
21
+ def post_process(env, status, headers, body)
22
+ # If an exception was raised by Goliath::API#call, it will store the
23
+ # exception in env[RACK_EXCEPTION] and automatically generate an error
24
+ # response. We circumvent the usual error response by returning our own.
25
+ return handle_exception(env[RACK_EXCEPTION]) if env[RACK_EXCEPTION]
26
+ [status, headers, body]
27
+ end
28
+
29
+ private
30
+
31
+ def handle_exception(e)
32
+ # You could imagine this middleware having some sort of side effect, like
33
+ # sending your team an email alert detailing the exception. For the
34
+ # purposes of the spec, we'll just return some text.
35
+ [200, {}, "Exception raised: #{e.message}"]
36
+ end
37
+ end
38
+
39
+ class ExceptionRaisingMiddleware
40
+ def initialize(app)
41
+ @app = app
42
+ end
43
+
44
+ def call(env)
45
+ raise env.params['raise'] if env.params['raise']
46
+ @app.call(env)
47
+ end
48
+ end
49
+
50
+ class ExceptionHandlingAPI < Goliath::API
51
+ # The exception handling middleware should come first in the chain so that
52
+ # any exception in the rest of the chain gets rescued.
53
+ use ExceptionHandlingMiddleware
54
+
55
+ # Require param parsing before the exception raising middleware, because the
56
+ # latter depends on params being parsed. Herein lies the regression:
57
+ # Goliath::Rack::Params used to swallow any exceptions raised downstream from
58
+ # it (here, ExceptionRaisingMiddleware), so any upstream middleware (here,
59
+ # ExceptionHandlingMiddleware) would be none the wiser. Really, the only
60
+ # thing Goliath::Rack::Params should be worried about is whether it can parse
61
+ # the params.
62
+ use Goliath::Rack::Params
63
+
64
+ # Allow us to raise an exception on demand with the param raise=<message>.
65
+ # You could imagine something less dumb for a real-life application that
66
+ # incidentally raises an exception, if you'd like.
67
+ use ExceptionRaisingMiddleware
68
+
69
+ def response(env)
70
+ # Goliath::API#call ensures that any exceptions raised here get rescued and
71
+ # stored in env[RACK_EXCEPTION].
72
+ fail env.params['fail'] if env.params['fail']
73
+ [200, {}, 'No exceptions raised']
74
+ end
75
+ end
76
+
77
+ describe ExceptionHandlingAPI do
78
+ let(:err) { Proc.new { fail 'API request failed' } }
79
+
80
+ context 'when no exceptions get raised' do
81
+ let(:query) { '' }
82
+
83
+ it 'returns a normal response' do
84
+ with_api(ExceptionHandlingAPI) do
85
+ get_request({ query: query }, err) do |c|
86
+ c.response_header.status.should == 200
87
+ c.response.should == 'No exceptions raised'
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ context 'when a middleware raises an exception' do
94
+ let(:query) { 'raise=zoinks' }
95
+
96
+ it 'handles the exception using ExceptionHandlingMiddleware' do
97
+ with_api(ExceptionHandlingAPI) do
98
+ get_request({ query: query }, err) do |c|
99
+ c.response_header.status.should == 200
100
+ c.response.should == 'Exception raised: zoinks'
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ context 'when the API raises an exception' do
107
+ let(:query) { 'fail=jinkies' }
108
+
109
+ it 'handles the exception using ExceptionHandlingMiddleware' do
110
+ with_api(ExceptionHandlingAPI) do
111
+ get_request({ query: query }, err) do |c|
112
+ c.response_header.status.should == 200
113
+ c.response.should == 'Exception raised: jinkies'
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ context 'when param parsing raises an exception' do
120
+ let(:query) { 'ambiguous[]=&ambiguous[4]=' }
121
+
122
+ it 'returns a validation error generated by Goliath::Rack::Params' do
123
+ with_api(ExceptionHandlingAPI) do
124
+ get_request({ query: query }, err) do |c|
125
+ c.response_header.status.should == 400
126
+ c.response.should == "[:error, \"Invalid parameters: Rack::Utils::ParameterTypeError\"]"
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+
133
+ # This API has the same setup as ExceptionHandlingAPI, except for the crucial
134
+ # difference that it does *not* use ExceptionHandlingMiddleware. This is to
135
+ # make sure that exceptions are still rescued somewhere in an API call, even
136
+ # though they aren't getting swallowed by Goliath::Rack::Params anymore.
137
+
138
+ class PassiveExceptionHandlingAPI < Goliath::API
139
+ use Goliath::Rack::Params
140
+ use ExceptionRaisingMiddleware
141
+
142
+ def response(env)
143
+ fail env.params['fail'] if env.params['fail']
144
+ [200, {}, 'No exceptions raised']
145
+ end
146
+ end
147
+
148
+ describe PassiveExceptionHandlingAPI do
149
+ let(:err) { Proc.new { fail 'API request failed' } }
150
+
151
+ context 'when no exceptions get raised' do
152
+ let(:query) { '' }
153
+
154
+ it 'returns a normal response' do
155
+ with_api(PassiveExceptionHandlingAPI) do
156
+ get_request({ query: query }, err) do |c|
157
+ c.response_header.status.should == 200
158
+ c.response.should == 'No exceptions raised'
159
+ end
160
+ end
161
+ end
162
+ end
163
+
164
+ context 'when a middleware raises an exception' do
165
+ let(:query) { 'raise=ruh-roh' }
166
+
167
+ it 'returns the server error generated by Goliath::Request#process' do
168
+ with_api(PassiveExceptionHandlingAPI) do
169
+ get_request({ query: query }, err) do |c|
170
+ c.response_header.status.should == 500
171
+ c.response.should == 'ruh-roh'
172
+ end
173
+ end
174
+ end
175
+ end
176
+
177
+ context 'when the API raises an exception' do
178
+ let(:query) { 'fail=puppy-power' }
179
+
180
+ it 'returns the validation error generated by Goliath::API#call' do
181
+ with_api(PassiveExceptionHandlingAPI) do
182
+ get_request({ query: query }, err) do |c|
183
+ c.response_header.status.should == 500
184
+ c.response.should == '[:error, "puppy-power"]'
185
+ end
186
+ end
187
+ end
188
+ end
189
+
190
+ context 'when param parsing raises an exception' do
191
+ let(:query) { 'ambiguous[]=&ambiguous[4]=' }
192
+
193
+ it 'returns a validation error generated by Goliath::Rack::Params' do
194
+ with_api(PassiveExceptionHandlingAPI) do
195
+ get_request({ query: query }, err) do |c|
196
+ c.response_header.status.should == 400
197
+ c.response.should == '[:error, "Invalid parameters: Rack::Utils::ParameterTypeError"]'
198
+ end
199
+ end
200
+ end
201
+ end
202
+ end