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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) 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 +75 -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 +14 -9
  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 +71 -34
  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 +32 -37
  29. data/lib/shark_on_lambda/concerns/resettable_singleton.rb +0 -18
  30. data/lib/shark_on_lambda/concerns/yaml_config_loader.rb +0 -28
  31. data/lib/shark_on_lambda/dispatcher.rb +0 -26
  32. data/lib/shark_on_lambda/inferrers/name_inferrer.rb +0 -66
  33. data/lib/shark_on_lambda/jsonapi_controller.rb +0 -29
  34. data/lib/shark_on_lambda/middleware/rescuer.rb +0 -37
  35. data/lib/shark_on_lambda/query.rb +0 -67
  36. data/lib/shark_on_lambda/rack_adapters/api_gateway.rb +0 -128
  37. 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!(&: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,42 +18,88 @@ 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
- set_content_type_and_content_length
40
+ env['CONTENT_TYPE'] = headers['content-type']
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'])
51
50
  end
52
51
 
53
52
  def initialize_env
54
53
  @env = Rack::MockRequest.env_for(
55
- 'https://localhost:9292',
54
+ request_uri.to_s,
56
55
  method: method,
57
- params: params,
58
- 'shark.controller' => controller,
59
- 'shark.action' => action
56
+ params: params
60
57
  )
61
58
  end
62
59
 
63
- def set_content_type_and_content_length
64
- env['CONTENT_TYPE'] = headers['content-type']
65
- env['CONTENT_LENGTH'] = env['rack.input'].length.to_s
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)
66
103
  end
67
104
  end
68
105
  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