shark-on-lambda 0.6.10 → 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|