shark-on-lambda 1.0.0.rc3 → 2.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -3
  3. data/.rubocop.yml +4 -0
  4. data/README.md +70 -17
  5. data/changelog.md +19 -1
  6. data/doc/upgrade-from-0.6.x-to-1.x.md +122 -0
  7. data/lib/shark-on-lambda.rb +9 -0
  8. data/lib/shark_on_lambda.rb +14 -54
  9. data/lib/shark_on_lambda/api_gateway_handler.rb +3 -55
  10. data/lib/shark_on_lambda/application.rb +73 -5
  11. data/lib/shark_on_lambda/base_controller.rb +29 -9
  12. data/lib/shark_on_lambda/cacheable.rb +21 -0
  13. data/lib/shark_on_lambda/configuration.rb +1 -65
  14. data/lib/shark_on_lambda/inferrers/serializer_inferrer.rb +10 -7
  15. data/lib/shark_on_lambda/jsonapi_renderer.rb +16 -10
  16. data/lib/shark_on_lambda/middleware/base.rb +2 -4
  17. data/lib/shark_on_lambda/middleware/honeybadger.rb +45 -0
  18. data/lib/shark_on_lambda/middleware/jsonapi_rescuer.rb +21 -1
  19. data/lib/shark_on_lambda/middleware/lambda_logger.rb +8 -16
  20. data/lib/shark_on_lambda/rake_tasks.rb +16 -0
  21. data/lib/shark_on_lambda/request.rb +0 -3
  22. data/lib/shark_on_lambda/rspec/env_builder.rb +72 -31
  23. data/lib/shark_on_lambda/rspec/helpers.rb +5 -87
  24. data/lib/shark_on_lambda/rspec/request_helpers.rb +63 -0
  25. data/lib/shark_on_lambda/rspec/{jsonapi_helpers.rb → response_helpers.rb} +4 -10
  26. data/lib/shark_on_lambda/version.rb +1 -1
  27. data/shark-on-lambda.gemspec +7 -5
  28. metadata +33 -38
  29. data/gems.locked +0 -142
  30. data/lib/shark_on_lambda/concerns/resettable_singleton.rb +0 -18
  31. data/lib/shark_on_lambda/concerns/yaml_config_loader.rb +0 -28
  32. data/lib/shark_on_lambda/dispatcher.rb +0 -26
  33. data/lib/shark_on_lambda/inferrers/name_inferrer.rb +0 -66
  34. data/lib/shark_on_lambda/jsonapi_controller.rb +0 -29
  35. data/lib/shark_on_lambda/middleware/rescuer.rb +0 -38
  36. data/lib/shark_on_lambda/query.rb +0 -67
  37. data/lib/shark_on_lambda/rack_adapters/api_gateway.rb +0 -128
  38. data/lib/shark_on_lambda/secrets.rb +0 -43
@@ -2,7 +2,15 @@
2
2
 
3
3
  module SharkOnLambda
4
4
  module Middleware
5
- class JsonapiRescuer < Rescuer
5
+ class JsonapiRescuer < Base
6
+ def call!(env)
7
+ app.call(env)
8
+ rescue Errors::Base => e
9
+ rescue_shark_error(e)
10
+ rescue StandardError => e
11
+ rescue_standard_error(e)
12
+ end
13
+
6
14
  private
7
15
 
8
16
  def error_object(status, message)
@@ -26,6 +34,18 @@ module SharkOnLambda
26
34
 
27
35
  [status, headers, response_body]
28
36
  end
37
+
38
+ def rescue_shark_error(error)
39
+ status = error.status || 500
40
+ error_response(status, {}, error.message)
41
+ end
42
+
43
+ def rescue_standard_error(error)
44
+ SharkOnLambda.logger.error(error.message)
45
+ SharkOnLambda.logger.error(error.backtrace.join("\n"))
46
+
47
+ error_response(500, {}, error.message)
48
+ end
29
49
  end
30
50
  end
31
51
  end
@@ -10,37 +10,34 @@ module SharkOnLambda
10
10
  @logger = logger
11
11
  end
12
12
 
13
- private
14
-
15
- def _call(env)
13
+ def call!(env)
16
14
  start_time = Time.now
17
15
  response = app.call(env)
18
- end_time = Time.now
16
+ duration = duration_in_ms(start_time, Time.now)
19
17
 
20
18
  if logger.info?
21
- log_request(env: env,
22
- response: response,
23
- start_time: start_time,
24
- end_time: end_time)
19
+ log_request(env: env, response: response, duration: duration)
25
20
  end
26
21
 
27
22
  response
28
23
  end
29
24
 
25
+ private
26
+
30
27
  def body_size(body)
31
28
  size = 0
32
29
  body.each { |chunk| size += chunk.bytesize }
33
30
  size
34
31
  end
35
32
 
36
- def log_request(env:, response:, start_time:, end_time:)
33
+ def log_request(env:, response:, duration:)
37
34
  log_object = {
38
35
  url: env['PATH_INFO'],
39
36
  method: env['REQUEST_METHOD'],
40
- params: params(env),
37
+ params: env.fetch('action_dispatch.request.parameters', {}),
41
38
  status: response[0],
42
39
  length: body_size(response[2]),
43
- duration: "#{duration_in_ms(start_time, end_time)} ms"
40
+ duration: "#{duration} ms"
44
41
  }
45
42
  logger.info log_object.to_json
46
43
  end
@@ -49,11 +46,6 @@ module SharkOnLambda
49
46
  duration = (end_time - start_time) * 1000
50
47
  duration.abs.floor(3)
51
48
  end
52
-
53
- def params(env)
54
- query_params = Rack::Utils.parse_nested_query(env['QUERY_STRING'])
55
- query_params.merge(env['shark.path_parameters'] || {})
56
- end
57
49
  end
58
50
  end
59
51
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'action_dispatch/routing/inspector'
4
+ require 'rake'
5
+ require 'shark_on_lambda'
6
+
7
+ namespace :'shark-on-lambda' do
8
+ desc 'Print out all defined routes in match order, with names'
9
+ task :routes do
10
+ routes = SharkOnLambda.application.routes.routes
11
+ inspector = ActionDispatch::Routing::RoutesInspector.new(routes)
12
+ formatter = ActionDispatch::Routing::ConsoleFormatter::Sheet.new
13
+
14
+ puts inspector.format(formatter)
15
+ end
16
+ end
@@ -2,8 +2,5 @@
2
2
 
3
3
  module SharkOnLambda
4
4
  class Request < ActionDispatch::Request
5
- def path_parameters
6
- super.merge(env['shark.path_parameters'] || {})
7
- end
8
5
  end
9
6
  end
@@ -3,23 +3,14 @@
3
3
  module SharkOnLambda
4
4
  module RSpec
5
5
  class EnvBuilder
6
- attr_reader :action, :controller, :headers, :method, :params
7
-
8
- def initialize(options = {})
9
- @method = options.fetch(:method).to_s.upcase
10
- @controller = options.fetch(:controller)
11
- @action = options.fetch(:action)
12
-
13
- @headers = (options[:headers] || {}).deep_stringify_keys
14
- @headers.transform_keys! { |key| key.downcase }
15
- @params = options[:params]
16
-
17
- initialize_env
18
- add_headers
19
- add_request_body
6
+ def initialize(**options)
7
+ @options = options
20
8
  end
21
9
 
22
10
  def build
11
+ initialize_env
12
+ add_headers
13
+ add_request_body_as_json if body? && jsonable_params? && json_request?
23
14
  env.deep_stringify_keys
24
15
  end
25
16
 
@@ -27,39 +18,89 @@ module SharkOnLambda
27
18
 
28
19
  attr_reader :env
29
20
 
30
- def add_header(name, value)
31
- name = name.upcase.tr('-', '_')
32
- key = case name
33
- when 'CONTENT_LENGTH', 'CONTENT_TYPE' then name
34
- else "HTTP_#{name}"
35
- end
36
- @env[key] = value.to_s
21
+ def action
22
+ @options.fetch(:action)
37
23
  end
38
24
 
39
25
  def add_headers
40
- headers.each_pair { |name, value| add_header(name, value) }
26
+ headers.each_pair do |name, value|
27
+ name = name.upcase.tr('-', '_')
28
+ key = case name
29
+ when 'CONTENT_LENGTH', 'CONTENT_TYPE' then name
30
+ else "HTTP_#{name}"
31
+ end
32
+ env[key] = value.to_s
33
+ end
41
34
  end
42
35
 
43
- def add_request_body
44
- return if %w[GET HEAD OPTIONS].include?(env['REQUEST_METHOD'])
45
- return unless params.is_a?(Hash)
46
-
36
+ def add_request_body_as_json
47
37
  body = params.to_json
48
38
 
49
39
  env['rack.input'] = StringIO.new(body).set_encoding(Encoding::BINARY)
50
40
  env['CONTENT_TYPE'] = headers['content-type']
51
- env['CONTENT_LENGTH'] = body.bytesize.to_s
41
+ env['CONTENT_LENGTH'] = env['rack.input'].length.to_s
42
+ end
43
+
44
+ def as
45
+ @options.fetch(:as, :json)
46
+ end
47
+
48
+ def body?
49
+ !%w[GET HEAD OPTIONS].include?(env['REQUEST_METHOD'])
52
50
  end
53
51
 
54
52
  def initialize_env
55
53
  @env = Rack::MockRequest.env_for(
56
- 'https://localhost:9292',
54
+ request_uri.to_s,
57
55
  method: method,
58
- params: params,
59
- 'shark.controller' => controller,
60
- 'shark.action' => action
56
+ params: params
61
57
  )
62
58
  end
59
+
60
+ def controller
61
+ @options.fetch(:controller, nil)
62
+ end
63
+
64
+ def headers
65
+ return @headers if defined?(@headers)
66
+
67
+ @headers = @options.fetch(:headers, {}).deep_stringify_keys
68
+ @headers.transform_keys!(&:downcase)
69
+ @headers
70
+ end
71
+
72
+ def json_request?
73
+ as == :json
74
+ end
75
+
76
+ def jsonable_params?
77
+ params.is_a?(Hash)
78
+ end
79
+
80
+ def method
81
+ @options.fetch(:method).to_s.upcase
82
+ end
83
+
84
+ def params
85
+ @options.fetch(:params, {}).deep_stringify_keys
86
+ end
87
+
88
+ def path_from_routes
89
+ path_params = {
90
+ controller: controller.name.underscore.sub(/_controller$/, ''),
91
+ action: action,
92
+ only_path: true
93
+ }
94
+ url = SharkOnLambda.application.routes.url_for(path_params, nil)
95
+ URI.parse(url).path
96
+ end
97
+
98
+ def request_uri
99
+ return @request_uri if defined?(@request_uri)
100
+
101
+ path = action.is_a?(String) ? action : path_from_routes
102
+ @request_uri = URI.join('https://localhost:9292', path)
103
+ end
63
104
  end
64
105
  end
65
106
  end
@@ -3,94 +3,12 @@
3
3
  module SharkOnLambda
4
4
  module RSpec
5
5
  module Helpers
6
- def delete(controller_method, options = {})
7
- make_request('DELETE', controller_method, options)
8
- end
9
-
10
- def get(controller_method, options = {})
11
- make_request('GET', controller_method, options)
12
- end
13
-
14
- def patch(controller_method, options = {})
15
- make_request('PATCH', controller_method, options)
16
- end
17
-
18
- def post(controller_method, options = {})
19
- make_request('POST', controller_method, options)
20
- end
21
-
22
- def put(controller_method, options = {})
23
- make_request('PUT', controller_method, options)
24
- end
25
-
26
- def response
27
- if @response.nil?
28
- raise 'You must make a request before you can request a response.'
29
- end
30
-
31
- @response
32
- end
33
-
34
- private
35
-
36
- def build_env(method, action, options = {})
37
- env_builder = EnvBuilder.new(
38
- method: method,
39
- controller: controller_name,
40
- action: action,
41
- headers: options[:headers],
42
- params: options[:params]
43
- )
44
- env_builder.build
45
- end
46
-
47
- def controller?
48
- controller_name.present?
49
- end
50
-
51
- def controller_name
52
- self.class.ancestors.find do |klass|
53
- klass.name.end_with?('Controller')
54
- end&.description
55
- end
56
-
57
- def default_content_type
58
- 'application/json'
59
- end
60
-
61
- def dispatch_request(env, skip_middleware: false)
62
- return SharkOnLambda.application.call(env) unless skip_middleware
63
-
64
- controller_class = env['shark.controller'].constantize
65
- action = env['shark.action']
66
-
67
- request = Request.new(env)
68
- response = Response.new
69
- controller_class.dispatch(action, request, response)
70
- response.prepare!
71
- end
72
-
73
- def headers_with_content_type(headers)
74
- headers ||= {}
75
- headers.transform_keys! { |key| key.to_s.downcase }
76
- headers['content-type'] ||= default_content_type
77
- headers
78
- end
79
-
80
- def make_request(method, action, options = {})
81
- raise ArgumentError, 'Cannot find controller name.' unless controller?
82
-
83
- options = options.with_indifferent_access
84
- options[:headers] = headers_with_content_type(options[:headers])
85
-
86
- env = build_env(method, action, options)
6
+ extend ActiveSupport::Concern
7
+ include RequestHelpers
8
+ include ResponseHelpers
87
9
 
88
- status, headers, body = dispatch_request(
89
- env,
90
- skip_middleware: options[:skip_middleware]
91
- )
92
- errors = env['rack.errors']
93
- @response = Rack::MockResponse.new(status, headers, body, errors)
10
+ included do
11
+ include SharkOnLambda.application.routes.url_helpers
94
12
  end
95
13
  end
96
14
  end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SharkOnLambda
4
+ module RSpec
5
+ module RequestHelpers
6
+ attr_writer :app
7
+
8
+ SUPPORTED_HTTP_METHODS = %w[
9
+ DELETE GET HEAD OPTIONS PATCH POST PUT
10
+ ].freeze
11
+
12
+ SUPPORTED_HTTP_METHODS.each do |http_method|
13
+ define_method(http_method.underscore) do |action, **options|
14
+ make_request(http_method, action, **options)
15
+ end
16
+ end
17
+
18
+ def app
19
+ @app ||= SharkOnLambda.application
20
+ end
21
+
22
+ def response
23
+ if @response.nil?
24
+ raise 'You must make a request before you can request a response.'
25
+ end
26
+
27
+ @response
28
+ end
29
+
30
+ private
31
+
32
+ def build_env(method, action, **options)
33
+ headers = options.fetch(:headers, {})
34
+ env_builder = EnvBuilder.new(
35
+ method: method,
36
+ controller: described_class,
37
+ action: action,
38
+ headers: normalized_headers(headers),
39
+ params: options.fetch(:params, {})
40
+ )
41
+ env_builder.build
42
+ end
43
+
44
+ def default_content_type
45
+ 'application/vnd.api+json'
46
+ end
47
+
48
+ def make_request(method, action, **options)
49
+ env = build_env(method, action, **options)
50
+
51
+ status, headers, body = app.call(env)
52
+ errors = env['rack.errors']
53
+ @response = Rack::MockResponse.new(status, headers, body, errors)
54
+ end
55
+
56
+ def normalized_headers(headers)
57
+ headers.transform_keys! { |key| key.to_s.downcase }
58
+ headers['content-type'] ||= default_content_type
59
+ headers
60
+ end
61
+ end
62
+ end
63
+ end
@@ -2,27 +2,21 @@
2
2
 
3
3
  module SharkOnLambda
4
4
  module RSpec
5
- module JsonapiHelpers
6
- include Helpers
7
-
5
+ module ResponseHelpers
8
6
  def jsonapi_attributes
9
- jsonapi_data[:attributes] || {}
7
+ jsonapi_data.fetch(:attributes, {})
10
8
  end
11
9
 
12
10
  def jsonapi_data
13
- parsed_body[:data] || {}
11
+ parsed_body.fetch(:data, {})
14
12
  end
15
13
 
16
14
  def jsonapi_errors
17
- parsed_body[:errors] || []
15
+ parsed_body.fetch(:errors, [])
18
16
  end
19
17
 
20
18
  private
21
19
 
22
- def default_content_type
23
- 'application/vnd.api+json'
24
- end
25
-
26
20
  def parsed_body
27
21
  @parsed_body ||= JSON.parse(response.body).with_indifferent_access
28
22
  end