shark-on-lambda 0.6.10 → 1.0.0.rc1
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/.github/workflows/push-gem.yml +67 -0
- data/.github/workflows/tests.yml +32 -0
- data/README.md +10 -12
- data/changelog.md +11 -0
- data/gems.locked +73 -23
- data/lib/shark_on_lambda/api_gateway_handler.rb +61 -0
- data/lib/shark_on_lambda/application.rb +17 -0
- data/lib/shark_on_lambda/base_controller.rb +50 -0
- data/lib/shark_on_lambda/configuration.rb +9 -1
- data/lib/shark_on_lambda/dispatcher.rb +26 -0
- data/lib/shark_on_lambda/errors/base.rb +47 -0
- data/lib/shark_on_lambda/errors/base_serializer.rb +18 -0
- data/lib/shark_on_lambda/jsonapi_controller.rb +29 -0
- data/lib/shark_on_lambda/jsonapi_parameters.rb +66 -0
- data/lib/shark_on_lambda/jsonapi_renderer.rb +115 -0
- data/lib/shark_on_lambda/middleware/base.rb +23 -0
- data/lib/shark_on_lambda/middleware/jsonapi_rescuer.rb +31 -0
- data/lib/shark_on_lambda/middleware/rescuer.rb +38 -0
- data/lib/shark_on_lambda/query.rb +67 -0
- data/lib/shark_on_lambda/rack_adapters/api_gateway.rb +127 -0
- data/lib/shark_on_lambda/request.rb +9 -0
- data/lib/shark_on_lambda/response.rb +6 -0
- data/lib/shark_on_lambda/rspec/env_builder.rb +64 -0
- data/lib/shark_on_lambda/rspec/helpers.rb +84 -0
- data/lib/shark_on_lambda/rspec/jsonapi_helpers.rb +27 -0
- data/lib/shark_on_lambda/version.rb +1 -1
- data/lib/shark_on_lambda.rb +25 -28
- data/shark-on-lambda.gemspec +8 -3
- metadata +104 -24
- data/.gitlab-ci.yml +0 -39
- data/.travis.yml +0 -14
- data/lib/shark_on_lambda/api_gateway/base_controller.rb +0 -76
- data/lib/shark_on_lambda/api_gateway/base_handler.rb +0 -82
- data/lib/shark_on_lambda/api_gateway/concerns/http_response_validation.rb +0 -61
- data/lib/shark_on_lambda/api_gateway/errors.rb +0 -49
- data/lib/shark_on_lambda/api_gateway/headers.rb +0 -37
- data/lib/shark_on_lambda/api_gateway/jsonapi_controller.rb +0 -77
- data/lib/shark_on_lambda/api_gateway/jsonapi_parameters.rb +0 -68
- data/lib/shark_on_lambda/api_gateway/jsonapi_renderer.rb +0 -105
- data/lib/shark_on_lambda/api_gateway/parameters.rb +0 -18
- data/lib/shark_on_lambda/api_gateway/query.rb +0 -69
- data/lib/shark_on_lambda/api_gateway/request.rb +0 -148
- data/lib/shark_on_lambda/api_gateway/response.rb +0 -82
- data/lib/shark_on_lambda/api_gateway/serializers/base_error_serializer.rb +0 -20
- data/lib/shark_on_lambda/concerns/filter_actions.rb +0 -93
- data/lib/shark_on_lambda/tasks/build.rake +0 -146
- data/lib/shark_on_lambda/tasks.rb +0 -3
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SharkOnLambda
|
4
|
+
class JsonapiParameters
|
5
|
+
def initialize(params = {})
|
6
|
+
@class = default_serializer_classes
|
7
|
+
@fields = HashWithIndifferentAccess.new
|
8
|
+
@include = []
|
9
|
+
|
10
|
+
parse_params(params) if params.present?
|
11
|
+
end
|
12
|
+
|
13
|
+
def classes(serializer_classes = {})
|
14
|
+
@class = default_serializer_classes.merge(serializer_classes)
|
15
|
+
end
|
16
|
+
|
17
|
+
def fields(serialized_fields = {})
|
18
|
+
@fields = serialized_fields.with_indifferent_access
|
19
|
+
end
|
20
|
+
|
21
|
+
def includes(*includes_list)
|
22
|
+
@include = includes_list
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_h
|
26
|
+
{
|
27
|
+
class: @class,
|
28
|
+
fields: @fields,
|
29
|
+
include: @include
|
30
|
+
}
|
31
|
+
end
|
32
|
+
alias to_hash to_h
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def default_serializer_classes
|
37
|
+
HashWithIndifferentAccess.new do |hash, key|
|
38
|
+
serializer_service = Inferrers::SerializerInferrer.new(key)
|
39
|
+
serializer_class = serializer_service.serializer_class
|
40
|
+
hash[key] = serializer_class
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def parse_fields_params(fields_params)
|
45
|
+
return if fields_params.blank?
|
46
|
+
|
47
|
+
serialized_fields = fields_params.transform_values do |fields_param|
|
48
|
+
fields = fields_param.split(',')
|
49
|
+
fields.map!(&:strip)
|
50
|
+
fields.map!(&:to_sym)
|
51
|
+
end
|
52
|
+
fields(serialized_fields)
|
53
|
+
end
|
54
|
+
|
55
|
+
def parse_include_params(include_params)
|
56
|
+
include_params = JSONAPI::IncludeDirective.new(include_params)
|
57
|
+
include_params = include_params.to_hash
|
58
|
+
includes(include_params.with_indifferent_access)
|
59
|
+
end
|
60
|
+
|
61
|
+
def parse_params(params)
|
62
|
+
parse_fields_params(params[:fields])
|
63
|
+
parse_include_params(params[:include])
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SharkOnLambda
|
4
|
+
class JsonapiRenderer
|
5
|
+
def initialize(renderer: nil)
|
6
|
+
@renderer = renderer || JSONAPI::Serializable::Renderer.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def render(object, options = {})
|
10
|
+
object = transform_active_model_errors(object)
|
11
|
+
|
12
|
+
unless renderable?(object, options)
|
13
|
+
return handle_unrenderable_objects(object, options)
|
14
|
+
end
|
15
|
+
|
16
|
+
if error?(object)
|
17
|
+
render_errors(object, options).to_json
|
18
|
+
else
|
19
|
+
render_success(object, options).to_json
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
protected
|
24
|
+
|
25
|
+
attr_reader :renderer
|
26
|
+
|
27
|
+
def active_model_error?(error)
|
28
|
+
return false unless defined?(::ActiveModel::Errors)
|
29
|
+
|
30
|
+
error.is_a?(::ActiveModel::Errors)
|
31
|
+
end
|
32
|
+
|
33
|
+
def attribute_name(attribute)
|
34
|
+
File.basename(attribute_path(attribute))
|
35
|
+
end
|
36
|
+
|
37
|
+
def attribute_path(attribute)
|
38
|
+
attribute.to_s.tr('.', '/').gsub(/\[(\d+)\]/, '/\1')
|
39
|
+
end
|
40
|
+
|
41
|
+
def error?(object)
|
42
|
+
if object.respond_to?(:to_ary)
|
43
|
+
object.to_ary.any? { |item| item.is_a?(StandardError) }
|
44
|
+
else
|
45
|
+
object.is_a?(StandardError)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def handle_unrenderable_objects(object, options)
|
50
|
+
objects_without_serializer = unrenderable_objects(object, options)
|
51
|
+
classes_without_serializer = objects_without_serializer.map(&:class)
|
52
|
+
classes_without_serializer.uniq!
|
53
|
+
errors = classes_without_serializer.map do |item|
|
54
|
+
Errors[500].new("Could not find serializer for: #{item.name}.")
|
55
|
+
end
|
56
|
+
|
57
|
+
render_errors(errors, options)
|
58
|
+
end
|
59
|
+
|
60
|
+
def render_errors(error, options)
|
61
|
+
renderer.render_errors(Array(error), options)
|
62
|
+
end
|
63
|
+
|
64
|
+
def render_success(object, options)
|
65
|
+
return { data: {} } if object.nil?
|
66
|
+
|
67
|
+
renderer.render(object, options)
|
68
|
+
end
|
69
|
+
|
70
|
+
def renderable?(object, options)
|
71
|
+
return true if object.nil?
|
72
|
+
|
73
|
+
serializers = serializer_classes(options)
|
74
|
+
|
75
|
+
if object.respond_to?(:to_ary)
|
76
|
+
object.to_ary.all? { |item| serializers[item.class.name].present? }
|
77
|
+
else
|
78
|
+
serializers[object.class.name].present?
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def serializer_classes(options)
|
83
|
+
return @serializer_classes if defined?(@serializer_classes)
|
84
|
+
|
85
|
+
@serializer_classes = HashWithIndifferentAccess.new do |hash, key|
|
86
|
+
serializer_inferrer = Inferrers::SerializerInferrer.new(key)
|
87
|
+
serializer_class = serializer_inferrer.serializer_class
|
88
|
+
hash[key] = serializer_class
|
89
|
+
end
|
90
|
+
@serializer_classes.merge!(options[:class])
|
91
|
+
end
|
92
|
+
|
93
|
+
def transform_active_model_errors(errors)
|
94
|
+
return errors unless active_model_error?(errors)
|
95
|
+
|
96
|
+
result = errors.messages.map do |attribute, attribute_errors|
|
97
|
+
attribute_errors.map do |attribute_error|
|
98
|
+
error_message = "`#{attribute_name(attribute)}' #{attribute_error}"
|
99
|
+
Errors[422].new(error_message).tap do |error|
|
100
|
+
error.pointer = "/data/attributes/#{attribute_path(attribute)}"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
result.flatten! || result
|
105
|
+
end
|
106
|
+
|
107
|
+
def unrenderable_objects(object, options)
|
108
|
+
if object.respond_to?(:to_ary)
|
109
|
+
object.to_ary.reject { |item| renderable?(item, options) }
|
110
|
+
else
|
111
|
+
renderable?(object, options) ? [] : [object]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SharkOnLambda
|
4
|
+
module Middleware
|
5
|
+
class Base
|
6
|
+
attr_reader :app
|
7
|
+
|
8
|
+
def initialize(app = nil)
|
9
|
+
@app = app
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(env)
|
13
|
+
dup.send('_call', env)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def _call(_env)
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SharkOnLambda
|
4
|
+
module Middleware
|
5
|
+
class JsonapiRescuer < Rescuer
|
6
|
+
private
|
7
|
+
|
8
|
+
def error_object(status, message)
|
9
|
+
{
|
10
|
+
status: status.to_s,
|
11
|
+
title: Rack::Utils::HTTP_STATUS_CODES[status],
|
12
|
+
detail: message
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
def error_response(status, headers, message)
|
17
|
+
body = {
|
18
|
+
errors: [
|
19
|
+
error_object(status, message)
|
20
|
+
]
|
21
|
+
}.to_json
|
22
|
+
|
23
|
+
response_body = Rack::BodyProxy.new([body]) do
|
24
|
+
message.close if message.respond_to?(:close)
|
25
|
+
end
|
26
|
+
|
27
|
+
[status, headers, response_body]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SharkOnLambda
|
4
|
+
module Middleware
|
5
|
+
class Rescuer < Base
|
6
|
+
private
|
7
|
+
|
8
|
+
def _call(env)
|
9
|
+
app.call(env)
|
10
|
+
rescue Errors::Base => e
|
11
|
+
rescue_shark_error(e)
|
12
|
+
rescue StandardError => e
|
13
|
+
rescue_standard_error(e)
|
14
|
+
end
|
15
|
+
|
16
|
+
def error_response(status, headers, message)
|
17
|
+
response_body = Rack::BodyProxy.new([message]) do
|
18
|
+
message.close if message.respond_to?(:close)
|
19
|
+
end
|
20
|
+
|
21
|
+
[status, headers, response_body]
|
22
|
+
end
|
23
|
+
|
24
|
+
def rescue_shark_error(error)
|
25
|
+
status = error.status || 500
|
26
|
+
error_response(status, {}, error.message)
|
27
|
+
end
|
28
|
+
|
29
|
+
def rescue_standard_error(error)
|
30
|
+
SharkOnLambda.logger.error(error.message)
|
31
|
+
SharkOnLambda.logger.error(error.backtrace.join("\n"))
|
32
|
+
Honeybadger.notify(error) if defined?(Honeybadger)
|
33
|
+
|
34
|
+
error_response(500, {}, error.message)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SharkOnLambda
|
4
|
+
# The `multiValueQueryStringParameters` object from the API Gateway event
|
5
|
+
# keeps _all_ values in an array, regardless of the actual size of the array
|
6
|
+
# and regardless of the "intent" of the query string parameter.
|
7
|
+
#
|
8
|
+
# In order to normalise this behaviour, we treat the query strings
|
9
|
+
#
|
10
|
+
# `key=value1&key=value2`
|
11
|
+
#
|
12
|
+
# and
|
13
|
+
#
|
14
|
+
# `key[]=value1&key[]=value2`
|
15
|
+
#
|
16
|
+
# the same. Both are to be serialised to the query string
|
17
|
+
#
|
18
|
+
# `key[]=value1&key[]=value2`
|
19
|
+
#
|
20
|
+
# However, the query strings
|
21
|
+
#
|
22
|
+
# `key=value`
|
23
|
+
#
|
24
|
+
# and
|
25
|
+
#
|
26
|
+
# `key[]=value`
|
27
|
+
#
|
28
|
+
# are _not_ to be treated the same.
|
29
|
+
class Query
|
30
|
+
def initialize(data = {})
|
31
|
+
@params = HashWithIndifferentAccess.new.merge(data)
|
32
|
+
end
|
33
|
+
|
34
|
+
def add(key, values)
|
35
|
+
if key.to_s.end_with?('[]')
|
36
|
+
actual_key = key[0..-3]
|
37
|
+
add_list(actual_key, values)
|
38
|
+
else
|
39
|
+
values.each { |value| add_item(key, value) }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_h
|
44
|
+
@params
|
45
|
+
end
|
46
|
+
|
47
|
+
def to_s
|
48
|
+
Rack::Utils.build_nested_query(to_h)
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def add_item(key, value)
|
54
|
+
if @params[key].nil?
|
55
|
+
@params[key] = value
|
56
|
+
else
|
57
|
+
@params[key] = Array(@params[key])
|
58
|
+
@params[key] << value
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def add_list(key, value)
|
63
|
+
@params[key] ||= []
|
64
|
+
@params[key].concat(value)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SharkOnLambda
|
4
|
+
module RackAdapters
|
5
|
+
class ApiGateway
|
6
|
+
attr_reader :context, :event
|
7
|
+
|
8
|
+
def initialize(context:, event:)
|
9
|
+
@context = context
|
10
|
+
@event = event.deep_stringify_keys
|
11
|
+
end
|
12
|
+
|
13
|
+
def build_response(status, headers, body)
|
14
|
+
body_content = ''
|
15
|
+
body.each { |line| body_content += line.to_s }
|
16
|
+
response = {
|
17
|
+
'statusCode' => status,
|
18
|
+
'headers' => headers,
|
19
|
+
'body' => body_content
|
20
|
+
}
|
21
|
+
response['isBase64Encoded'] = false if elb?
|
22
|
+
response
|
23
|
+
end
|
24
|
+
|
25
|
+
def env
|
26
|
+
default_env.merge(env_request_metadata)
|
27
|
+
.merge(env_headers)
|
28
|
+
.merge(env_params)
|
29
|
+
.merge(env_body)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def default_env
|
35
|
+
{
|
36
|
+
'SCRIPT_NAME' => '',
|
37
|
+
'rack.version' => Rack::VERSION,
|
38
|
+
'rack.errors' => $stderr,
|
39
|
+
'rack.multithread' => true,
|
40
|
+
'rack.multiprocess' => true,
|
41
|
+
'rack.run_once' => false
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def elb?
|
46
|
+
return false if event['requestContext'].nil?
|
47
|
+
|
48
|
+
event['requestContext'].key?('elb')
|
49
|
+
end
|
50
|
+
|
51
|
+
def env_body
|
52
|
+
result = event['body'] || ''
|
53
|
+
result = Base64.decode64(result) if event['isBase64Encoded']
|
54
|
+
|
55
|
+
{
|
56
|
+
'rack.input' => StringIO.new(result).set_encoding(Encoding::BINARY)
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
def env_headers
|
61
|
+
result = {}
|
62
|
+
http_headers.each_pair do |header, value|
|
63
|
+
key = key_for_header(header)
|
64
|
+
result[key] = value.to_s
|
65
|
+
end
|
66
|
+
result
|
67
|
+
end
|
68
|
+
|
69
|
+
def env_params
|
70
|
+
{
|
71
|
+
'QUERY_STRING' => query_string,
|
72
|
+
'shark.path_parameters' => event['pathParameters']
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
def env_request_metadata
|
77
|
+
{
|
78
|
+
'REQUEST_METHOD' => event['httpMethod'],
|
79
|
+
'PATH_INFO' => path_info,
|
80
|
+
'SERVER_NAME' => server_name,
|
81
|
+
'SERVER_PORT' => server_port.to_s,
|
82
|
+
'rack.url_scheme' => url_scheme
|
83
|
+
}
|
84
|
+
end
|
85
|
+
|
86
|
+
def http_headers
|
87
|
+
event['headers'] || {}
|
88
|
+
end
|
89
|
+
|
90
|
+
def key_for_header(header)
|
91
|
+
key = header.upcase.tr('-', '_')
|
92
|
+
case key
|
93
|
+
when 'CONTENT_LENGTH', 'CONTENT_TYPE' then key
|
94
|
+
else "HTTP_#{key}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def path_info
|
99
|
+
event['path'] || ''
|
100
|
+
end
|
101
|
+
|
102
|
+
def query_string
|
103
|
+
return @query_string if defined?(@query_string)
|
104
|
+
|
105
|
+
query = Query.new
|
106
|
+
event['multiValueQueryStringParameters']&.each_pair do |key, value|
|
107
|
+
query.add(key, value)
|
108
|
+
end
|
109
|
+
@query_string = query.to_s
|
110
|
+
end
|
111
|
+
|
112
|
+
def server_name
|
113
|
+
http_headers['Host'] || 'localhost'
|
114
|
+
end
|
115
|
+
|
116
|
+
def server_port
|
117
|
+
http_headers['X-Forwarded-Port'] || 443
|
118
|
+
end
|
119
|
+
|
120
|
+
def url_scheme
|
121
|
+
http_headers['CloudFront-Forwarded-Proto'] ||
|
122
|
+
http_headers['X-Forwarded-Proto'] ||
|
123
|
+
'https'
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SharkOnLambda
|
4
|
+
module RSpec
|
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
|
+
@params = options[:params]
|
15
|
+
|
16
|
+
initialize_env
|
17
|
+
add_headers
|
18
|
+
add_request_body
|
19
|
+
end
|
20
|
+
|
21
|
+
def build
|
22
|
+
env.deep_stringify_keys
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
attr_reader :env
|
28
|
+
|
29
|
+
def add_header(name, value)
|
30
|
+
name = name.upcase.tr('-', '_')
|
31
|
+
key = case name
|
32
|
+
when 'CONTENT_LENGTH', 'CONTENT_TYPE' then name
|
33
|
+
else "HTTP_#{name}"
|
34
|
+
end
|
35
|
+
@env[key] = value.to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_headers
|
39
|
+
headers.each_pair { |name, value| add_header(name, value) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def add_request_body
|
43
|
+
return if %w[GET HEAD OPTIONS].include?(env['REQUEST_METHOD'])
|
44
|
+
return unless params.is_a?(Hash)
|
45
|
+
|
46
|
+
body = params.to_json
|
47
|
+
|
48
|
+
env['rack.input'] = StringIO.new(body).set_encoding(Encoding::BINARY)
|
49
|
+
env['CONTENT_TYPE'] = 'application/json'
|
50
|
+
env['CONTENT_LENGTH'] = body.bytesize.to_s
|
51
|
+
end
|
52
|
+
|
53
|
+
def initialize_env
|
54
|
+
@env = Rack::MockRequest.env_for(
|
55
|
+
'https://localhost:9292',
|
56
|
+
method: method,
|
57
|
+
params: params,
|
58
|
+
'shark.controller' => controller,
|
59
|
+
'shark.action' => action
|
60
|
+
)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SharkOnLambda
|
4
|
+
module RSpec
|
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 dispatch_request(env, skip_middleware: false)
|
58
|
+
return SharkOnLambda.application.call(env) unless skip_middleware
|
59
|
+
|
60
|
+
controller_class = env['shark.controller'].constantize
|
61
|
+
action = env['shark.action']
|
62
|
+
|
63
|
+
request = Request.new(env)
|
64
|
+
response = Response.new
|
65
|
+
controller_class.dispatch(action, request, response)
|
66
|
+
response.prepare!
|
67
|
+
end
|
68
|
+
|
69
|
+
def make_request(method, action, options = {})
|
70
|
+
raise ArgumentError, 'Cannot find controller name.' unless controller?
|
71
|
+
|
72
|
+
options = options.with_indifferent_access
|
73
|
+
env = build_env(method, action, options)
|
74
|
+
|
75
|
+
status, headers, body = dispatch_request(
|
76
|
+
env,
|
77
|
+
skip_middleware: options[:skip_middleware]
|
78
|
+
)
|
79
|
+
errors = env['rack.errors']
|
80
|
+
@response = Rack::MockResponse.new(status, headers, body, errors)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SharkOnLambda
|
4
|
+
module RSpec
|
5
|
+
module JsonapiHelpers
|
6
|
+
include Helpers
|
7
|
+
|
8
|
+
def jsonapi_attributes
|
9
|
+
jsonapi_data[:attributes] || {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def jsonapi_data
|
13
|
+
parsed_body[:data] || {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def jsonapi_errors
|
17
|
+
parsed_body[:errors] || []
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def parsed_body
|
23
|
+
@parsed_body ||= JSON.parse(response.body).with_indifferent_access
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|