hearth 1.0.0.pre1 → 1.0.0.pre2
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/CHANGELOG.md +4 -4
- data/VERSION +1 -1
- data/lib/hearth/api_error.rb +15 -1
- data/lib/hearth/auth_option.rb +21 -0
- data/lib/hearth/auth_schemes/anonymous.rb +21 -0
- data/lib/hearth/auth_schemes/http_api_key.rb +16 -0
- data/lib/hearth/auth_schemes/http_basic.rb +16 -0
- data/lib/hearth/auth_schemes/http_bearer.rb +16 -0
- data/lib/hearth/auth_schemes/http_digest.rb +16 -0
- data/lib/hearth/auth_schemes.rb +32 -0
- data/lib/hearth/checksums.rb +31 -0
- data/lib/hearth/client_stubs.rb +130 -0
- data/lib/hearth/config/env_provider.rb +53 -0
- data/lib/hearth/config/resolver.rb +52 -0
- data/lib/hearth/configuration.rb +15 -0
- data/lib/hearth/connection_pool.rb +77 -0
- data/lib/hearth/context.rb +28 -4
- data/lib/hearth/dns/host_address.rb +23 -0
- data/lib/hearth/dns/host_resolver.rb +92 -0
- data/lib/hearth/dns.rb +48 -0
- data/lib/hearth/http/api_error.rb +4 -8
- data/lib/hearth/http/client.rb +208 -59
- data/lib/hearth/http/error_inspector.rb +85 -0
- data/lib/hearth/http/error_parser.rb +18 -20
- data/lib/hearth/http/field.rb +64 -0
- data/lib/hearth/http/fields.rb +117 -0
- data/lib/hearth/http/middleware/content_length.rb +5 -2
- data/lib/hearth/http/middleware/content_md5.rb +31 -0
- data/lib/hearth/http/middleware/request_compression.rb +157 -0
- data/lib/hearth/http/middleware.rb +12 -0
- data/lib/hearth/http/networking_error.rb +1 -14
- data/lib/hearth/http/request.rb +83 -56
- data/lib/hearth/http/response.rb +42 -13
- data/lib/hearth/http.rb +14 -5
- data/lib/hearth/identities/anonymous.rb +8 -0
- data/lib/hearth/identities/http_api_key.rb +16 -0
- data/lib/hearth/identities/http_bearer.rb +16 -0
- data/lib/hearth/identities/http_login.rb +20 -0
- data/lib/hearth/identities.rb +21 -0
- data/lib/hearth/identity_resolver.rb +17 -0
- data/lib/hearth/interceptor.rb +506 -0
- data/lib/hearth/interceptor_context.rb +36 -0
- data/lib/hearth/interceptor_list.rb +48 -0
- data/lib/hearth/interceptors.rb +75 -0
- data/lib/hearth/middleware/auth.rb +100 -0
- data/lib/hearth/middleware/build.rb +32 -0
- data/lib/hearth/middleware/host_prefix.rb +10 -6
- data/lib/hearth/middleware/initialize.rb +58 -0
- data/lib/hearth/middleware/parse.rb +45 -6
- data/lib/hearth/middleware/retry.rb +97 -23
- data/lib/hearth/middleware/send.rb +137 -25
- data/lib/hearth/middleware/sign.rb +65 -0
- data/lib/hearth/middleware/validate.rb +11 -1
- data/lib/hearth/middleware.rb +19 -8
- data/lib/hearth/middleware_stack.rb +1 -43
- data/lib/hearth/networking_error.rb +18 -0
- data/lib/hearth/number_helper.rb +2 -2
- data/lib/hearth/output.rb +8 -4
- data/lib/hearth/plugin_list.rb +53 -0
- data/lib/hearth/query/param.rb +52 -0
- data/lib/hearth/query/param_list.rb +54 -0
- data/lib/hearth/query/param_matcher.rb +32 -0
- data/lib/hearth/refreshing_identity_resolver.rb +63 -0
- data/lib/hearth/request.rb +22 -0
- data/lib/hearth/response.rb +33 -0
- data/lib/hearth/retry/adaptive.rb +60 -0
- data/lib/hearth/retry/capacity_not_available_error.rb +9 -0
- data/lib/hearth/retry/client_rate_limiter.rb +143 -0
- data/lib/hearth/retry/exponential_backoff.rb +15 -0
- data/lib/hearth/retry/retry_quota.rb +56 -0
- data/lib/hearth/retry/standard.rb +46 -0
- data/lib/hearth/retry/strategy.rb +20 -0
- data/lib/hearth/retry.rb +16 -0
- data/lib/hearth/signers/anonymous.rb +16 -0
- data/lib/hearth/signers/http_api_key.rb +29 -0
- data/lib/hearth/signers/http_basic.rb +23 -0
- data/lib/hearth/signers/http_bearer.rb +19 -0
- data/lib/hearth/signers/http_digest.rb +19 -0
- data/lib/hearth/signers.rb +23 -0
- data/lib/hearth/stubs.rb +30 -0
- data/lib/hearth/time_helper.rb +5 -3
- data/lib/hearth/validator.rb +44 -5
- data/lib/hearth/waiters/poller.rb +6 -7
- data/lib/hearth/waiters/waiter.rb +17 -4
- data/lib/hearth/xml/formatter.rb +11 -2
- data/lib/hearth/xml/node.rb +2 -2
- data/lib/hearth.rb +32 -5
- data/sig/lib/hearth/aliases.rbs +4 -0
- data/sig/lib/hearth/api_error.rbs +13 -0
- data/sig/lib/hearth/auth_option.rbs +11 -0
- data/sig/lib/hearth/auth_schemes/anonymous.rbs +7 -0
- data/sig/lib/hearth/auth_schemes/http_api_key.rbs +7 -0
- data/sig/lib/hearth/auth_schemes/http_basic.rbs +7 -0
- data/sig/lib/hearth/auth_schemes/http_bearer.rbs +7 -0
- data/sig/lib/hearth/auth_schemes/http_digest.rbs +7 -0
- data/sig/lib/hearth/auth_schemes.rbs +13 -0
- data/sig/lib/hearth/block_io.rbs +9 -0
- data/sig/lib/hearth/client_stubs.rbs +5 -0
- data/sig/lib/hearth/configuration.rbs +7 -0
- data/sig/lib/hearth/dns/host_address.rbs +13 -0
- data/sig/lib/hearth/dns/host_resolver.rbs +19 -0
- data/sig/lib/hearth/http/api_error.rbs +13 -0
- data/sig/lib/hearth/http/client.rbs +9 -0
- data/sig/lib/hearth/http/field.rbs +19 -0
- data/sig/lib/hearth/http/fields.rbs +43 -0
- data/sig/lib/hearth/http/request.rbs +25 -0
- data/sig/lib/hearth/http/response.rbs +21 -0
- data/sig/lib/hearth/identities/anonymous.rbs +6 -0
- data/sig/lib/hearth/identities/http_api_key.rbs +9 -0
- data/sig/lib/hearth/identities/http_bearer.rbs +9 -0
- data/sig/lib/hearth/identities/http_login.rbs +11 -0
- data/sig/lib/hearth/identities.rbs +9 -0
- data/sig/lib/hearth/identity_resolver.rbs +7 -0
- data/sig/lib/hearth/interceptor.rbs +9 -0
- data/sig/lib/hearth/interceptor_context.rbs +15 -0
- data/sig/lib/hearth/interceptor_list.rbs +16 -0
- data/sig/lib/hearth/interfaces.rbs +65 -0
- data/sig/lib/hearth/output.rbs +11 -0
- data/sig/lib/hearth/plugin_list.rbs +15 -0
- data/sig/lib/hearth/query/param.rbs +17 -0
- data/sig/lib/hearth/query/param_list.rbs +25 -0
- data/sig/lib/hearth/request.rbs +9 -0
- data/sig/lib/hearth/response.rbs +11 -0
- data/sig/lib/hearth/retry/adaptive.rbs +13 -0
- data/sig/lib/hearth/retry/exponential_backoff.rbs +7 -0
- data/sig/lib/hearth/retry/standard.rbs +13 -0
- data/sig/lib/hearth/retry/strategy.rbs +11 -0
- data/sig/lib/hearth/retry.rbs +9 -0
- data/sig/lib/hearth/signers/anonymous.rbs +9 -0
- data/sig/lib/hearth/signers/http_api_key.rbs +9 -0
- data/sig/lib/hearth/signers/http_basic.rbs +9 -0
- data/sig/lib/hearth/signers/http_bearer.rbs +9 -0
- data/sig/lib/hearth/signers/http_digest.rbs +9 -0
- data/sig/lib/hearth/signers.rbs +9 -0
- data/sig/lib/hearth/structure.rbs +7 -0
- data/sig/lib/hearth/union.rbs +5 -0
- data/sig/lib/hearth/waiters/waiter.rbs +17 -0
- metadata +132 -22
- data/lib/hearth/http/headers.rb +0 -70
- data/lib/hearth/middleware/around_handler.rb +0 -24
- data/lib/hearth/middleware/request_handler.rb +0 -24
- data/lib/hearth/middleware/response_handler.rb +0 -25
- data/lib/hearth/middleware_builder.rb +0 -246
- data/lib/hearth/stubbing/client_stubs.rb +0 -115
- data/lib/hearth/stubbing/stubs.rb +0 -32
- data/lib/hearth/waiters/errors.rb +0 -15
- data/sig/lib/seahorse/api_error.rbs +0 -10
- data/sig/lib/seahorse/document.rbs +0 -2
- data/sig/lib/seahorse/http/api_error.rbs +0 -21
- data/sig/lib/seahorse/http/headers.rbs +0 -47
- data/sig/lib/seahorse/http/response.rbs +0 -21
- data/sig/lib/seahorse/simple_delegator.rbs +0 -3
- data/sig/lib/seahorse/structure.rbs +0 -18
- data/sig/lib/seahorse/stubbing/client_stubs.rbs +0 -103
- data/sig/lib/seahorse/stubbing/stubs.rbs +0 -14
- data/sig/lib/seahorse/union.rbs +0 -6
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hearth
|
|
4
|
+
module Middleware
|
|
5
|
+
# A middleware that resolves identities for signing requests.
|
|
6
|
+
# @api private
|
|
7
|
+
class Auth
|
|
8
|
+
include Middleware::Logging
|
|
9
|
+
|
|
10
|
+
# @param [Class] app The next middleware in the stack.
|
|
11
|
+
# @param [#resolve(auth_params)] auth_resolver A class that responds to a
|
|
12
|
+
# `resolve(auth_params)` method where `auth_params` is a struct with an
|
|
13
|
+
# operation_name. For a given operation_name, the method must return an
|
|
14
|
+
# ordered list of {Hearth::AuthOption} objects to be considered for
|
|
15
|
+
# authentication.
|
|
16
|
+
# @param [Struct] auth_params A struct with an operation_name and other
|
|
17
|
+
# parameters that may be used to resolve auth options.
|
|
18
|
+
# @param [Array<Hearth::AuthScheme::Base>] auth_schemes A list of
|
|
19
|
+
# auth schemes to consider for authentication.
|
|
20
|
+
def initialize(app, auth_resolver:, auth_params:, auth_schemes:, **kwargs)
|
|
21
|
+
@app = app
|
|
22
|
+
@auth_resolver = auth_resolver
|
|
23
|
+
@auth_params = auth_params
|
|
24
|
+
@auth_schemes = auth_schemes.to_h { |s| [s.scheme_id, s] }
|
|
25
|
+
|
|
26
|
+
@identity_resolvers = {}
|
|
27
|
+
kwargs.each do |key, value|
|
|
28
|
+
next unless key.superclass == Hearth::Identities::Base
|
|
29
|
+
|
|
30
|
+
@identity_resolvers[key] = value
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @param input
|
|
35
|
+
# @param context
|
|
36
|
+
# @return [Output]
|
|
37
|
+
def call(input, context)
|
|
38
|
+
log_debug(context, 'Resolving auth')
|
|
39
|
+
auth_options = @auth_resolver.resolve(@auth_params)
|
|
40
|
+
log_debug(context, "Resolved auth options: #{auth_options}")
|
|
41
|
+
context.auth = resolve_auth(auth_options)
|
|
42
|
+
log_debug(context, "Resolved auth: #{context.auth}")
|
|
43
|
+
@app.call(input, context)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
ResolvedAuth = Struct.new(
|
|
49
|
+
:signer,
|
|
50
|
+
:signer_properties,
|
|
51
|
+
:identity,
|
|
52
|
+
:identity_properties,
|
|
53
|
+
keyword_init: true
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def resolve_auth(auth_options)
|
|
57
|
+
failures = []
|
|
58
|
+
|
|
59
|
+
auth_options.each do |auth_option|
|
|
60
|
+
auth_scheme = @auth_schemes[auth_option.scheme_id]
|
|
61
|
+
resolved_auth = try_load_auth_scheme(
|
|
62
|
+
auth_option,
|
|
63
|
+
auth_scheme,
|
|
64
|
+
failures
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
return resolved_auth if resolved_auth
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
raise failures.join("\n")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def try_load_auth_scheme(auth_option, auth_scheme, failures)
|
|
74
|
+
scheme_id = auth_option.scheme_id
|
|
75
|
+
unless auth_scheme
|
|
76
|
+
failures << "Auth scheme #{scheme_id} was not enabled " \
|
|
77
|
+
'for this request'
|
|
78
|
+
return
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
identity_resolver = auth_scheme.identity_resolver(@identity_resolvers)
|
|
82
|
+
unless identity_resolver
|
|
83
|
+
failures << "Auth scheme #{scheme_id} did not have an " \
|
|
84
|
+
'identity resolver configured'
|
|
85
|
+
return
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
identity_properties = auth_option.identity_properties
|
|
89
|
+
identity = identity_resolver.identity(identity_properties)
|
|
90
|
+
|
|
91
|
+
ResolvedAuth.new(
|
|
92
|
+
identity: identity,
|
|
93
|
+
identity_properties: auth_option.identity_properties,
|
|
94
|
+
signer: auth_scheme.signer,
|
|
95
|
+
signer_properties: auth_option.signer_properties
|
|
96
|
+
)
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -5,6 +5,8 @@ module Hearth
|
|
|
5
5
|
# A middleware that builds a request object.
|
|
6
6
|
# @api private
|
|
7
7
|
class Build
|
|
8
|
+
include Middleware::Logging
|
|
9
|
+
|
|
8
10
|
# @param [Class] app The next middleware in the stack.
|
|
9
11
|
# @param [Class] builder A builder object responsible for building the
|
|
10
12
|
# request. It must respond to #build and take the request and input as
|
|
@@ -18,7 +20,37 @@ module Hearth
|
|
|
18
20
|
# @param context
|
|
19
21
|
# @return [Output]
|
|
20
22
|
def call(input, context)
|
|
23
|
+
interceptor_error = Interceptors.invoke(
|
|
24
|
+
hook: Interceptor::MODIFY_BEFORE_SERIALIZATION,
|
|
25
|
+
input: input,
|
|
26
|
+
context: context,
|
|
27
|
+
output: nil,
|
|
28
|
+
aggregate_errors: false
|
|
29
|
+
)
|
|
30
|
+
return Hearth::Output.new(error: interceptor_error) if interceptor_error
|
|
31
|
+
|
|
32
|
+
interceptor_error = Interceptors.invoke(
|
|
33
|
+
hook: Interceptor::READ_BEFORE_SERIALIZATION,
|
|
34
|
+
input: input,
|
|
35
|
+
context: context,
|
|
36
|
+
output: nil,
|
|
37
|
+
aggregate_errors: false
|
|
38
|
+
)
|
|
39
|
+
return Hearth::Output.new(error: interceptor_error) if interceptor_error
|
|
40
|
+
|
|
41
|
+
log_debug(context, "Building request with: #{input}")
|
|
21
42
|
@builder.build(context.request, input: input)
|
|
43
|
+
log_debug(context, "Built request: #{context.request.inspect}")
|
|
44
|
+
|
|
45
|
+
interceptor_error = Interceptors.invoke(
|
|
46
|
+
hook: Interceptor::READ_AFTER_SERIALIZATION,
|
|
47
|
+
input: input,
|
|
48
|
+
context: context,
|
|
49
|
+
output: nil,
|
|
50
|
+
aggregate_errors: false
|
|
51
|
+
)
|
|
52
|
+
return Hearth::Output.new(error: interceptor_error) if interceptor_error
|
|
53
|
+
|
|
22
54
|
@app.call(input, context)
|
|
23
55
|
end
|
|
24
56
|
end
|
|
@@ -5,6 +5,8 @@ module Hearth
|
|
|
5
5
|
# A middleware that prefixes the host.
|
|
6
6
|
# @api private
|
|
7
7
|
class HostPrefix
|
|
8
|
+
include Middleware::Logging
|
|
9
|
+
|
|
8
10
|
# @param [Class] app The next middleware in the stack.
|
|
9
11
|
# @param [Boolean] disable_host_prefix If true, this option will not
|
|
10
12
|
# modify the host url.
|
|
@@ -21,25 +23,27 @@ module Hearth
|
|
|
21
23
|
# @param context
|
|
22
24
|
# @return [Output]
|
|
23
25
|
def call(input, context)
|
|
24
|
-
unless @disable_host_prefix
|
|
25
|
-
prefix = apply_labels(@host_prefix, input)
|
|
26
|
-
context.request.prefix_host(prefix)
|
|
27
|
-
end
|
|
26
|
+
prefix_host(input, context) unless @disable_host_prefix
|
|
28
27
|
@app.call(input, context)
|
|
29
28
|
end
|
|
30
29
|
|
|
31
30
|
private
|
|
32
31
|
|
|
32
|
+
def prefix_host(input, context)
|
|
33
|
+
log_debug(context, "Prefixing host with #{@host_prefix}")
|
|
34
|
+
prefix = apply_labels(@host_prefix, input)
|
|
35
|
+
context.request.prefix_host(prefix)
|
|
36
|
+
log_debug(context, "Prefixed host: #{context.request.uri.host}")
|
|
37
|
+
end
|
|
38
|
+
|
|
33
39
|
def apply_labels(host_prefix, input)
|
|
34
40
|
host_prefix.gsub(/\{.+?\}/) do |host_label|
|
|
35
41
|
key = host_label.delete('{}')
|
|
36
42
|
value = input[key.to_sym]
|
|
37
|
-
|
|
38
43
|
if value.nil? || value.empty?
|
|
39
44
|
raise ArgumentError,
|
|
40
45
|
"Host label #{key} cannot be nil or empty."
|
|
41
46
|
end
|
|
42
|
-
|
|
43
47
|
value
|
|
44
48
|
end
|
|
45
49
|
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Hearth
|
|
4
|
+
module Middleware
|
|
5
|
+
# A middleware used to initialize the request, called first in the stack
|
|
6
|
+
# @api private
|
|
7
|
+
class Initialize
|
|
8
|
+
include Middleware::Logging
|
|
9
|
+
|
|
10
|
+
# @param [Class] app The next middleware in the stack.
|
|
11
|
+
def initialize(app)
|
|
12
|
+
@app = app
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @param input
|
|
16
|
+
# @param context
|
|
17
|
+
# @return [Output]
|
|
18
|
+
def call(input, context)
|
|
19
|
+
interceptor_error = Interceptors.invoke(
|
|
20
|
+
hook: Interceptor::READ_BEFORE_EXECUTION,
|
|
21
|
+
input: input,
|
|
22
|
+
context: context,
|
|
23
|
+
output: nil,
|
|
24
|
+
aggregate_errors: true
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
output =
|
|
28
|
+
if interceptor_error
|
|
29
|
+
Hearth::Output.new(error: interceptor_error)
|
|
30
|
+
else
|
|
31
|
+
log_debug(context, "Initializing request with #{input}")
|
|
32
|
+
@app.call(input, context)
|
|
33
|
+
end
|
|
34
|
+
log_debug(context, 'Finished request')
|
|
35
|
+
|
|
36
|
+
interceptor_error = Interceptors.invoke(
|
|
37
|
+
hook: Interceptor::MODIFY_BEFORE_COMPLETION,
|
|
38
|
+
input: input,
|
|
39
|
+
context: context,
|
|
40
|
+
output: output,
|
|
41
|
+
aggregate_errors: false
|
|
42
|
+
)
|
|
43
|
+
output.error = interceptor_error if interceptor_error
|
|
44
|
+
|
|
45
|
+
interceptor_error = Interceptors.invoke(
|
|
46
|
+
hook: Interceptor::READ_AFTER_EXECUTION,
|
|
47
|
+
input: input,
|
|
48
|
+
context: context,
|
|
49
|
+
output: output,
|
|
50
|
+
aggregate_errors: true
|
|
51
|
+
)
|
|
52
|
+
output.error = interceptor_error if interceptor_error
|
|
53
|
+
|
|
54
|
+
output
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -5,6 +5,8 @@ module Hearth
|
|
|
5
5
|
# A middleware that parses a response object.
|
|
6
6
|
# @api private
|
|
7
7
|
class Parse
|
|
8
|
+
include Middleware::Logging
|
|
9
|
+
|
|
8
10
|
# @param [Class] app The next middleware in the stack.
|
|
9
11
|
# @param [Class] error_parser A parser object responsible for parsing the
|
|
10
12
|
# response if there is an error. It must respond to #parse and take the
|
|
@@ -23,19 +25,56 @@ module Hearth
|
|
|
23
25
|
# @return [Output]
|
|
24
26
|
def call(input, context)
|
|
25
27
|
output = @app.call(input, context)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
|
|
29
|
+
interceptor_error = Interceptors.invoke(
|
|
30
|
+
hook: Interceptor::MODIFY_BEFORE_DESERIALIZATION,
|
|
31
|
+
input: input,
|
|
32
|
+
context: context,
|
|
33
|
+
output: output,
|
|
34
|
+
aggregate_errors: false
|
|
35
|
+
)
|
|
36
|
+
if interceptor_error
|
|
37
|
+
output.error = interceptor_error
|
|
38
|
+
return output
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
interceptor_error = Interceptors.invoke(
|
|
42
|
+
hook: Interceptor::READ_BEFORE_DESERIALIZATION,
|
|
43
|
+
input: input,
|
|
44
|
+
context: context,
|
|
45
|
+
output: output,
|
|
46
|
+
aggregate_errors: false
|
|
47
|
+
)
|
|
48
|
+
if interceptor_error
|
|
49
|
+
output.error = interceptor_error
|
|
50
|
+
return output
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
parse_error(context, output) unless output.error
|
|
54
|
+
parse_data(context, output) unless output.error
|
|
55
|
+
|
|
56
|
+
interceptor_error = Interceptors.invoke(
|
|
57
|
+
hook: Interceptor::READ_AFTER_DESERIALIZATION,
|
|
58
|
+
input: input,
|
|
59
|
+
context: context,
|
|
60
|
+
output: output,
|
|
61
|
+
aggregate_errors: false
|
|
62
|
+
)
|
|
63
|
+
output.error = interceptor_error if interceptor_error
|
|
64
|
+
|
|
28
65
|
output
|
|
29
66
|
end
|
|
30
67
|
|
|
31
68
|
private
|
|
32
69
|
|
|
33
|
-
def parse_error(
|
|
34
|
-
output.error = @error_parser.parse(response)
|
|
70
|
+
def parse_error(context, output)
|
|
71
|
+
output.error = @error_parser.parse(context.response, output.metadata)
|
|
72
|
+
log_debug(context, "Parsed error: #{output.error}") if output.error
|
|
35
73
|
end
|
|
36
74
|
|
|
37
|
-
def parse_data(
|
|
38
|
-
output.data = @data_parser.parse(response)
|
|
75
|
+
def parse_data(context, output)
|
|
76
|
+
output.data = @data_parser.parse(context.response)
|
|
77
|
+
log_debug(context, "Parsed data: #{output.data}") if output.data
|
|
39
78
|
end
|
|
40
79
|
end
|
|
41
80
|
end
|
|
@@ -2,41 +2,115 @@
|
|
|
2
2
|
|
|
3
3
|
module Hearth
|
|
4
4
|
module Middleware
|
|
5
|
-
# A middleware that retries the request.
|
|
5
|
+
# A middleware that retries the request using a retry strategy.
|
|
6
6
|
# @api private
|
|
7
7
|
class Retry
|
|
8
|
+
include Middleware::Logging
|
|
9
|
+
|
|
8
10
|
# @param [Class] app The next middleware in the stack.
|
|
9
|
-
# @param [
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
|
|
11
|
+
# @param [Strategy] retry_strategy (Standard) The retry strategy
|
|
12
|
+
# to use. Hearth has two built in classes, Standard and Adaptive.
|
|
13
|
+
# * `Retry::Standard` - A standardized set of retry rules across
|
|
14
|
+
# the AWS SDKs. This includes support for retry quotas, which limit
|
|
15
|
+
# the number of unsuccessful retries a client can make.
|
|
16
|
+
# * `Retry::Adaptive` - An experimental retry mode that includes
|
|
17
|
+
# all the functionality of `standard` mode along with automatic
|
|
18
|
+
# client side throttling. This is a provisional mode that may change
|
|
19
|
+
# behavior in the future.
|
|
20
|
+
def initialize(app, retry_strategy:, error_inspector_class:)
|
|
13
21
|
@app = app
|
|
14
|
-
@
|
|
15
|
-
@
|
|
22
|
+
@retry_strategy = retry_strategy
|
|
23
|
+
@error_inspector_class = error_inspector_class
|
|
24
|
+
|
|
25
|
+
@retries = 0
|
|
16
26
|
end
|
|
17
27
|
|
|
18
|
-
# @param input
|
|
19
|
-
# @param context
|
|
20
|
-
# @return [Output]
|
|
21
28
|
def call(input, context)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
interceptor_error = Interceptors.invoke(
|
|
30
|
+
hook: Interceptor::MODIFY_BEFORE_RETRY_LOOP,
|
|
31
|
+
input: input,
|
|
32
|
+
context: context,
|
|
33
|
+
output: nil,
|
|
34
|
+
aggregate_errors: false
|
|
35
|
+
)
|
|
36
|
+
return Hearth::Output.new(error: interceptor_error) if interceptor_error
|
|
37
|
+
|
|
38
|
+
output = nil
|
|
39
|
+
token = @retry_strategy.acquire_initial_retry_token(nil)
|
|
40
|
+
log_debug(context, "Starting retry loop with token: #{token}")
|
|
41
|
+
loop do
|
|
42
|
+
interceptor_error = Interceptors.invoke(
|
|
43
|
+
hook: Interceptor::READ_BEFORE_ATTEMPT,
|
|
44
|
+
input: input,
|
|
45
|
+
context: context,
|
|
46
|
+
output: nil,
|
|
47
|
+
aggregate_errors: true
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
output =
|
|
51
|
+
if interceptor_error
|
|
52
|
+
Hearth::Output.new(error: interceptor_error)
|
|
53
|
+
else
|
|
54
|
+
log_debug(context, 'Attempting request in retry loop')
|
|
55
|
+
@app.call(input, context)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
interceptor_error = Interceptors.invoke(
|
|
59
|
+
hook: Interceptor::MODIFY_BEFORE_ATTEMPT_COMPLETION,
|
|
60
|
+
input: input,
|
|
61
|
+
context: context,
|
|
62
|
+
output: output,
|
|
63
|
+
aggregate_errors: false
|
|
64
|
+
)
|
|
65
|
+
output.error = interceptor_error if interceptor_error
|
|
66
|
+
|
|
67
|
+
interceptor_error = Interceptors.invoke(
|
|
68
|
+
hook: Interceptor::READ_AFTER_ATTEMPT,
|
|
69
|
+
input: input,
|
|
70
|
+
context: context,
|
|
71
|
+
output: output,
|
|
72
|
+
aggregate_errors: true
|
|
73
|
+
)
|
|
74
|
+
output.error = interceptor_error if interceptor_error
|
|
75
|
+
|
|
76
|
+
if (error = output.error)
|
|
77
|
+
log_debug(context, "Request failed with error: #{error}")
|
|
78
|
+
error_info = @error_inspector_class.new(error, context.response)
|
|
79
|
+
token = @retry_strategy.refresh_retry_token(token, error_info)
|
|
80
|
+
break unless token
|
|
81
|
+
|
|
82
|
+
log_debug(context, "Retry token refreshed: #{token}")
|
|
83
|
+
log_debug(context, "Sleeping for #{token.retry_delay} seconds")
|
|
84
|
+
Kernel.sleep(token.retry_delay)
|
|
85
|
+
else
|
|
86
|
+
@retry_strategy.record_success(token)
|
|
87
|
+
log_debug(context, 'Request succeeded')
|
|
88
|
+
break
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
reset_request(context)
|
|
92
|
+
reset_response(context, output)
|
|
93
|
+
@retries += 1
|
|
31
94
|
end
|
|
95
|
+
log_debug(context, 'Finished retry loop')
|
|
96
|
+
output
|
|
32
97
|
end
|
|
33
98
|
|
|
34
99
|
private
|
|
35
100
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
101
|
+
def reset_request(context)
|
|
102
|
+
request = context.request
|
|
103
|
+
request.body.rewind if request.body.respond_to?(:rewind)
|
|
104
|
+
|
|
105
|
+
context.auth.signer.reset(
|
|
106
|
+
request: request,
|
|
107
|
+
properties: context.auth.signer_properties
|
|
108
|
+
)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def reset_response(context, output)
|
|
112
|
+
context.response.reset
|
|
113
|
+
output.error = nil
|
|
40
114
|
end
|
|
41
115
|
end
|
|
42
116
|
end
|
|
@@ -5,58 +5,170 @@ module Hearth
|
|
|
5
5
|
# A middleware used to send the request.
|
|
6
6
|
# @api private
|
|
7
7
|
class Send
|
|
8
|
+
include Middleware::Logging
|
|
9
|
+
|
|
8
10
|
# @param [Class] _app The next middleware in the stack.
|
|
9
11
|
# @param [Boolean] stub_responses If true, a request is not sent and a
|
|
10
12
|
# stubbed response is returned.
|
|
11
|
-
# @param [Class]
|
|
12
|
-
# a stubbed response. It must respond to #stub and take
|
|
13
|
-
# and stub data as arguments.
|
|
14
|
-
# @param [
|
|
13
|
+
# @param [Class] stub_data_class A stub object that is responsible for
|
|
14
|
+
# creating a stubbed data response. It must respond to #stub and take
|
|
15
|
+
# the response and stub data as arguments.
|
|
16
|
+
# @param [Array<Class>] stub_error_classes An array of error classes
|
|
17
|
+
# that are responsible for creating a stubbed error response. They
|
|
18
|
+
# must respond to #stub and take the response and stub data as
|
|
19
|
+
# arguments.
|
|
20
|
+
# @param [Stubs] stubs A {Hearth::Stubs} object containing
|
|
15
21
|
# stubbed data for any given operation.
|
|
16
|
-
def initialize(_app, client:, stub_responses:,
|
|
22
|
+
def initialize(_app, client:, stub_responses:,
|
|
23
|
+
stub_data_class:, stub_error_classes:, stubs:)
|
|
17
24
|
@client = client
|
|
18
25
|
@stub_responses = stub_responses
|
|
19
|
-
@
|
|
26
|
+
@stub_data_class = stub_data_class
|
|
27
|
+
@stub_error_classes = stub_error_classes
|
|
20
28
|
@stubs = stubs
|
|
21
29
|
end
|
|
22
30
|
|
|
23
|
-
# @param
|
|
31
|
+
# @param input
|
|
24
32
|
# @param context
|
|
25
33
|
# @return [Output]
|
|
26
|
-
def call(
|
|
34
|
+
def call(input, context)
|
|
35
|
+
interceptor_error = Interceptors.invoke(
|
|
36
|
+
hook: Interceptor::MODIFY_BEFORE_TRANSMIT,
|
|
37
|
+
input: input,
|
|
38
|
+
context: context,
|
|
39
|
+
output: nil,
|
|
40
|
+
aggregate_errors: false
|
|
41
|
+
)
|
|
42
|
+
return Hearth::Output.new(error: interceptor_error) if interceptor_error
|
|
43
|
+
|
|
44
|
+
interceptor_error = Interceptors.invoke(
|
|
45
|
+
hook: Interceptor::READ_BEFORE_TRANSMIT,
|
|
46
|
+
input: input,
|
|
47
|
+
context: context,
|
|
48
|
+
output: nil,
|
|
49
|
+
aggregate_errors: false
|
|
50
|
+
)
|
|
51
|
+
return Hearth::Output.new(error: interceptor_error) if interceptor_error
|
|
52
|
+
|
|
53
|
+
output = Output.new
|
|
27
54
|
if @stub_responses
|
|
28
|
-
|
|
29
|
-
output = Output.new
|
|
30
|
-
apply_stub(stub, context, output)
|
|
31
|
-
output
|
|
55
|
+
stub_response(input, context, output)
|
|
32
56
|
else
|
|
33
|
-
|
|
34
|
-
request: context.request,
|
|
35
|
-
response: context.response
|
|
36
|
-
)
|
|
37
|
-
Output.new
|
|
57
|
+
send_request(context, output)
|
|
38
58
|
end
|
|
59
|
+
|
|
60
|
+
interceptor_error = Interceptors.invoke(
|
|
61
|
+
hook: Interceptor::READ_AFTER_TRANSMIT,
|
|
62
|
+
input: input,
|
|
63
|
+
context: context,
|
|
64
|
+
output: output,
|
|
65
|
+
aggregate_errors: false
|
|
66
|
+
)
|
|
67
|
+
output.error = interceptor_error if interceptor_error
|
|
68
|
+
|
|
69
|
+
output
|
|
39
70
|
end
|
|
40
71
|
|
|
41
72
|
private
|
|
42
73
|
|
|
43
|
-
def
|
|
74
|
+
def stub_response(input, context, output)
|
|
75
|
+
stub = @stubs.next(context.operation_name)
|
|
76
|
+
log_debug(context, "Stubbing response with stub: #{stub}")
|
|
77
|
+
apply_stub(stub, input, context, output)
|
|
78
|
+
log_debug(context, "Stubbed response: #{context.response.inspect}")
|
|
79
|
+
return unless context.response.body.respond_to?(:rewind)
|
|
80
|
+
|
|
81
|
+
context.response.body.rewind
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def send_request(context, output)
|
|
85
|
+
log_debug(context, "Sending request: #{context.request.inspect}")
|
|
86
|
+
@client.transmit(
|
|
87
|
+
request: context.request,
|
|
88
|
+
response: context.response,
|
|
89
|
+
logger: context.logger
|
|
90
|
+
)
|
|
91
|
+
log_debug(context, "Received response: #{context.response.inspect}")
|
|
92
|
+
rescue Hearth::NetworkingError => e
|
|
93
|
+
output.error = e
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def apply_stub(stub, input, context, output)
|
|
44
97
|
case stub
|
|
45
98
|
when Proc
|
|
46
|
-
stub = stub.call(
|
|
47
|
-
apply_stub(stub, context, output)
|
|
48
|
-
when Exception
|
|
99
|
+
stub = stub.call(input)
|
|
100
|
+
apply_stub(stub, input, context, output)
|
|
101
|
+
when Exception, ApiError
|
|
49
102
|
output.error = stub
|
|
50
|
-
when Class
|
|
51
|
-
output.error = stub.new
|
|
52
103
|
when Hash
|
|
53
|
-
|
|
104
|
+
apply_stub_hash(stub, context)
|
|
54
105
|
when NilClass
|
|
55
|
-
|
|
106
|
+
apply_stub_nil(context)
|
|
107
|
+
when Hearth::Structure
|
|
108
|
+
apply_stub_hearth_structure(stub, context)
|
|
109
|
+
when Hearth::Response
|
|
110
|
+
context.response.replace(stub)
|
|
56
111
|
else
|
|
57
112
|
raise ArgumentError, 'Unsupported stub type'
|
|
58
113
|
end
|
|
59
114
|
end
|
|
115
|
+
|
|
116
|
+
def apply_stub_hash(stub, context)
|
|
117
|
+
if stub.key?(:error) && !stub.key?(:data)
|
|
118
|
+
apply_stub_hash_error(stub, context)
|
|
119
|
+
elsif stub.key?(:data) && !stub.key?(:error)
|
|
120
|
+
apply_stub_hash_data(stub, context)
|
|
121
|
+
else
|
|
122
|
+
raise ArgumentError, 'Unsupported stub hash, must be :data or :error'
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def apply_stub_hash_data(stub, context)
|
|
127
|
+
output = @stub_data_class.build(stub[:data], context: 'stub')
|
|
128
|
+
@stub_data_class.validate!(output, context: 'stub')
|
|
129
|
+
@stub_data_class.stub(context.response, stub: output)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def apply_stub_hash_error(stub, context)
|
|
133
|
+
stub_error_class = stub_error_class(stub[:error][:class])
|
|
134
|
+
output = stub_error_class.build(
|
|
135
|
+
stub[:error][:data] || {},
|
|
136
|
+
context: 'stub'
|
|
137
|
+
)
|
|
138
|
+
stub_error_class.validate!(output, context: 'stub')
|
|
139
|
+
stub_error_class.stub(context.response, stub: output)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def stub_error_class(error_class)
|
|
143
|
+
raise ArgumentError, 'Missing stub error class' unless error_class
|
|
144
|
+
|
|
145
|
+
unless error_class.is_a?(Class)
|
|
146
|
+
raise ArgumentError, 'Stub error class must be a class'
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
error_base_name = error_class.name.split('::').last
|
|
150
|
+
stub_class = @stub_error_classes.find do |stub_error_class|
|
|
151
|
+
stub_base_name = stub_error_class.name.split('::').last
|
|
152
|
+
error_base_name == stub_base_name
|
|
153
|
+
end
|
|
154
|
+
raise ArgumentError, 'Unsupported stub error class' unless stub_class
|
|
155
|
+
|
|
156
|
+
stub_class
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def apply_stub_nil(context)
|
|
160
|
+
output = @stub_data_class.build(
|
|
161
|
+
@stub_data_class.default,
|
|
162
|
+
context: 'stub'
|
|
163
|
+
)
|
|
164
|
+
@stub_data_class.validate!(output, context: 'stub')
|
|
165
|
+
@stub_data_class.stub(context.response, stub: output)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def apply_stub_hearth_structure(stub, context)
|
|
169
|
+
@stub_data_class.validate!(stub, context: 'stub')
|
|
170
|
+
@stub_data_class.stub(context.response, stub: stub)
|
|
171
|
+
end
|
|
60
172
|
end
|
|
61
173
|
end
|
|
62
174
|
end
|