hearth 1.0.0.pre1
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 +7 -0
- data/CHANGELOG.md +12 -0
- data/VERSION +1 -0
- data/lib/hearth/api_error.rb +15 -0
- data/lib/hearth/block_io.rb +24 -0
- data/lib/hearth/context.rb +34 -0
- data/lib/hearth/http/api_error.rb +29 -0
- data/lib/hearth/http/client.rb +152 -0
- data/lib/hearth/http/error_parser.rb +105 -0
- data/lib/hearth/http/headers.rb +70 -0
- data/lib/hearth/http/middleware/content_length.rb +29 -0
- data/lib/hearth/http/networking_error.rb +20 -0
- data/lib/hearth/http/request.rb +132 -0
- data/lib/hearth/http/response.rb +29 -0
- data/lib/hearth/http.rb +36 -0
- data/lib/hearth/json/parse_error.rb +18 -0
- data/lib/hearth/json.rb +30 -0
- data/lib/hearth/middleware/around_handler.rb +24 -0
- data/lib/hearth/middleware/build.rb +26 -0
- data/lib/hearth/middleware/host_prefix.rb +48 -0
- data/lib/hearth/middleware/parse.rb +42 -0
- data/lib/hearth/middleware/request_handler.rb +24 -0
- data/lib/hearth/middleware/response_handler.rb +25 -0
- data/lib/hearth/middleware/retry.rb +43 -0
- data/lib/hearth/middleware/send.rb +62 -0
- data/lib/hearth/middleware/validate.rb +29 -0
- data/lib/hearth/middleware.rb +16 -0
- data/lib/hearth/middleware_builder.rb +246 -0
- data/lib/hearth/middleware_stack.rb +73 -0
- data/lib/hearth/number_helper.rb +33 -0
- data/lib/hearth/output.rb +20 -0
- data/lib/hearth/structure.rb +40 -0
- data/lib/hearth/stubbing/client_stubs.rb +115 -0
- data/lib/hearth/stubbing/stubs.rb +32 -0
- data/lib/hearth/time_helper.rb +35 -0
- data/lib/hearth/union.rb +10 -0
- data/lib/hearth/validator.rb +20 -0
- data/lib/hearth/waiters/errors.rb +15 -0
- data/lib/hearth/waiters/poller.rb +132 -0
- data/lib/hearth/waiters/waiter.rb +79 -0
- data/lib/hearth/xml/formatter.rb +68 -0
- data/lib/hearth/xml/node.rb +123 -0
- data/lib/hearth/xml/node_matcher.rb +24 -0
- data/lib/hearth/xml/parse_error.rb +18 -0
- data/lib/hearth/xml.rb +58 -0
- data/lib/hearth.rb +26 -0
- data/sig/lib/seahorse/api_error.rbs +10 -0
- data/sig/lib/seahorse/document.rbs +2 -0
- data/sig/lib/seahorse/http/api_error.rbs +21 -0
- data/sig/lib/seahorse/http/headers.rbs +47 -0
- data/sig/lib/seahorse/http/response.rbs +21 -0
- data/sig/lib/seahorse/simple_delegator.rbs +3 -0
- data/sig/lib/seahorse/structure.rbs +18 -0
- data/sig/lib/seahorse/stubbing/client_stubs.rbs +103 -0
- data/sig/lib/seahorse/stubbing/stubs.rbs +14 -0
- data/sig/lib/seahorse/union.rbs +6 -0
- metadata +111 -0
data/lib/hearth/http.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cgi'
|
4
|
+
require_relative 'http/api_error'
|
5
|
+
require_relative 'http/client'
|
6
|
+
require_relative 'http/error_parser'
|
7
|
+
require_relative 'http/headers'
|
8
|
+
require_relative 'http/middleware/content_length'
|
9
|
+
require_relative 'http/networking_error'
|
10
|
+
require_relative 'http/request'
|
11
|
+
require_relative 'http/response'
|
12
|
+
|
13
|
+
module Hearth
|
14
|
+
# HTTP namespace for HTTP specific functionality. Also includes utility
|
15
|
+
# methods for URI escaping.
|
16
|
+
# @api private
|
17
|
+
module HTTP
|
18
|
+
# TODO: - do these belong here?
|
19
|
+
class << self
|
20
|
+
# URI escapes the given value.
|
21
|
+
#
|
22
|
+
# Hearth::_escape("a b/c")
|
23
|
+
# #=> "a%20b%2Fc"
|
24
|
+
#
|
25
|
+
# @param [String] value
|
26
|
+
# @return [String] URI encoded value except for '+' and '~'.
|
27
|
+
def uri_escape(value)
|
28
|
+
CGI.escape(value.encode('UTF-8')).gsub('+', '%20').gsub('%7E', '~')
|
29
|
+
end
|
30
|
+
|
31
|
+
def uri_escape_path(path)
|
32
|
+
path.gsub(%r{[^/]+}) { |part| uri_escape(part) }
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
module JSON
|
5
|
+
# An error class encountered when parsing JSON.
|
6
|
+
class ParseError < StandardError
|
7
|
+
MSG = 'Encountered an error while parsing the response: %<message>s'
|
8
|
+
|
9
|
+
def initialize(original_error)
|
10
|
+
@original_error = original_error
|
11
|
+
super(format(MSG, message: original_error.message))
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [StandardError]
|
15
|
+
attr_reader :original_error
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/hearth/json.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'json/parse_error'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module Hearth
|
7
|
+
# Hearth::JSON is a purpose-built set of utilities for working with
|
8
|
+
# JSON. It does not support many/most features of generic JSON
|
9
|
+
# parsing and serialization.
|
10
|
+
# @api private
|
11
|
+
module JSON
|
12
|
+
class << self
|
13
|
+
# @param [String] json
|
14
|
+
# @return [Hash]
|
15
|
+
def load(json)
|
16
|
+
# rubocop:disable Security/JSONLoad
|
17
|
+
::JSON.load(json)
|
18
|
+
# rubocop:enable Security/JSONLoad
|
19
|
+
rescue ::JSON::ParserError => e
|
20
|
+
raise ParseError, e
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param [Hash] value
|
24
|
+
# @return [String] json
|
25
|
+
def dump(value)
|
26
|
+
::JSON.dump(value)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
module Middleware
|
5
|
+
# A class used to register middleware around a request.
|
6
|
+
# @api private
|
7
|
+
class AroundHandler
|
8
|
+
# @param [Class] app The next middleware in the stack.
|
9
|
+
# @param [Proc] handler A proc object that is called around the request.
|
10
|
+
# The proc must return the next middleware.
|
11
|
+
def initialize(app, handler:)
|
12
|
+
@app = app
|
13
|
+
@handler = handler
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param input
|
17
|
+
# @param context
|
18
|
+
# @return [Output]
|
19
|
+
def call(input, context)
|
20
|
+
@handler.call(@app, input, context)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
module Middleware
|
5
|
+
# A middleware that builds a request object.
|
6
|
+
# @api private
|
7
|
+
class Build
|
8
|
+
# @param [Class] app The next middleware in the stack.
|
9
|
+
# @param [Class] builder A builder object responsible for building the
|
10
|
+
# request. It must respond to #build and take the request and input as
|
11
|
+
# arguments.
|
12
|
+
def initialize(app, builder:)
|
13
|
+
@app = app
|
14
|
+
@builder = builder
|
15
|
+
end
|
16
|
+
|
17
|
+
# @param input
|
18
|
+
# @param context
|
19
|
+
# @return [Output]
|
20
|
+
def call(input, context)
|
21
|
+
@builder.build(context.request, input: input)
|
22
|
+
@app.call(input, context)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
module Middleware
|
5
|
+
# A middleware that prefixes the host.
|
6
|
+
# @api private
|
7
|
+
class HostPrefix
|
8
|
+
# @param [Class] app The next middleware in the stack.
|
9
|
+
# @param [Boolean] disable_host_prefix If true, this option will not
|
10
|
+
# modify the host url.
|
11
|
+
# @param [String] host_prefix The prefix for the host. It may contain
|
12
|
+
# labels populated by input (i.e. \\{foo.} would be populated by
|
13
|
+
# input[:foo]).
|
14
|
+
def initialize(app, disable_host_prefix:, host_prefix:)
|
15
|
+
@app = app
|
16
|
+
@disable_host_prefix = disable_host_prefix
|
17
|
+
@host_prefix = host_prefix
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param input
|
21
|
+
# @param context
|
22
|
+
# @return [Output]
|
23
|
+
def call(input, context)
|
24
|
+
unless @disable_host_prefix
|
25
|
+
prefix = apply_labels(@host_prefix, input)
|
26
|
+
context.request.prefix_host(prefix)
|
27
|
+
end
|
28
|
+
@app.call(input, context)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def apply_labels(host_prefix, input)
|
34
|
+
host_prefix.gsub(/\{.+?\}/) do |host_label|
|
35
|
+
key = host_label.delete('{}')
|
36
|
+
value = input[key.to_sym]
|
37
|
+
|
38
|
+
if value.nil? || value.empty?
|
39
|
+
raise ArgumentError,
|
40
|
+
"Host label #{key} cannot be nil or empty."
|
41
|
+
end
|
42
|
+
|
43
|
+
value
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
module Middleware
|
5
|
+
# A middleware that parses a response object.
|
6
|
+
# @api private
|
7
|
+
class Parse
|
8
|
+
# @param [Class] app The next middleware in the stack.
|
9
|
+
# @param [Class] error_parser A parser object responsible for parsing the
|
10
|
+
# response if there is an error. It must respond to #parse and take the
|
11
|
+
# response as an argument.
|
12
|
+
# @param [Class] data_parser A parser object responsible for parsing the
|
13
|
+
# response if there is data. It must respond to #parse and take the
|
14
|
+
# response as an argument.
|
15
|
+
def initialize(app, error_parser:, data_parser:)
|
16
|
+
@app = app
|
17
|
+
@error_parser = error_parser
|
18
|
+
@data_parser = data_parser
|
19
|
+
end
|
20
|
+
|
21
|
+
# @param input
|
22
|
+
# @param context
|
23
|
+
# @return [Output]
|
24
|
+
def call(input, context)
|
25
|
+
output = @app.call(input, context)
|
26
|
+
parse_error(context.response, output) unless output.error
|
27
|
+
parse_data(context.response, output) unless output.error
|
28
|
+
output
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def parse_error(response, output)
|
34
|
+
output.error = @error_parser.parse(response)
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse_data(response, output)
|
38
|
+
output.data = @data_parser.parse(response)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
module Middleware
|
5
|
+
# A class used to register middleware before a request is sent.
|
6
|
+
# @api private
|
7
|
+
class RequestHandler
|
8
|
+
# @param [Class] app The next middleware in the stack.
|
9
|
+
# @param [Proc] handler A proc object that is called before the request.
|
10
|
+
def initialize(app, handler:)
|
11
|
+
@app = app
|
12
|
+
@handler = handler
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param input
|
16
|
+
# @param context
|
17
|
+
# @return [Output]
|
18
|
+
def call(input, context)
|
19
|
+
@handler.call(input, context)
|
20
|
+
@app.call(input, context)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
module Middleware
|
5
|
+
# A class used to register middleware after a request is sent.
|
6
|
+
# @api private
|
7
|
+
class ResponseHandler
|
8
|
+
# @param [Class] app The next middleware in the stack.
|
9
|
+
# @param [Proc] handler A proc object that is called after the request.
|
10
|
+
def initialize(app, handler:)
|
11
|
+
@app = app
|
12
|
+
@handler = handler
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param input
|
16
|
+
# @param context
|
17
|
+
# @return [Output]
|
18
|
+
def call(input, context)
|
19
|
+
output = @app.call(input, context)
|
20
|
+
@handler.call(output, context)
|
21
|
+
output
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
module Middleware
|
5
|
+
# A middleware that retries the request.
|
6
|
+
# @api private
|
7
|
+
class Retry
|
8
|
+
# @param [Class] app The next middleware in the stack.
|
9
|
+
# @param [Integer] max_attempts The maximum number of attempts to make
|
10
|
+
# before giving up.
|
11
|
+
# @param [Integer] max_delay The maximum delay between attempts.
|
12
|
+
def initialize(app, max_attempts:, max_delay:)
|
13
|
+
@app = app
|
14
|
+
@max_attempts = max_attempts
|
15
|
+
@max_delay = max_delay
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param input
|
19
|
+
# @param context
|
20
|
+
# @return [Output]
|
21
|
+
def call(input, context)
|
22
|
+
attempt = 1
|
23
|
+
begin
|
24
|
+
@app.call(input, context)
|
25
|
+
rescue Hearth::HTTP::NetworkingError => e
|
26
|
+
raise e if attempt >= @max_attempts
|
27
|
+
|
28
|
+
Kernel.sleep(backoff_with_jitter(attempt))
|
29
|
+
attempt += 1
|
30
|
+
retry
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
# https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
|
37
|
+
def backoff_with_jitter(attempt)
|
38
|
+
# scales like 1,2,4,8
|
39
|
+
Kernel.rand * [@max_delay, 2**(attempt - 1)].min
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
module Middleware
|
5
|
+
# A middleware used to send the request.
|
6
|
+
# @api private
|
7
|
+
class Send
|
8
|
+
# @param [Class] _app The next middleware in the stack.
|
9
|
+
# @param [Boolean] stub_responses If true, a request is not sent and a
|
10
|
+
# stubbed response is returned.
|
11
|
+
# @param [Class] stub_class A stub object that is responsible for creating
|
12
|
+
# a stubbed response. It must respond to #stub and take the response
|
13
|
+
# and stub data as arguments.
|
14
|
+
# @param [Stubs] stubs A {Hearth::Stubbing:Stubs} object containing
|
15
|
+
# stubbed data for any given operation.
|
16
|
+
def initialize(_app, client:, stub_responses:, stub_class:, stubs:)
|
17
|
+
@client = client
|
18
|
+
@stub_responses = stub_responses
|
19
|
+
@stub_class = stub_class
|
20
|
+
@stubs = stubs
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param _input
|
24
|
+
# @param context
|
25
|
+
# @return [Output]
|
26
|
+
def call(_input, context)
|
27
|
+
if @stub_responses
|
28
|
+
stub = @stubs.next(context.operation_name)
|
29
|
+
output = Output.new
|
30
|
+
apply_stub(stub, context, output)
|
31
|
+
output
|
32
|
+
else
|
33
|
+
@client.transmit(
|
34
|
+
request: context.request,
|
35
|
+
response: context.response
|
36
|
+
)
|
37
|
+
Output.new
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def apply_stub(stub, context, output)
|
44
|
+
case stub
|
45
|
+
when Proc
|
46
|
+
stub = stub.call(context)
|
47
|
+
apply_stub(stub, context, output) if stub
|
48
|
+
when Exception
|
49
|
+
output.error = stub
|
50
|
+
when Class
|
51
|
+
output.error = stub.new
|
52
|
+
when Hash
|
53
|
+
@stub_class.stub(context.response, stub: stub)
|
54
|
+
when NilClass
|
55
|
+
@stub_class.stub(context.response, stub: @stub_class.default)
|
56
|
+
else
|
57
|
+
raise ArgumentError, 'Unsupported stub type'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
module Middleware
|
5
|
+
# A middleware used to validate input.
|
6
|
+
# @api private
|
7
|
+
class Validate
|
8
|
+
# @param [Class] app The next middleware in the stack.
|
9
|
+
# @param [Boolean] validate_input If true, the input is validated against
|
10
|
+
# the model and an error is raised for unexpected types.
|
11
|
+
# @param [Class] validator A validator object responsible for validating
|
12
|
+
# the input. It must respond to #validate! and take input and a context
|
13
|
+
# as arguments.
|
14
|
+
def initialize(app, validate_input:, validator:)
|
15
|
+
@app = app
|
16
|
+
@validate_input = validate_input
|
17
|
+
@validator = validator
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param input
|
21
|
+
# @param context
|
22
|
+
# @return [Output]
|
23
|
+
def call(input, context)
|
24
|
+
@validator.validate!(input, context: 'input') if @validate_input
|
25
|
+
@app.call(input, context)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'middleware/around_handler'
|
4
|
+
require_relative 'middleware/build'
|
5
|
+
require_relative 'middleware/host_prefix'
|
6
|
+
require_relative 'middleware/parse'
|
7
|
+
require_relative 'middleware/request_handler'
|
8
|
+
require_relative 'middleware/response_handler'
|
9
|
+
require_relative 'middleware/retry'
|
10
|
+
require_relative 'middleware/send'
|
11
|
+
require_relative 'middleware/validate'
|
12
|
+
|
13
|
+
module Hearth
|
14
|
+
# @api private
|
15
|
+
module Middleware; end
|
16
|
+
end
|
@@ -0,0 +1,246 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
# A utility class for registering middleware for a request.
|
5
|
+
# You register middleware handlers to execute relative to
|
6
|
+
# Middleware classes. You can register middleware
|
7
|
+
# before/after/around any middleware class in the stack.
|
8
|
+
# You may also remove middleware from the stack.
|
9
|
+
# There are also convenience methods
|
10
|
+
# (eg: before_build, after_parse, ect)
|
11
|
+
# defined for all of the key request lifecycle events:
|
12
|
+
#
|
13
|
+
# * validate
|
14
|
+
# * host_prefix
|
15
|
+
# * build
|
16
|
+
# * send
|
17
|
+
# * parse
|
18
|
+
# * retry
|
19
|
+
#
|
20
|
+
# You can register request handlers that invoke before, after,
|
21
|
+
# or around each lifecycle events. These handlers are
|
22
|
+
# request, response, or around handlers.
|
23
|
+
#
|
24
|
+
# All before/after/around methods are defined on the class
|
25
|
+
# and instance and are chainable:
|
26
|
+
#
|
27
|
+
# MiddlewareBuilder.before_send(my_request_handler)
|
28
|
+
# .after_parse(my_response_handler)
|
29
|
+
#
|
30
|
+
# ## Request Handlers
|
31
|
+
#
|
32
|
+
# A request handler is invoked before the request is sent.
|
33
|
+
#
|
34
|
+
# # invoked after a request has been built, but before it has
|
35
|
+
# # been signed/authorized
|
36
|
+
# middleware.before_build do |input, context|
|
37
|
+
# # use input, inspect or modify the request
|
38
|
+
# # context.request
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# ## Response Handlers
|
42
|
+
#
|
43
|
+
# A response handler is invoked after the HTTP request has been sent
|
44
|
+
# and a HTTP response has been received.
|
45
|
+
#
|
46
|
+
# # invoked after the HTTP response has been parsed
|
47
|
+
# middleware.after_parse do |output, context|
|
48
|
+
# # inspect or modify the output or context
|
49
|
+
# # output.data
|
50
|
+
# # output.error
|
51
|
+
# # context.response
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# ## Around Handlers
|
55
|
+
#
|
56
|
+
# Around handlers see a request before it has been sent along
|
57
|
+
# with the response returned. Around handlers must invoke `#call`
|
58
|
+
# method of the next middleware in the stack. Around handlers
|
59
|
+
# must also return the response returned from the next middleware.
|
60
|
+
#
|
61
|
+
# # invoke before the request has been sent, receives the
|
62
|
+
# # response from the send middleware
|
63
|
+
# middleware.around_send do |app, input, context|
|
64
|
+
#
|
65
|
+
# # this code is invoked before the request is sent
|
66
|
+
# # ...
|
67
|
+
#
|
68
|
+
# # around handlers MUST call the next middleware in the stack
|
69
|
+
# output = app.call(input, context)
|
70
|
+
#
|
71
|
+
# # this code is invoked after the response has been received
|
72
|
+
# # ...
|
73
|
+
#
|
74
|
+
# # around handlers must return the response down the stack
|
75
|
+
# output
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# ## Removing Middleware
|
79
|
+
# You may remove existing middleware from the stack using either the class
|
80
|
+
# or instance `remove` methods and providing the middleware class to
|
81
|
+
# be removed. The remove methods are chainable and convenience methods are
|
82
|
+
# defined for the same set of lifecycle events as the `before`, `after`
|
83
|
+
# and `around` methods:
|
84
|
+
#
|
85
|
+
# # create a middleware builder that removes send and build middlewares
|
86
|
+
# MiddlewareBuilder
|
87
|
+
# .remove_send
|
88
|
+
# .remove_build
|
89
|
+
#
|
90
|
+
class MiddlewareBuilder
|
91
|
+
# @private
|
92
|
+
BOTH = 'expected a handler or a Proc, got both'
|
93
|
+
|
94
|
+
# @private
|
95
|
+
NEITHER = 'expected a handler or a Proc, got neither'
|
96
|
+
|
97
|
+
# @private
|
98
|
+
TOO_MANY = 'wrong number of arguments (given %<count>d, expected 0 or 1)'
|
99
|
+
|
100
|
+
# @private
|
101
|
+
CALLABLE = 'expected handler to respond to #call'
|
102
|
+
|
103
|
+
# @param [Proc, MiddlewareBuilder] middleware
|
104
|
+
#
|
105
|
+
# If `middleware` is another {MiddlewareBuilder} instance, then
|
106
|
+
# the middleware handlers are copied.
|
107
|
+
#
|
108
|
+
def initialize(middleware = nil)
|
109
|
+
@middleware = []
|
110
|
+
case middleware
|
111
|
+
when MiddlewareBuilder then @middleware.concat(middleware.to_a)
|
112
|
+
when nil then nil
|
113
|
+
else
|
114
|
+
raise ArgumentError, 'expected :middleware to be a' \
|
115
|
+
'Hearth::MiddlewareBuilder,' \
|
116
|
+
" got #{middleware.class}"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# @param [MiddlewareStack] middleware_stack
|
121
|
+
def apply(middleware_stack)
|
122
|
+
@middleware.each do |handler|
|
123
|
+
method, relation, middleware, kwargs = handler
|
124
|
+
if method == :remove
|
125
|
+
middleware_stack.remove(relation)
|
126
|
+
else
|
127
|
+
middleware_stack.send(method, relation, middleware, **kwargs)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def before(klass, *args, &block)
|
133
|
+
@middleware << [
|
134
|
+
:use_before,
|
135
|
+
klass,
|
136
|
+
Middleware::RequestHandler,
|
137
|
+
{ handler: handler_or_proc!(args, &block) }
|
138
|
+
]
|
139
|
+
self
|
140
|
+
end
|
141
|
+
|
142
|
+
def after(klass, *args, &block)
|
143
|
+
@middleware << [
|
144
|
+
:use_before,
|
145
|
+
klass,
|
146
|
+
Middleware::ResponseHandler,
|
147
|
+
{ handler: handler_or_proc!(args, &block) }
|
148
|
+
]
|
149
|
+
self
|
150
|
+
end
|
151
|
+
|
152
|
+
def around(klass, *args, &block)
|
153
|
+
@middleware << [
|
154
|
+
:use_before,
|
155
|
+
klass,
|
156
|
+
Middleware::AroundHandler,
|
157
|
+
{ handler: handler_or_proc!(args, &block) }
|
158
|
+
]
|
159
|
+
self
|
160
|
+
end
|
161
|
+
|
162
|
+
def remove(klass)
|
163
|
+
@middleware << [
|
164
|
+
:remove,
|
165
|
+
klass,
|
166
|
+
nil,
|
167
|
+
nil
|
168
|
+
]
|
169
|
+
self
|
170
|
+
end
|
171
|
+
|
172
|
+
# Define convenience methods for chaining
|
173
|
+
class << self
|
174
|
+
def before(klass, *args, &block)
|
175
|
+
MiddlewareBuilder.new.before(klass, *args, &block)
|
176
|
+
end
|
177
|
+
|
178
|
+
def after(klass, *args, &block)
|
179
|
+
MiddlewareBuilder.new.after(klass, *args, &block)
|
180
|
+
end
|
181
|
+
|
182
|
+
def around(klass, *args, &block)
|
183
|
+
MiddlewareBuilder.new.around(klass, *args, &block)
|
184
|
+
end
|
185
|
+
|
186
|
+
def remove(klass)
|
187
|
+
MiddlewareBuilder.new.remove(klass)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# define convenience methods for standard middleware classes
|
192
|
+
# these define methods and class methods for before,after,around
|
193
|
+
# eg: before_build, after_build, around_build.
|
194
|
+
STANDARD_MIDDLEWARE = [
|
195
|
+
Hearth::Middleware::Validate,
|
196
|
+
Hearth::Middleware::HostPrefix,
|
197
|
+
Hearth::Middleware::Build,
|
198
|
+
Hearth::Middleware::Send,
|
199
|
+
Hearth::Middleware::Retry,
|
200
|
+
Hearth::Middleware::Parse
|
201
|
+
].freeze
|
202
|
+
|
203
|
+
STANDARD_MIDDLEWARE.each do |klass|
|
204
|
+
simple_step_name = klass.to_s.split('::').last.downcase
|
205
|
+
%w[before after around].each do |method|
|
206
|
+
method_name = "#{method}_#{simple_step_name}"
|
207
|
+
define_method(method_name) do |*args, &block|
|
208
|
+
return send(method, klass, *args, &block)
|
209
|
+
end
|
210
|
+
|
211
|
+
define_singleton_method(method_name) do |*args, &block|
|
212
|
+
return send(method, klass, *args, &block)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
remove_method_name = "remove_#{simple_step_name}"
|
217
|
+
define_method(remove_method_name) do
|
218
|
+
return remove(klass)
|
219
|
+
end
|
220
|
+
|
221
|
+
define_singleton_method(remove_method_name) do
|
222
|
+
return remove(klass)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def to_a
|
227
|
+
@middleware
|
228
|
+
end
|
229
|
+
|
230
|
+
private
|
231
|
+
|
232
|
+
def handler_or_proc!(args, &block)
|
233
|
+
validate_args!(args, &block)
|
234
|
+
callable = args.first || Proc.new(&block)
|
235
|
+
raise ArgumentError, CALLABLE unless callable.respond_to?(:call)
|
236
|
+
|
237
|
+
callable
|
238
|
+
end
|
239
|
+
|
240
|
+
def validate_args!(args, &block)
|
241
|
+
raise ArgumentError, BOTH if args.size.positive? && block
|
242
|
+
raise ArgumentError, NEITHER if args.empty? && block.nil?
|
243
|
+
raise ArgumentError, format(TOO_MANY, count: args.size) if args.size > 1
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|