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
@@ -1,61 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SharkOnLambda
4
- class ApiGatewayHandler
5
- class << self
6
- attr_writer :controller_class_name
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.send('_call', env)
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 _call(env)
12
- dispatcher = SharkOnLambda.config.dispatcher
13
- middleware_stack = SharkOnLambda.config.middleware.build(dispatcher)
14
- middleware_stack.call(env)
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::Base.without_modules(EXCLUDED_MODULES).each do |mod|
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
- self.response_body = '' if no_body?
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
- include Concerns::ResettableSingleton
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 = object_class.ancestors.map do |ancestor|
35
- ancestor_name = ancestor.name
36
- next if ancestor_name.blank?
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
- name_inferrer = NameInferrer.from_model_name(ancestor_name)
39
- name_inferrer.serializer
40
- end
41
- @serializer_class_names.compact! || @serializer_class_names
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
- def initialize(renderer: nil)
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(object, options = {})
10
- object = transform_active_model_errors(object)
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?(object, options)
13
- return handle_unrenderable_objects(object, options)
18
+ unless renderable?(object_to_render, options)
19
+ return handle_unrenderable_objects(object_to_render, options)
14
20
  end
15
21
 
16
- if error?(object)
17
- render_errors(object, options).to_json
22
+ if error?(object_to_render)
23
+ render_errors(object_to_render, options)
18
24
  else
19
- render_success(object, options).to_json
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
- result = errors.messages.map do |attribute, attribute_errors|
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)
@@ -10,12 +10,10 @@ module SharkOnLambda
10
10
  end
11
11
 
12
12
  def call(env)
13
- dup.send('_call', env)
13
+ dup.call!(env)
14
14
  end
15
15
 
16
- private
17
-
18
- def _call(_env)
16
+ def call!(_env)
19
17
  raise NotImplementedError
20
18
  end
21
19
  end
@@ -11,24 +11,29 @@ module SharkOnLambda
11
11
  @tags = tags
12
12
  end
13
13
 
14
- private
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
- def notify(error)
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: @env['shark.controller'],
30
- action: @env['shark.action'],
31
- parameters: @env['action_dispatch.request.parameters']
34
+ controller: params[:controller],
35
+ action: params[:action],
36
+ parameters: params
32
37
  )
33
38
  end
34
39