Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POC - DO NOT REVIEW - Interceptor sandbox #136

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions hearth/lib/hearth/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ def initialize(options = {})
@params = options[:params]
@signer_params = options[:signer_params] || {}
@metadata = options[:metadata] || {}
@interceptors = options[:interceptors] || []
@interceptor_attributes = {}
end

# @return [Symbol] Name of the API operation called.
Expand All @@ -34,5 +36,18 @@ def initialize(options = {})

# @return [Hash]
attr_reader :metadata

# @return [Array] An ordered list of interceptors
attr_reader :interceptors

def interceptor_context(input, output)
Hearth::Interceptor::Context.new(
input: input,
request: request,
response: response,
output: output,
attributes: @interceptor_attributes
)
end
end
end
35 changes: 35 additions & 0 deletions hearth/lib/hearth/interceptor/hook.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

module Hearth
module Interceptor
# A module mixed into Middleware classes to provide methods
# for calling interceptor hooks
module Hook

# if an exception is thrown AND output is not nil, the output will be modified to include the error
# @return nil if successful, an exception otherwise
def interceptor_hook(hook, input, context, output, aggregate_errors: false)
ictx = context.interceptor_context(input, output)
last_error = nil
context.interceptors.each do |i|
next unless i.respond_to?(hook)

begin
i.send(hook, ictx)
rescue StandardError => e
context.logger.error(e) if last_error
last_error = e
break unless aggregate_errors
end
end

if last_error && output
output.data = nil
output.error = last_error
end

return last_error
end
end
end
end
3 changes: 3 additions & 0 deletions hearth/lib/hearth/middleware.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
require_relative 'middleware/retry'
require_relative 'middleware/send'
require_relative 'middleware/validate'
require_relative 'middleware/initialize'
require_relative 'interceptor/context'
require_relative 'interceptor/hook'

module Hearth
# @api private
Expand Down
29 changes: 28 additions & 1 deletion hearth/lib/hearth/middleware/build.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,42 @@ class Build
# @param [Class] builder A builder object responsible for building the
# request. It must respond to #build and take the request and input as
# arguments.
def initialize(app, builder:)
def initialize(app, builder:, interceptors:)
@app = app
@builder = builder
@interceptors = interceptors
end

# @param input
# @param context
# @return [Output]
def call(input, context)
# modify_before_serialization hook
# exception behavior - exceptions set to output.error and control
# bubbles up to modifyBeforeCompletion
@interceptors.reverse.each do |i|
if i.respond_to?(:modify_before_serialization)
begin
input = i.modify_before_serialization(context.interceptor_context(input, nil))
rescue StandardError => e
return Hearth::Output.new(error: e)
end
end
end

# read_before_serialization hook
# exception behavior - exceptions set to output.error and control
# bubbles up to modifyBeforeCompletion
@interceptors.reverse.each do |i|
if i.respond_to?(:modify_before_serialization)
begin
i.read_before_serialization(context.interceptor_context(input, nil))
rescue StandardError => e
return Hearth::Output.new(error: e)
end
end
end

@builder.build(context.request, input: input)
@app.call(input, context)
end
Expand Down
44 changes: 43 additions & 1 deletion hearth/lib/hearth/middleware/parse.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,61 @@ class Parse
# @param [Class] data_parser A parser object responsible for parsing the
# response if there is data. It must respond to #parse and take the
# response as an argument.
def initialize(app, error_parser:, data_parser:)
def initialize(app, error_parser:, data_parser:, interceptors:)
@app = app
@error_parser = error_parser
@data_parser = data_parser
@interceptors = interceptors
end

# @param input
# @param context
# @return [Output]
def call(input, context)
output = @app.call(input, context)

# modify_before_deserialization hook
# exception behavior - exceptions set to output.error and control
# bubbles up to modifyBeforeAttemptCompletion
@interceptors.reverse.each do |i|
if i.respond_to?(:modify_before_deserialization)
begin
context.request = i.modify_before_deserialization(context.interceptor_context(input, output))
rescue StandardError => e
return Hearth::Output.new(error: e)
end
end
end

# read_before_deserialization hook
# exception behavior - exceptions set to output.error and control
# bubbles up to modifyBeforeAttemptCompletion
@interceptors.reverse.each do |i|
if i.respond_to?(:read_before_deserialization)
begin
i.read_before_deserialization(context.interceptor_context(input, output))
rescue StandardError => e
return Hearth::Output.new(error: e)
end
end
end

parse_error(context, output) unless output.error
parse_data(context, output) unless output.error

# read_after_deserialization hook
# exception behavior - exceptions set to output.error and control
# bubbles up to modifyBeforeAttemptCompletion
@interceptors.reverse.each do |i|
if i.respond_to?(:read_after_deserialization)
begin
i.read_after_deserialization(context.interceptor_context(input, output))
rescue StandardError => e
return Hearth::Output.new(error: e)
end
end
end

output
end

Expand Down
2 changes: 2 additions & 0 deletions hearth/lib/hearth/middleware/send.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def initialize(_app, client:, stub_responses:,
# @param context
# @return [Output]
def call(input, context)
# TODO: Add modify and read_before_transmit hooks
output = Output.new
if @stub_responses
stub = @stubs.next(context.operation_name)
Expand All @@ -46,6 +47,7 @@ def call(input, context)
output.error = resp_or_error
end
end
# TODO: Add read after transmit hook
output
end

Expand Down
Loading