shark-on-lambda 1.0.1 → 2.0.0
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.
- checksums.yaml +4 -4
- data/.rubocop.yml +4 -0
- data/changelog.md +12 -0
- data/doc/upgrade-from-0.6.x-to-1.x.md +3 -3
- data/lib/shark-on-lambda.rb +9 -0
- data/lib/shark_on_lambda.rb +14 -54
- data/lib/shark_on_lambda/api_gateway_handler.rb +3 -55
- data/lib/shark_on_lambda/application.rb +74 -2
- data/lib/shark_on_lambda/base_controller.rb +29 -9
- data/lib/shark_on_lambda/cacheable.rb +21 -0
- data/lib/shark_on_lambda/configuration.rb +1 -65
- data/lib/shark_on_lambda/inferrers/serializer_inferrer.rb +10 -7
- data/lib/shark_on_lambda/jsonapi_renderer.rb +1 -2
- data/lib/shark_on_lambda/middleware/honeybadger.rb +5 -3
- data/lib/shark_on_lambda/middleware/jsonapi_rescuer.rb +21 -1
- data/lib/shark_on_lambda/middleware/lambda_logger.rb +5 -13
- data/lib/shark_on_lambda/rake_tasks.rb +16 -0
- data/lib/shark_on_lambda/request.rb +0 -3
- data/lib/shark_on_lambda/rspec/env_builder.rb +71 -37
- data/lib/shark_on_lambda/rspec/helpers.rb +5 -88
- data/lib/shark_on_lambda/rspec/request_helpers.rb +63 -0
- data/lib/shark_on_lambda/rspec/{jsonapi_helpers.rb → response_helpers.rb} +4 -10
- data/lib/shark_on_lambda/version.rb +1 -1
- data/shark-on-lambda.gemspec +7 -5
- metadata +37 -37
- data/lib/shark_on_lambda/concerns/resettable_singleton.rb +0 -18
- data/lib/shark_on_lambda/concerns/yaml_config_loader.rb +0 -28
- data/lib/shark_on_lambda/dispatcher.rb +0 -26
- data/lib/shark_on_lambda/inferrers/name_inferrer.rb +0 -66
- data/lib/shark_on_lambda/jsonapi_controller.rb +0 -32
- data/lib/shark_on_lambda/middleware/rescuer.rb +0 -37
- data/lib/shark_on_lambda/query.rb +0 -67
- data/lib/shark_on_lambda/rack_adapters/api_gateway.rb +0 -128
- data/lib/shark_on_lambda/secrets.rb +0 -43
@@ -31,14 +31,17 @@ module SharkOnLambda
|
|
31
31
|
def serializer_class_names
|
32
32
|
return @serializer_class_names if defined?(@serializer_class_names)
|
33
33
|
|
34
|
-
@serializer_class_names =
|
35
|
-
|
36
|
-
|
34
|
+
@serializer_class_names =
|
35
|
+
object_class.ancestors.reduce([]) do |result, ancestor|
|
36
|
+
ancestor_name = ancestor.name
|
37
|
+
next result if ancestor_name.blank?
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
39
|
+
result << serializer_name_from_model_name(ancestor_name)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def serializer_name_from_model_name(model_name)
|
44
|
+
"#{model_name.underscore}_serializer".camelize
|
42
45
|
end
|
43
46
|
end
|
44
47
|
end
|
@@ -100,7 +100,7 @@ module SharkOnLambda
|
|
100
100
|
def transform_active_model_errors(errors)
|
101
101
|
return errors unless active_model_error?(errors)
|
102
102
|
|
103
|
-
|
103
|
+
errors.messages.flat_map do |attribute, attribute_errors|
|
104
104
|
attribute_errors.map do |attribute_error|
|
105
105
|
error_message = "`#{attribute_name(attribute)}' #{attribute_error}"
|
106
106
|
Errors[422].new(error_message).tap do |error|
|
@@ -108,7 +108,6 @@ module SharkOnLambda
|
|
108
108
|
end
|
109
109
|
end
|
110
110
|
end
|
111
|
-
result.flatten! || result
|
112
111
|
end
|
113
112
|
|
114
113
|
def unrenderable_objects(object, options)
|
@@ -26,12 +26,14 @@ module SharkOnLambda
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def notify(error, env)
|
29
|
+
params = env.fetch('action_dispatch.request.parameters', {})
|
30
|
+
|
29
31
|
::Honeybadger.notify(
|
30
32
|
error,
|
31
33
|
tags: tags,
|
32
|
-
controller:
|
33
|
-
action:
|
34
|
-
parameters:
|
34
|
+
controller: params[:controller],
|
35
|
+
action: params[:action],
|
36
|
+
parameters: params
|
35
37
|
)
|
36
38
|
end
|
37
39
|
|
@@ -2,7 +2,15 @@
|
|
2
2
|
|
3
3
|
module SharkOnLambda
|
4
4
|
module Middleware
|
5
|
-
class JsonapiRescuer <
|
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
|
@@ -13,13 +13,10 @@ module SharkOnLambda
|
|
13
13
|
def call!(env)
|
14
14
|
start_time = Time.now
|
15
15
|
response = app.call(env)
|
16
|
-
|
16
|
+
duration = duration_in_ms(start_time, Time.now)
|
17
17
|
|
18
18
|
if logger.info?
|
19
|
-
log_request(env: env,
|
20
|
-
response: response,
|
21
|
-
start_time: start_time,
|
22
|
-
end_time: end_time)
|
19
|
+
log_request(env: env, response: response, duration: duration)
|
23
20
|
end
|
24
21
|
|
25
22
|
response
|
@@ -33,14 +30,14 @@ module SharkOnLambda
|
|
33
30
|
size
|
34
31
|
end
|
35
32
|
|
36
|
-
def log_request(env:, response:,
|
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:
|
37
|
+
params: env.fetch('action_dispatch.request.parameters', {}),
|
41
38
|
status: response[0],
|
42
39
|
length: body_size(response[2]),
|
43
|
-
duration: "#{
|
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
|
@@ -3,25 +3,14 @@
|
|
3
3
|
module SharkOnLambda
|
4
4
|
module RSpec
|
5
5
|
class EnvBuilder
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
def initialize(options = {})
|
10
|
-
@method = options.fetch(:method).to_s.upcase
|
11
|
-
@controller = options.fetch(:controller)
|
12
|
-
@action = options.fetch(:action)
|
13
|
-
|
14
|
-
@headers = (options[:headers] || {}).deep_stringify_keys
|
15
|
-
@headers.transform_keys!(&:downcase)
|
16
|
-
@params = options[:params] || {}
|
17
|
-
@path_parameters = options[:path_parameters] || {}
|
18
|
-
|
19
|
-
initialize_env
|
20
|
-
add_headers
|
21
|
-
add_request_body
|
6
|
+
def initialize(**options)
|
7
|
+
@options = options
|
22
8
|
end
|
23
9
|
|
24
10
|
def build
|
11
|
+
initialize_env
|
12
|
+
add_headers
|
13
|
+
add_request_body_as_json if body? && jsonable_params? && json_request?
|
25
14
|
env.deep_stringify_keys
|
26
15
|
end
|
27
16
|
|
@@ -29,43 +18,88 @@ module SharkOnLambda
|
|
29
18
|
|
30
19
|
attr_reader :env
|
31
20
|
|
32
|
-
def
|
33
|
-
|
34
|
-
key = case name
|
35
|
-
when 'CONTENT_LENGTH', 'CONTENT_TYPE' then name
|
36
|
-
else "HTTP_#{name}"
|
37
|
-
end
|
38
|
-
@env[key] = value.to_s
|
21
|
+
def action
|
22
|
+
@options.fetch(:action)
|
39
23
|
end
|
40
24
|
|
41
25
|
def add_headers
|
42
|
-
headers.each_pair
|
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
|
43
34
|
end
|
44
35
|
|
45
|
-
def
|
46
|
-
return if %w[GET HEAD OPTIONS].include?(env['REQUEST_METHOD'])
|
47
|
-
return unless params.is_a?(Hash)
|
48
|
-
|
36
|
+
def add_request_body_as_json
|
49
37
|
body = params.to_json
|
50
38
|
|
51
39
|
env['rack.input'] = StringIO.new(body).set_encoding(Encoding::BINARY)
|
52
|
-
|
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'])
|
53
50
|
end
|
54
51
|
|
55
52
|
def initialize_env
|
56
53
|
@env = Rack::MockRequest.env_for(
|
57
|
-
|
54
|
+
request_uri.to_s,
|
58
55
|
method: method,
|
59
|
-
params: params
|
60
|
-
'shark.controller' => controller,
|
61
|
-
'shark.action' => action,
|
62
|
-
'shark.path_parameters' => path_parameters
|
56
|
+
params: params
|
63
57
|
)
|
64
58
|
end
|
65
59
|
|
66
|
-
def
|
67
|
-
|
68
|
-
|
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)
|
69
103
|
end
|
70
104
|
end
|
71
105
|
end
|
@@ -3,95 +3,12 @@
|
|
3
3
|
module SharkOnLambda
|
4
4
|
module RSpec
|
5
5
|
module Helpers
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
path_parameters: options[:path_parameters]
|
44
|
-
)
|
45
|
-
env_builder.build
|
46
|
-
end
|
47
|
-
|
48
|
-
def controller?
|
49
|
-
controller_name.present?
|
50
|
-
end
|
51
|
-
|
52
|
-
def controller_name
|
53
|
-
self.class.ancestors.find do |klass|
|
54
|
-
klass.name.end_with?('Controller')
|
55
|
-
end&.description
|
56
|
-
end
|
57
|
-
|
58
|
-
def default_content_type
|
59
|
-
'application/json'
|
60
|
-
end
|
61
|
-
|
62
|
-
def dispatch_request(env, skip_middleware: false)
|
63
|
-
return SharkOnLambda.application.call(env) unless skip_middleware
|
64
|
-
|
65
|
-
controller_class = env['shark.controller'].constantize
|
66
|
-
action = env['shark.action']
|
67
|
-
|
68
|
-
request = Request.new(env)
|
69
|
-
response = Response.new
|
70
|
-
controller_class.dispatch(action, request, response)
|
71
|
-
response.prepare!
|
72
|
-
end
|
73
|
-
|
74
|
-
def headers_with_content_type(headers)
|
75
|
-
headers ||= {}
|
76
|
-
headers.transform_keys! { |key| key.to_s.downcase }
|
77
|
-
headers['content-type'] ||= default_content_type
|
78
|
-
headers
|
79
|
-
end
|
80
|
-
|
81
|
-
def make_request(method, action, options = {})
|
82
|
-
raise ArgumentError, 'Cannot find controller name.' unless controller?
|
83
|
-
|
84
|
-
options = options.with_indifferent_access
|
85
|
-
options[:headers] = headers_with_content_type(options[:headers])
|
86
|
-
|
87
|
-
env = build_env(method, action, options)
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
include RequestHelpers
|
8
|
+
include ResponseHelpers
|
88
9
|
|
89
|
-
|
90
|
-
|
91
|
-
skip_middleware: options[:skip_middleware]
|
92
|
-
)
|
93
|
-
errors = env['rack.errors']
|
94
|
-
@response = Rack::MockResponse.new(status, headers, body, errors)
|
10
|
+
included do
|
11
|
+
include SharkOnLambda.application.routes.url_helpers
|
95
12
|
end
|
96
13
|
end
|
97
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
|