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.
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