hearth 1.0.0.pre1 → 1.0.0.pre3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -4
- data/VERSION +1 -1
- data/lib/hearth/anonymous_auth_resolver.rb +11 -0
- 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.rb +66 -0
- data/lib/hearth/client_stubs.rb +128 -0
- data/lib/hearth/config/env_provider.rb +53 -0
- data/lib/hearth/config/resolver.rb +53 -0
- data/lib/hearth/configuration.rb +15 -0
- data/lib/hearth/connection_pool.rb +77 -0
- data/lib/hearth/context.rb +29 -4
- data/lib/hearth/dns/host_address.rb +27 -0
- data/lib/hearth/dns/host_resolver.rb +92 -0
- data/lib/hearth/dns.rb +48 -0
- data/lib/hearth/endpoint_rules.rb +154 -0
- data/lib/hearth/http/api_error.rb +4 -8
- data/lib/hearth/http/client.rb +206 -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 +49 -0
- data/lib/hearth/http/fields.rb +117 -0
- data/lib/hearth/http/header_list_builder.rb +42 -0
- data/lib/hearth/http/header_list_parser.rb +92 -0
- data/lib/hearth/http/middleware/content_length.rb +7 -4
- data/lib/hearth/http/middleware/content_md5.rb +30 -0
- data/lib/hearth/http/middleware/request_compression.rb +154 -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 +16 -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_provider.rb +17 -0
- data/lib/hearth/interceptor.rb +506 -0
- data/lib/hearth/interceptor_context.rb +40 -0
- data/lib/hearth/interceptor_list.rb +48 -0
- data/lib/hearth/interceptors.rb +76 -0
- data/lib/hearth/json.rb +4 -4
- data/lib/hearth/middleware/auth.rb +103 -0
- data/lib/hearth/middleware/build.rb +32 -1
- data/lib/hearth/middleware/endpoint.rb +79 -0
- data/lib/hearth/middleware/host_prefix.rb +11 -8
- data/lib/hearth/middleware/initialize.rb +57 -0
- data/lib/hearth/middleware/parse.rb +45 -7
- data/lib/hearth/middleware/retry.rb +105 -24
- data/lib/hearth/middleware/send.rb +137 -26
- data/lib/hearth/middleware/sign.rb +65 -0
- data/lib/hearth/middleware/validate.rb +11 -1
- data/lib/hearth/middleware.rb +20 -8
- data/lib/hearth/middleware_stack.rb +2 -44
- data/lib/hearth/networking_error.rb +18 -0
- data/lib/hearth/number_helper.rb +3 -3
- data/lib/hearth/output.rb +8 -4
- data/lib/hearth/plugin_list.rb +53 -0
- data/lib/hearth/query/param.rb +56 -0
- data/lib/hearth/query/param_list.rb +54 -0
- data/lib/hearth/query/param_matcher.rb +31 -0
- data/lib/hearth/refreshing_identity_provider.rb +63 -0
- data/lib/hearth/request.rb +22 -0
- data/lib/hearth/response.rb +36 -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 +145 -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.rb +29 -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/structure.rb +7 -3
- data/lib/hearth/stubs.rb +38 -0
- data/lib/hearth/time_helper.rb +6 -5
- data/lib/hearth/validator.rb +60 -5
- data/lib/hearth/waiters/poller.rb +10 -9
- data/lib/hearth/waiters/waiter.rb +23 -9
- data/lib/hearth/xml/formatter.rb +11 -2
- data/lib/hearth/xml/node.rb +2 -3
- data/lib/hearth/xml/node_matcher.rb +0 -1
- data/lib/hearth.rb +37 -6
- data/sig/lib/hearth/aliases.rbs +6 -0
- data/sig/lib/hearth/anonymous_auth_resolver.rbs +5 -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.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 +11 -0
- data/sig/lib/hearth/dns/host_resolver.rbs +19 -0
- data/sig/lib/hearth/endpoint_rules.rbs +17 -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/header_list_builder.rbs +15 -0
- data/sig/lib/hearth/http/header_list_parser.rbs +19 -0
- data/sig/lib/hearth/http/networking_error.rbs +6 -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_provider.rbs +7 -0
- data/sig/lib/hearth/interceptor.rbs +9 -0
- data/sig/lib/hearth/interceptor_context.rbs +17 -0
- data/sig/lib/hearth/interceptor_list.rbs +16 -0
- data/sig/lib/hearth/interfaces.rbs +87 -0
- data/sig/lib/hearth/json/parse_error.rbs +9 -0
- data/sig/lib/hearth/networking_error.rbs +7 -0
- data/sig/lib/hearth/output.rbs +11 -0
- data/sig/lib/hearth/plugin_list.rbs +13 -0
- data/sig/lib/hearth/query/param.rbs +17 -0
- data/sig/lib/hearth/query/param_list.rbs +25 -0
- data/sig/lib/hearth/refreshing_identity_provider.rbs +10 -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 +6 -0
- data/sig/lib/hearth/stubs.rbs +9 -0
- data/sig/lib/hearth/union.rbs +5 -0
- data/sig/lib/hearth/waiters/waiter.rbs +17 -0
- data/sig/lib/hearth/xml/parse_error.rbs +9 -0
- metadata +151 -25
- 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,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
# Context provided to interceptor hook methods. Interceptors can use this
|
5
|
+
# context to read and modify the input, request, response, and output.
|
6
|
+
# Attributes can be used to pass additional data between interceptors.
|
7
|
+
class InterceptorContext
|
8
|
+
# @param [Hearth::Structure] input
|
9
|
+
# @param [Hearth::Request] request
|
10
|
+
# @param [Hearth::Response] response
|
11
|
+
# @param [Hearth::Output] output
|
12
|
+
# @param [Logger] logger
|
13
|
+
def initialize(input:, request:, response:, output:, logger:)
|
14
|
+
@input = input
|
15
|
+
@request = request
|
16
|
+
@response = response
|
17
|
+
@output = output
|
18
|
+
@logger = logger
|
19
|
+
@attributes = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [Struct] Modeled input, i.e. Types::<Operation>Input
|
23
|
+
attr_reader :input
|
24
|
+
|
25
|
+
# @return [Hearth::Request]
|
26
|
+
attr_reader :request
|
27
|
+
|
28
|
+
# @return [Hearth::Response]
|
29
|
+
attr_reader :response
|
30
|
+
|
31
|
+
# @return [Hearth::Output] Operation output
|
32
|
+
attr_reader :output
|
33
|
+
|
34
|
+
# @return [Logger] logger
|
35
|
+
attr_reader :logger
|
36
|
+
|
37
|
+
# @return [Hash] attributes Additional interceptor data
|
38
|
+
attr_reader :attributes
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
# A list of {Hearth::Interceptor}s or classes that respond to Interceptor
|
5
|
+
# hook methods.
|
6
|
+
class InterceptorList
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
# @param [Array<Interceptor>] interceptors ([])
|
10
|
+
def initialize(interceptors = [])
|
11
|
+
unless interceptors.respond_to?(:each)
|
12
|
+
raise ArgumentError, 'Interceptors must be an enumerable'
|
13
|
+
end
|
14
|
+
|
15
|
+
@interceptors = []
|
16
|
+
interceptors.each { |i| append(i) }
|
17
|
+
end
|
18
|
+
|
19
|
+
# @param [Interceptor] interceptor
|
20
|
+
def append(interceptor)
|
21
|
+
unless valid_interceptor?(interceptor)
|
22
|
+
raise ArgumentError,
|
23
|
+
'Invalid interceptor - must respond to any of: ' \
|
24
|
+
"#{Interceptor.hooks.join(', ')}"
|
25
|
+
end
|
26
|
+
|
27
|
+
@interceptors << interceptor
|
28
|
+
end
|
29
|
+
alias << append
|
30
|
+
|
31
|
+
# @param [InterceptorList] other
|
32
|
+
# @return [InterceptorList] self
|
33
|
+
def concat(other)
|
34
|
+
other.each { |i| append(i) }
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def each(&block)
|
39
|
+
@interceptors.each(&block)
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def valid_interceptor?(interceptor)
|
45
|
+
!(interceptor.methods & Interceptor.hooks).empty?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
# @api private
|
5
|
+
module Interceptors
|
6
|
+
# Invoke all interceptors.
|
7
|
+
#
|
8
|
+
# @param [Symbol] hook The specific hook to invoke.
|
9
|
+
# @param input [Hearth::Structure] input
|
10
|
+
# @param [Hearth::Context] context
|
11
|
+
# @param [Hearth::Output] output
|
12
|
+
# @param [Boolean] aggregate_errors (false) When true, all interceptors are
|
13
|
+
# run and only the last error is returned. When false, returns immediately
|
14
|
+
# if an error is encountered.
|
15
|
+
# @return [nil, StandardError] nil if successful, a standard error otherwise
|
16
|
+
def self.invoke(hook:, input:, context:, output:, aggregate_errors: false)
|
17
|
+
i_ctx = interceptor_context(input, context, output)
|
18
|
+
last_error = nil
|
19
|
+
|
20
|
+
context.interceptors.each do |i|
|
21
|
+
next unless i.respond_to?(hook)
|
22
|
+
|
23
|
+
log_debug(context, i, "Invoking #{hook}")
|
24
|
+
i.send(hook, i_ctx)
|
25
|
+
log_debug(context, i, "Finished #{hook}")
|
26
|
+
rescue StandardError => e
|
27
|
+
log_debug(context, i, "Error in #{hook}: #{e} (#{e.class})")
|
28
|
+
log_last_interceptor_error(last_error, i, context)
|
29
|
+
last_error = e
|
30
|
+
break unless aggregate_errors
|
31
|
+
end
|
32
|
+
|
33
|
+
log_last_output_error(last_error, context, output)
|
34
|
+
last_error
|
35
|
+
end
|
36
|
+
|
37
|
+
class << self
|
38
|
+
private
|
39
|
+
|
40
|
+
def interceptor_context(input, context, output)
|
41
|
+
Hearth::InterceptorContext.new(
|
42
|
+
input: input,
|
43
|
+
request: context.request,
|
44
|
+
response: context.response,
|
45
|
+
output: output,
|
46
|
+
logger: context.logger
|
47
|
+
)
|
48
|
+
end
|
49
|
+
|
50
|
+
def log_last_interceptor_error(last_error, interceptor, context)
|
51
|
+
return unless last_error
|
52
|
+
|
53
|
+
message = "Dropping last error: #{last_error} (#{last_error.class})"
|
54
|
+
context.logger.error(
|
55
|
+
"[#{context.invocation_id}] [#{interceptor.class}] #{message}"
|
56
|
+
)
|
57
|
+
end
|
58
|
+
|
59
|
+
def log_last_output_error(last_error, context, output)
|
60
|
+
return unless last_error && output
|
61
|
+
return unless output.error
|
62
|
+
|
63
|
+
message = "Dropping last error: #{output.error} (#{output.error.class})"
|
64
|
+
context.logger.error(
|
65
|
+
"[#{context.invocation_id}] [Interceptors] #{message}"
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
def log_debug(context, interceptor, message)
|
70
|
+
context.logger.debug(
|
71
|
+
"[#{context.invocation_id}] [#{interceptor.class}] #{message}"
|
72
|
+
)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/hearth/json.rb
CHANGED
@@ -12,10 +12,10 @@ module Hearth
|
|
12
12
|
class << self
|
13
13
|
# @param [String] json
|
14
14
|
# @return [Hash]
|
15
|
-
def
|
16
|
-
|
17
|
-
|
18
|
-
|
15
|
+
def parse(json)
|
16
|
+
return nil if json.empty?
|
17
|
+
|
18
|
+
::JSON.parse(json)
|
19
19
|
rescue ::JSON::ParserError => e
|
20
20
|
raise ParseError, e
|
21
21
|
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
module Middleware
|
5
|
+
# A middleware that resolves identities for signing requests.
|
6
|
+
class Auth
|
7
|
+
include Middleware::Logging
|
8
|
+
|
9
|
+
# @param [Class] app The next middleware in the stack.
|
10
|
+
# @param [#resolve(auth_params)] auth_resolver A class that responds to a
|
11
|
+
# `resolve(auth_params)` method where `auth_params` is a struct with an
|
12
|
+
# operation_name. For a given operation_name, the method must return an
|
13
|
+
# ordered list of {Hearth::AuthOption} objects to be considered for
|
14
|
+
# authentication.
|
15
|
+
# @param [Struct] auth_params A struct with an operation_name and other
|
16
|
+
# parameters that may be used to resolve auth options.
|
17
|
+
# @param [Array<Hearth::AuthScheme::Base>] auth_schemes A list of
|
18
|
+
# auth schemes to consider for authentication.
|
19
|
+
def initialize(app, auth_resolver:, auth_params:, auth_schemes:, **kwargs)
|
20
|
+
@app = app
|
21
|
+
@auth_resolver = auth_resolver
|
22
|
+
@auth_params = auth_params
|
23
|
+
@auth_schemes = auth_schemes.to_h { |s| [s.scheme_id, s] }
|
24
|
+
|
25
|
+
@identity_providers = {}
|
26
|
+
kwargs.each do |key, value|
|
27
|
+
next unless key.superclass == Hearth::Identities::Base
|
28
|
+
|
29
|
+
@identity_providers[key] = value
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param input
|
34
|
+
# @param context
|
35
|
+
# @return [Output]
|
36
|
+
def call(input, context)
|
37
|
+
log_debug(context, 'Resolving auth')
|
38
|
+
auth_options = @auth_resolver.resolve(@auth_params)
|
39
|
+
log_debug(context, "Resolved auth options: #{auth_options}")
|
40
|
+
context.auth = resolve_auth(auth_options)
|
41
|
+
log_debug(context, "Resolved auth: #{context.auth}")
|
42
|
+
@app.call(input, context)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
ResolvedAuth = Struct.new(
|
48
|
+
:scheme_id,
|
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
|
+
raise 'No auth options were resolved' if auth_options.empty?
|
60
|
+
|
61
|
+
auth_options.each do |auth_option|
|
62
|
+
auth_scheme = @auth_schemes[auth_option.scheme_id]
|
63
|
+
resolved_auth = try_load_auth_scheme(
|
64
|
+
auth_option,
|
65
|
+
auth_scheme,
|
66
|
+
failures
|
67
|
+
)
|
68
|
+
|
69
|
+
return resolved_auth if resolved_auth
|
70
|
+
end
|
71
|
+
|
72
|
+
raise failures.join("\n")
|
73
|
+
end
|
74
|
+
|
75
|
+
def try_load_auth_scheme(auth_option, auth_scheme, failures)
|
76
|
+
scheme_id = auth_option.scheme_id
|
77
|
+
unless auth_scheme
|
78
|
+
failures << "Auth scheme #{scheme_id} was not enabled " \
|
79
|
+
'for this request'
|
80
|
+
return
|
81
|
+
end
|
82
|
+
|
83
|
+
identity_provider = auth_scheme.identity_provider(@identity_providers)
|
84
|
+
unless identity_provider
|
85
|
+
failures << "Auth scheme #{scheme_id} did not have an " \
|
86
|
+
'identity resolver configured'
|
87
|
+
return
|
88
|
+
end
|
89
|
+
|
90
|
+
identity_properties = auth_option.identity_properties
|
91
|
+
identity = identity_provider.identity(identity_properties)
|
92
|
+
|
93
|
+
ResolvedAuth.new(
|
94
|
+
scheme_id: scheme_id,
|
95
|
+
identity: identity,
|
96
|
+
identity_properties: auth_option.identity_properties,
|
97
|
+
signer: auth_scheme.signer,
|
98
|
+
signer_properties: auth_option.signer_properties
|
99
|
+
)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -3,8 +3,9 @@
|
|
3
3
|
module Hearth
|
4
4
|
module Middleware
|
5
5
|
# A middleware that builds a request object.
|
6
|
-
# @api private
|
7
6
|
class Build
|
7
|
+
include Middleware::Logging
|
8
|
+
|
8
9
|
# @param [Class] app The next middleware in the stack.
|
9
10
|
# @param [Class] builder A builder object responsible for building the
|
10
11
|
# request. It must respond to #build and take the request and input as
|
@@ -18,7 +19,37 @@ module Hearth
|
|
18
19
|
# @param context
|
19
20
|
# @return [Output]
|
20
21
|
def call(input, context)
|
22
|
+
interceptor_error = Interceptors.invoke(
|
23
|
+
hook: Interceptor::MODIFY_BEFORE_SERIALIZATION,
|
24
|
+
input: input,
|
25
|
+
context: context,
|
26
|
+
output: nil,
|
27
|
+
aggregate_errors: false
|
28
|
+
)
|
29
|
+
return Hearth::Output.new(error: interceptor_error) if interceptor_error
|
30
|
+
|
31
|
+
interceptor_error = Interceptors.invoke(
|
32
|
+
hook: Interceptor::READ_BEFORE_SERIALIZATION,
|
33
|
+
input: input,
|
34
|
+
context: context,
|
35
|
+
output: nil,
|
36
|
+
aggregate_errors: false
|
37
|
+
)
|
38
|
+
return Hearth::Output.new(error: interceptor_error) if interceptor_error
|
39
|
+
|
40
|
+
log_debug(context, "Building request with: #{input}")
|
21
41
|
@builder.build(context.request, input: input)
|
42
|
+
log_debug(context, "Built request: #{context.request.inspect}")
|
43
|
+
|
44
|
+
interceptor_error = Interceptors.invoke(
|
45
|
+
hook: Interceptor::READ_AFTER_SERIALIZATION,
|
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
|
+
|
22
53
|
@app.call(input, context)
|
23
54
|
end
|
24
55
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# in Hearth middleware
|
4
|
+
module Hearth
|
5
|
+
module Middleware
|
6
|
+
# Resolves endpoints from endpoint parameters and modifies
|
7
|
+
# the request with resolved endpoint, headers and auth schemes.
|
8
|
+
class Endpoint
|
9
|
+
include Middleware::Logging
|
10
|
+
|
11
|
+
# @param [Class] app The next middleware in the stack.
|
12
|
+
# @param [#resolve(endpoint_params)] endpoint_resolver An object
|
13
|
+
# that responds to a `resolve(endpoint_params)` method
|
14
|
+
# where `endpoint_params` is a service specific struct.
|
15
|
+
# The method must return an {Hearth::Endpoints::Endpoint} object.
|
16
|
+
# @param [#build(config, input, context)] param_builder An object that
|
17
|
+
# responds to a `build(config, input, context)` method and returns
|
18
|
+
# an endpoint_params object.
|
19
|
+
def initialize(
|
20
|
+
app, endpoint_resolver:, param_builder:, **kwargs
|
21
|
+
)
|
22
|
+
@app = app
|
23
|
+
@param_builder = param_builder
|
24
|
+
@endpoint_resolver = endpoint_resolver
|
25
|
+
@config = kwargs
|
26
|
+
end
|
27
|
+
|
28
|
+
def call(input, context)
|
29
|
+
params = @param_builder.build(@config, input, context)
|
30
|
+
log_debug(context, "Endpoint params: #{params}")
|
31
|
+
endpoint = @endpoint_resolver.resolve(params)
|
32
|
+
log_debug(context, "Resolved endpoint: #{endpoint}")
|
33
|
+
update_request(context, endpoint)
|
34
|
+
log_debug(context, "Updated request: #{context.request}")
|
35
|
+
update_auth_properties(context, endpoint.auth_schemes)
|
36
|
+
log_debug(context, "Updated auth properties: #{context.auth}")
|
37
|
+
|
38
|
+
@app.call(input, context)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
# apply the resolved endpoint to the request
|
44
|
+
def update_request(context, endpoint)
|
45
|
+
context.request.uri = merge_endpoints(
|
46
|
+
URI(endpoint.uri), context.request.uri
|
47
|
+
)
|
48
|
+
endpoint.headers.each do |key, val|
|
49
|
+
context.request.headers[key] = val
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# merge the path and query parameters from serialization
|
54
|
+
# URIs from endpoint resolution MUST contain port and hostname
|
55
|
+
# and MAY contain port and base path.
|
56
|
+
def merge_endpoints(resolved_uri, request_uri)
|
57
|
+
merged = URI(resolved_uri)
|
58
|
+
merged.path = resolved_uri.path + request_uri.path
|
59
|
+
merged.query = request_uri.query
|
60
|
+
merged
|
61
|
+
end
|
62
|
+
|
63
|
+
# merge properties from the endpoint resolved auth schemes onto
|
64
|
+
# the auth scheme resolved by the auth resolver.
|
65
|
+
def update_auth_properties(context, auth_schemes)
|
66
|
+
context[:endpoint_auth_schemes] = auth_schemes
|
67
|
+
return unless context.auth
|
68
|
+
|
69
|
+
matching = auth_schemes.find do |a|
|
70
|
+
context.auth.scheme_id == a.scheme_id
|
71
|
+
end
|
72
|
+
|
73
|
+
return unless matching
|
74
|
+
|
75
|
+
context.auth.signer_properties.merge!(matching.properties)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -3,8 +3,9 @@
|
|
3
3
|
module Hearth
|
4
4
|
module Middleware
|
5
5
|
# A middleware that prefixes the host.
|
6
|
-
# @api private
|
7
6
|
class HostPrefix
|
7
|
+
include Middleware::Logging
|
8
|
+
|
8
9
|
# @param [Class] app The next middleware in the stack.
|
9
10
|
# @param [Boolean] disable_host_prefix If true, this option will not
|
10
11
|
# modify the host url.
|
@@ -21,25 +22,27 @@ module Hearth
|
|
21
22
|
# @param context
|
22
23
|
# @return [Output]
|
23
24
|
def call(input, context)
|
24
|
-
unless @disable_host_prefix
|
25
|
-
prefix = apply_labels(@host_prefix, input)
|
26
|
-
context.request.prefix_host(prefix)
|
27
|
-
end
|
25
|
+
prefix_host(input, context) unless @disable_host_prefix
|
28
26
|
@app.call(input, context)
|
29
27
|
end
|
30
28
|
|
31
29
|
private
|
32
30
|
|
31
|
+
def prefix_host(input, context)
|
32
|
+
log_debug(context, "Prefixing host with #{@host_prefix}")
|
33
|
+
prefix = apply_labels(@host_prefix, input)
|
34
|
+
context.request.prefix_host(prefix)
|
35
|
+
log_debug(context, "Prefixed host: #{context.request.uri.host}")
|
36
|
+
end
|
37
|
+
|
33
38
|
def apply_labels(host_prefix, input)
|
34
|
-
host_prefix.gsub(/\{
|
39
|
+
host_prefix.gsub(/\{.+?}/) do |host_label|
|
35
40
|
key = host_label.delete('{}')
|
36
41
|
value = input[key.to_sym]
|
37
|
-
|
38
42
|
if value.nil? || value.empty?
|
39
43
|
raise ArgumentError,
|
40
44
|
"Host label #{key} cannot be nil or empty."
|
41
45
|
end
|
42
|
-
|
43
46
|
value
|
44
47
|
end
|
45
48
|
end
|
@@ -0,0 +1,57 @@
|
|
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
|
+
class Initialize
|
7
|
+
include Middleware::Logging
|
8
|
+
|
9
|
+
# @param [Class] app The next middleware in the stack.
|
10
|
+
def initialize(app)
|
11
|
+
@app = app
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param input
|
15
|
+
# @param context
|
16
|
+
# @return [Output]
|
17
|
+
def call(input, context)
|
18
|
+
interceptor_error = Interceptors.invoke(
|
19
|
+
hook: Interceptor::READ_BEFORE_EXECUTION,
|
20
|
+
input: input,
|
21
|
+
context: context,
|
22
|
+
output: nil,
|
23
|
+
aggregate_errors: true
|
24
|
+
)
|
25
|
+
|
26
|
+
output =
|
27
|
+
if interceptor_error
|
28
|
+
Hearth::Output.new(error: interceptor_error)
|
29
|
+
else
|
30
|
+
log_debug(context, "Initializing request with #{input}")
|
31
|
+
@app.call(input, context)
|
32
|
+
end
|
33
|
+
log_debug(context, 'Finished request')
|
34
|
+
|
35
|
+
interceptor_error = Interceptors.invoke(
|
36
|
+
hook: Interceptor::MODIFY_BEFORE_COMPLETION,
|
37
|
+
input: input,
|
38
|
+
context: context,
|
39
|
+
output: output,
|
40
|
+
aggregate_errors: false
|
41
|
+
)
|
42
|
+
output.error = interceptor_error if interceptor_error
|
43
|
+
|
44
|
+
interceptor_error = Interceptors.invoke(
|
45
|
+
hook: Interceptor::READ_AFTER_EXECUTION,
|
46
|
+
input: input,
|
47
|
+
context: context,
|
48
|
+
output: output,
|
49
|
+
aggregate_errors: true
|
50
|
+
)
|
51
|
+
output.error = interceptor_error if interceptor_error
|
52
|
+
|
53
|
+
output
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -3,8 +3,9 @@
|
|
3
3
|
module Hearth
|
4
4
|
module Middleware
|
5
5
|
# A middleware that parses a response object.
|
6
|
-
# @api private
|
7
6
|
class Parse
|
7
|
+
include Middleware::Logging
|
8
|
+
|
8
9
|
# @param [Class] app The next middleware in the stack.
|
9
10
|
# @param [Class] error_parser A parser object responsible for parsing the
|
10
11
|
# response if there is an error. It must respond to #parse and take the
|
@@ -23,19 +24,56 @@ module Hearth
|
|
23
24
|
# @return [Output]
|
24
25
|
def call(input, context)
|
25
26
|
output = @app.call(input, context)
|
26
|
-
|
27
|
-
|
27
|
+
|
28
|
+
interceptor_error = Interceptors.invoke(
|
29
|
+
hook: Interceptor::MODIFY_BEFORE_DESERIALIZATION,
|
30
|
+
input: input,
|
31
|
+
context: context,
|
32
|
+
output: output,
|
33
|
+
aggregate_errors: false
|
34
|
+
)
|
35
|
+
if interceptor_error
|
36
|
+
output.error = interceptor_error
|
37
|
+
return output
|
38
|
+
end
|
39
|
+
|
40
|
+
interceptor_error = Interceptors.invoke(
|
41
|
+
hook: Interceptor::READ_BEFORE_DESERIALIZATION,
|
42
|
+
input: input,
|
43
|
+
context: context,
|
44
|
+
output: output,
|
45
|
+
aggregate_errors: false
|
46
|
+
)
|
47
|
+
if interceptor_error
|
48
|
+
output.error = interceptor_error
|
49
|
+
return output
|
50
|
+
end
|
51
|
+
|
52
|
+
parse_error(context, output) unless output.error
|
53
|
+
parse_data(context, output) unless output.error
|
54
|
+
|
55
|
+
interceptor_error = Interceptors.invoke(
|
56
|
+
hook: Interceptor::READ_AFTER_DESERIALIZATION,
|
57
|
+
input: input,
|
58
|
+
context: context,
|
59
|
+
output: output,
|
60
|
+
aggregate_errors: false
|
61
|
+
)
|
62
|
+
output.error = interceptor_error if interceptor_error
|
63
|
+
|
28
64
|
output
|
29
65
|
end
|
30
66
|
|
31
67
|
private
|
32
68
|
|
33
|
-
def parse_error(
|
34
|
-
output.error = @error_parser.parse(response)
|
69
|
+
def parse_error(context, output)
|
70
|
+
output.error = @error_parser.parse(context.response, output.metadata)
|
71
|
+
log_debug(context, "Parsed error: #{output.error}") if output.error
|
35
72
|
end
|
36
73
|
|
37
|
-
def parse_data(
|
38
|
-
output.data = @data_parser.parse(response)
|
74
|
+
def parse_data(context, output)
|
75
|
+
output.data = @data_parser.parse(context.response)
|
76
|
+
log_debug(context, "Parsed data: #{output.data}") if output.data
|
39
77
|
end
|
40
78
|
end
|
41
79
|
end
|