shark-on-lambda 1.0.0.rc4 → 2.0.0.rc3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -3
- data/.rubocop.yml +4 -0
- data/README.md +70 -17
- data/changelog.md +19 -1
- data/doc/upgrade-from-0.6.x-to-1.x.md +122 -0
- 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 +75 -5
- 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 +16 -10
- data/lib/shark_on_lambda/middleware/base.rb +2 -4
- data/lib/shark_on_lambda/middleware/honeybadger.rb +14 -9
- data/lib/shark_on_lambda/middleware/jsonapi_rescuer.rb +21 -1
- data/lib/shark_on_lambda/middleware/lambda_logger.rb +8 -16
- 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 -34
- data/lib/shark_on_lambda/rspec/helpers.rb +5 -87
- 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 +32 -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 -29
- 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
@@ -1,61 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module SharkOnLambda
|
4
|
-
class ApiGatewayHandler
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
def controller_action?(action)
|
9
|
-
controller_actions.include?(action.to_sym)
|
10
|
-
end
|
11
|
-
|
12
|
-
def controller_class_name
|
13
|
-
return @controller_class_name if defined?(@controller_class_name)
|
14
|
-
|
15
|
-
name_inferrer = Inferrers::NameInferrer.from_handler_name(name)
|
16
|
-
@controller_class_name = name_inferrer.controller
|
17
|
-
end
|
18
|
-
|
19
|
-
private
|
20
|
-
|
21
|
-
def controller_actions
|
22
|
-
return [] if controller_class.nil?
|
23
|
-
|
24
|
-
controller_class.public_instance_methods(false)
|
25
|
-
end
|
26
|
-
|
27
|
-
def controller_class
|
28
|
-
controller_class_name.safe_constantize
|
29
|
-
end
|
30
|
-
|
31
|
-
def method_missing(action, *args, &_block)
|
32
|
-
return super unless respond_to_missing?(action)
|
33
|
-
|
34
|
-
new.call(action, *args)
|
35
|
-
end
|
36
|
-
|
37
|
-
def respond_to_missing?(name, _include_all = false)
|
38
|
-
controller_action?(name)
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
attr_reader :application, :env
|
43
|
-
delegate :context, :event, to: :adapter
|
44
|
-
|
45
|
-
def initialize
|
46
|
-
@application = Application.new
|
47
|
-
end
|
48
|
-
|
49
|
-
def call(action, event:, context:)
|
50
|
-
raise NoMethodError unless self.class.controller_action?(action)
|
51
|
-
|
52
|
-
adapter = RackAdapters::ApiGateway.new(context: context, event: event)
|
53
|
-
env = adapter.env
|
54
|
-
env['shark.controller'] = self.class.controller_class_name
|
55
|
-
env['shark.action'] = action.to_s
|
56
|
-
|
57
|
-
status, headers, body = @application.call(env)
|
58
|
-
adapter.build_response(status, headers, body)
|
4
|
+
class ApiGatewayHandler < RackOnLambda::Handlers::RestApi
|
5
|
+
def self.call(event:, context:)
|
6
|
+
super(event: event, context: context, app: SharkOnLambda.application)
|
59
7
|
end
|
60
8
|
end
|
61
9
|
end
|
@@ -2,16 +2,86 @@
|
|
2
2
|
|
3
3
|
module SharkOnLambda
|
4
4
|
class Application
|
5
|
+
attr_reader :routes
|
6
|
+
|
7
|
+
delegate :middleware, :root, to: :config
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def config
|
11
|
+
@config ||= Configuration.new
|
12
|
+
end
|
13
|
+
|
14
|
+
def inherited(subclass)
|
15
|
+
super
|
16
|
+
|
17
|
+
SharkOnLambda.application = subclass.new
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize
|
22
|
+
register_jsonapi_rendering
|
23
|
+
initialize_router
|
24
|
+
end
|
25
|
+
|
5
26
|
def call(env)
|
6
|
-
dup.
|
27
|
+
dup.call!(env)
|
28
|
+
end
|
29
|
+
|
30
|
+
def call!(env)
|
31
|
+
middleware_stack = middleware.build(routes)
|
32
|
+
middleware_stack.call(env)
|
33
|
+
end
|
34
|
+
|
35
|
+
def config
|
36
|
+
self.class.config
|
37
|
+
end
|
38
|
+
|
39
|
+
def config_for(name, env: SharkOnLambda.env)
|
40
|
+
config = load_config_file(name, env: env, fail_with_exception: true)
|
41
|
+
config.deep_merge(load_config_file("#{name}.local", env: env))
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize!
|
45
|
+
load_routes
|
46
|
+
run_initializers
|
7
47
|
end
|
8
48
|
|
9
49
|
private
|
10
50
|
|
11
|
-
def
|
12
|
-
|
13
|
-
|
14
|
-
|
51
|
+
def initialize_router
|
52
|
+
router_config = ActionDispatch::Routing::RouteSet::Config.new(nil, true)
|
53
|
+
@routes = ActionDispatch::Routing::RouteSet.new_with_config(router_config)
|
54
|
+
end
|
55
|
+
|
56
|
+
def load_config_file(name, env:, fail_with_exception: false)
|
57
|
+
filename = "#{name}.yml"
|
58
|
+
config_file = SharkOnLambda.root.join('config', filename)
|
59
|
+
unless config_file.exist?
|
60
|
+
return {} unless fail_with_exception
|
61
|
+
|
62
|
+
raise ArgumentError,
|
63
|
+
"Could not load configuration. No such file - #{config_file}"
|
64
|
+
end
|
65
|
+
|
66
|
+
erb_parsed_config = ERB.new(config_file.read).result
|
67
|
+
config = YAML.safe_load(erb_parsed_config, [], [], true, filename) || {}
|
68
|
+
config.fetch(env, {}).with_indifferent_access
|
69
|
+
end
|
70
|
+
|
71
|
+
def load_routes
|
72
|
+
routes_path = SharkOnLambda.root.join('config', 'routes.rb').to_s
|
73
|
+
load routes_path if File.exist?(routes_path)
|
74
|
+
end
|
75
|
+
|
76
|
+
def register_jsonapi_rendering
|
77
|
+
::Mime::Type.register('application/vnd.api+json', :jsonapi)
|
78
|
+
::ActionDispatch::Request.parameter_parsers[:jsonapi] =
|
79
|
+
::ActionDispatch::Request.parameter_parsers[:json].dup
|
80
|
+
end
|
81
|
+
|
82
|
+
def run_initializers
|
83
|
+
initializers_folder = SharkOnLambda.root.join('config', 'initializers')
|
84
|
+
Dir.glob(initializers_folder.join('*.rb')).each { |path| load path }
|
15
85
|
end
|
16
86
|
end
|
17
87
|
end
|
@@ -6,13 +6,6 @@ module SharkOnLambda
|
|
6
6
|
AbstractController::Translation,
|
7
7
|
AbstractController::AssetPaths,
|
8
8
|
|
9
|
-
ActionController::UrlFor,
|
10
|
-
ActionController::ConditionalGet,
|
11
|
-
ActionController::EtagWithTemplateDigest,
|
12
|
-
ActionController::EtagWithFlash,
|
13
|
-
ActionController::Caching,
|
14
|
-
ActionController::MimeResponds,
|
15
|
-
ActionController::ImplicitRender,
|
16
9
|
ActionController::Cookies,
|
17
10
|
ActionController::Flash,
|
18
11
|
ActionController::FormBuilder,
|
@@ -24,10 +17,27 @@ module SharkOnLambda
|
|
24
17
|
ActionController::HttpAuthentication::Token::ControllerMethods,
|
25
18
|
ActionView::Layouts
|
26
19
|
].freeze
|
27
|
-
ActionController::
|
20
|
+
ActionController::API.without_modules(EXCLUDED_MODULES).each do |mod|
|
28
21
|
include mod
|
29
22
|
end
|
30
23
|
|
24
|
+
ActionController::Renderers.add :jsonapi do |object, options|
|
25
|
+
response.set_header('content-type', 'application/vnd.api+json')
|
26
|
+
return { data: {} }.to_json if object.nil?
|
27
|
+
|
28
|
+
jsonapi_renderer = JsonapiRenderer.new(object)
|
29
|
+
|
30
|
+
jsonapi_params = params.slice(:fields, :include)
|
31
|
+
jsonapi_params.permit!
|
32
|
+
jsonapi_params = JsonapiParameters.new(jsonapi_params.to_h)
|
33
|
+
|
34
|
+
render_options = jsonapi_params.to_h.deep_merge(options)
|
35
|
+
jsonapi_object = jsonapi_renderer.render(render_options)
|
36
|
+
|
37
|
+
response.status = jsonapi_renderer.status
|
38
|
+
jsonapi_object.to_json
|
39
|
+
end
|
40
|
+
|
31
41
|
def self.dispatch(*)
|
32
42
|
super
|
33
43
|
rescue AbstractController::ActionNotFound,
|
@@ -38,7 +48,17 @@ module SharkOnLambda
|
|
38
48
|
|
39
49
|
def redirect_to(*)
|
40
50
|
super
|
41
|
-
|
51
|
+
|
52
|
+
self.response_body = no_body? ? nil : { data: {} }.to_json
|
53
|
+
end
|
54
|
+
|
55
|
+
def render(object, options = {})
|
56
|
+
options.merge!(
|
57
|
+
jsonapi: object,
|
58
|
+
content_type: 'application/vnd.api+json'
|
59
|
+
)
|
60
|
+
|
61
|
+
super(options)
|
42
62
|
end
|
43
63
|
|
44
64
|
private
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SharkOnLambda
|
4
|
+
module Cacheable
|
5
|
+
delegate :cache, :global_cache, to: SharkOnLambda
|
6
|
+
|
7
|
+
def cache_duration(item)
|
8
|
+
cache_durations[item] || cache_durations[:default]
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def cache_durations
|
14
|
+
return @cache_durations if defined?(@cache_durations)
|
15
|
+
|
16
|
+
settings = SharkOnLambda.application.config_for(:settings) || {}
|
17
|
+
@cache_durations = settings.fetch(:cache_durations, {})
|
18
|
+
@cache_durations = @cache_durations.with_indifferent_access
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -2,63 +2,7 @@
|
|
2
2
|
|
3
3
|
module SharkOnLambda
|
4
4
|
class Configuration < OpenStruct
|
5
|
-
|
6
|
-
|
7
|
-
attr_writer :dispatcher, :stage
|
8
|
-
|
9
|
-
class << self
|
10
|
-
include Concerns::YamlConfigLoader
|
11
|
-
|
12
|
-
attr_writer :database_files, :settings_files
|
13
|
-
|
14
|
-
def database_files
|
15
|
-
return @database_files if defined?(@database_files)
|
16
|
-
|
17
|
-
files = %w[config/database.yml config/database.local.yml]
|
18
|
-
@database_files = paths(files)
|
19
|
-
end
|
20
|
-
|
21
|
-
def load(stage, fallback: :default)
|
22
|
-
load_settings(stage, fallback: fallback)
|
23
|
-
load_database_configuration(stage, fallback: fallback)
|
24
|
-
|
25
|
-
instance
|
26
|
-
end
|
27
|
-
|
28
|
-
def settings_files
|
29
|
-
return @settings_files if defined?(@settings_files)
|
30
|
-
|
31
|
-
files = %w[config/settings.yml config/settings.local.yml]
|
32
|
-
@settings_files = paths(files)
|
33
|
-
end
|
34
|
-
|
35
|
-
protected
|
36
|
-
|
37
|
-
def load_database_configuration(stage, fallback:)
|
38
|
-
instance.database = load_yaml_files(stage: stage,
|
39
|
-
fallback: fallback,
|
40
|
-
paths: paths(database_files))
|
41
|
-
end
|
42
|
-
|
43
|
-
def load_settings(stage, fallback:)
|
44
|
-
settings = load_yaml_files(stage: stage,
|
45
|
-
fallback: fallback,
|
46
|
-
paths: paths(settings_files))
|
47
|
-
settings.each_pair do |key, value|
|
48
|
-
next if key.to_s == 'serverless'
|
49
|
-
|
50
|
-
instance.send("#{key}=", value)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def paths(files)
|
55
|
-
files.map { |file| SharkOnLambda.config.root.join(file) }
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
def dispatcher
|
60
|
-
@dispatcher ||= Dispatcher.new
|
61
|
-
end
|
5
|
+
attr_reader :root
|
62
6
|
|
63
7
|
def middleware
|
64
8
|
@middleware ||= ActionDispatch::MiddlewareStack.new do |middleware_stack|
|
@@ -66,16 +10,8 @@ module SharkOnLambda
|
|
66
10
|
end
|
67
11
|
end
|
68
12
|
|
69
|
-
def root
|
70
|
-
@root ||= Pathname.new('.')
|
71
|
-
end
|
72
|
-
|
73
13
|
def root=(new_root)
|
74
14
|
@root = Pathname.new(new_root)
|
75
15
|
end
|
76
|
-
|
77
|
-
def stage
|
78
|
-
@stage || 'development'
|
79
|
-
end
|
80
16
|
end
|
81
17
|
end
|
@@ -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
|
@@ -2,21 +2,27 @@
|
|
2
2
|
|
3
3
|
module SharkOnLambda
|
4
4
|
class JsonapiRenderer
|
5
|
-
|
5
|
+
attr_reader :object, :status
|
6
|
+
|
7
|
+
def initialize(object, renderer: nil)
|
8
|
+
@object = object
|
6
9
|
@renderer = renderer || JSONAPI::Serializable::Renderer.new
|
10
|
+
|
11
|
+
@status = 200
|
7
12
|
end
|
8
13
|
|
9
|
-
def render(
|
10
|
-
|
14
|
+
def render(options = {})
|
15
|
+
@status = options[:status] if options[:status]
|
16
|
+
object_to_render = transform_active_model_errors(object)
|
11
17
|
|
12
|
-
unless renderable?(
|
13
|
-
return handle_unrenderable_objects(
|
18
|
+
unless renderable?(object_to_render, options)
|
19
|
+
return handle_unrenderable_objects(object_to_render, options)
|
14
20
|
end
|
15
21
|
|
16
|
-
if error?(
|
17
|
-
render_errors(
|
22
|
+
if error?(object_to_render)
|
23
|
+
render_errors(object_to_render, options)
|
18
24
|
else
|
19
|
-
render_success(
|
25
|
+
render_success(object_to_render, options)
|
20
26
|
end
|
21
27
|
end
|
22
28
|
|
@@ -54,6 +60,7 @@ module SharkOnLambda
|
|
54
60
|
Errors[500].new("Could not find serializer for: #{item.name}.")
|
55
61
|
end
|
56
62
|
|
63
|
+
@status = 500
|
57
64
|
render_errors(errors, options)
|
58
65
|
end
|
59
66
|
|
@@ -93,7 +100,7 @@ module SharkOnLambda
|
|
93
100
|
def transform_active_model_errors(errors)
|
94
101
|
return errors unless active_model_error?(errors)
|
95
102
|
|
96
|
-
|
103
|
+
errors.messages.flat_map do |attribute, attribute_errors|
|
97
104
|
attribute_errors.map do |attribute_error|
|
98
105
|
error_message = "`#{attribute_name(attribute)}' #{attribute_error}"
|
99
106
|
Errors[422].new(error_message).tap do |error|
|
@@ -101,7 +108,6 @@ module SharkOnLambda
|
|
101
108
|
end
|
102
109
|
end
|
103
110
|
end
|
104
|
-
result.flatten! || result
|
105
111
|
end
|
106
112
|
|
107
113
|
def unrenderable_objects(object, options)
|
@@ -11,24 +11,29 @@ module SharkOnLambda
|
|
11
11
|
@tags = tags
|
12
12
|
end
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
def _call(env)
|
17
|
-
@env = env
|
14
|
+
def call!(env)
|
18
15
|
app.call(env)
|
19
16
|
rescue StandardError => e
|
20
|
-
notify(e) unless shark_error?(e)
|
17
|
+
notify(e, env) unless shark_error?(e) && client_error?(e)
|
21
18
|
|
22
19
|
raise e
|
23
20
|
end
|
24
21
|
|
25
|
-
|
22
|
+
private
|
23
|
+
|
24
|
+
def client_error?(error)
|
25
|
+
error.respond_to?(:status) && error.status < 500
|
26
|
+
end
|
27
|
+
|
28
|
+
def notify(error, env)
|
29
|
+
params = env.fetch('action_dispatch.request.parameters', {})
|
30
|
+
|
26
31
|
::Honeybadger.notify(
|
27
32
|
error,
|
28
33
|
tags: tags,
|
29
|
-
controller:
|
30
|
-
action:
|
31
|
-
parameters:
|
34
|
+
controller: params[:controller],
|
35
|
+
action: params[:action],
|
36
|
+
parameters: params
|
32
37
|
)
|
33
38
|
end
|
34
39
|
|