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.
Files changed (176) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -4
  3. data/VERSION +1 -1
  4. data/lib/hearth/anonymous_auth_resolver.rb +11 -0
  5. data/lib/hearth/api_error.rb +15 -1
  6. data/lib/hearth/auth_option.rb +21 -0
  7. data/lib/hearth/auth_schemes/anonymous.rb +21 -0
  8. data/lib/hearth/auth_schemes/http_api_key.rb +16 -0
  9. data/lib/hearth/auth_schemes/http_basic.rb +16 -0
  10. data/lib/hearth/auth_schemes/http_bearer.rb +16 -0
  11. data/lib/hearth/auth_schemes/http_digest.rb +16 -0
  12. data/lib/hearth/auth_schemes.rb +32 -0
  13. data/lib/hearth/checksums.rb +31 -0
  14. data/lib/hearth/client.rb +66 -0
  15. data/lib/hearth/client_stubs.rb +128 -0
  16. data/lib/hearth/config/env_provider.rb +53 -0
  17. data/lib/hearth/config/resolver.rb +53 -0
  18. data/lib/hearth/configuration.rb +15 -0
  19. data/lib/hearth/connection_pool.rb +77 -0
  20. data/lib/hearth/context.rb +29 -4
  21. data/lib/hearth/dns/host_address.rb +27 -0
  22. data/lib/hearth/dns/host_resolver.rb +92 -0
  23. data/lib/hearth/dns.rb +48 -0
  24. data/lib/hearth/endpoint_rules.rb +154 -0
  25. data/lib/hearth/http/api_error.rb +4 -8
  26. data/lib/hearth/http/client.rb +206 -59
  27. data/lib/hearth/http/error_inspector.rb +85 -0
  28. data/lib/hearth/http/error_parser.rb +18 -20
  29. data/lib/hearth/http/field.rb +49 -0
  30. data/lib/hearth/http/fields.rb +117 -0
  31. data/lib/hearth/http/header_list_builder.rb +42 -0
  32. data/lib/hearth/http/header_list_parser.rb +92 -0
  33. data/lib/hearth/http/middleware/content_length.rb +7 -4
  34. data/lib/hearth/http/middleware/content_md5.rb +30 -0
  35. data/lib/hearth/http/middleware/request_compression.rb +154 -0
  36. data/lib/hearth/http/middleware.rb +12 -0
  37. data/lib/hearth/http/networking_error.rb +1 -14
  38. data/lib/hearth/http/request.rb +83 -56
  39. data/lib/hearth/http/response.rb +42 -13
  40. data/lib/hearth/http.rb +16 -5
  41. data/lib/hearth/identities/anonymous.rb +8 -0
  42. data/lib/hearth/identities/http_api_key.rb +16 -0
  43. data/lib/hearth/identities/http_bearer.rb +16 -0
  44. data/lib/hearth/identities/http_login.rb +20 -0
  45. data/lib/hearth/identities.rb +21 -0
  46. data/lib/hearth/identity_provider.rb +17 -0
  47. data/lib/hearth/interceptor.rb +506 -0
  48. data/lib/hearth/interceptor_context.rb +40 -0
  49. data/lib/hearth/interceptor_list.rb +48 -0
  50. data/lib/hearth/interceptors.rb +76 -0
  51. data/lib/hearth/json.rb +4 -4
  52. data/lib/hearth/middleware/auth.rb +103 -0
  53. data/lib/hearth/middleware/build.rb +32 -1
  54. data/lib/hearth/middleware/endpoint.rb +79 -0
  55. data/lib/hearth/middleware/host_prefix.rb +11 -8
  56. data/lib/hearth/middleware/initialize.rb +57 -0
  57. data/lib/hearth/middleware/parse.rb +45 -7
  58. data/lib/hearth/middleware/retry.rb +105 -24
  59. data/lib/hearth/middleware/send.rb +137 -26
  60. data/lib/hearth/middleware/sign.rb +65 -0
  61. data/lib/hearth/middleware/validate.rb +11 -1
  62. data/lib/hearth/middleware.rb +20 -8
  63. data/lib/hearth/middleware_stack.rb +2 -44
  64. data/lib/hearth/networking_error.rb +18 -0
  65. data/lib/hearth/number_helper.rb +3 -3
  66. data/lib/hearth/output.rb +8 -4
  67. data/lib/hearth/plugin_list.rb +53 -0
  68. data/lib/hearth/query/param.rb +56 -0
  69. data/lib/hearth/query/param_list.rb +54 -0
  70. data/lib/hearth/query/param_matcher.rb +31 -0
  71. data/lib/hearth/refreshing_identity_provider.rb +63 -0
  72. data/lib/hearth/request.rb +22 -0
  73. data/lib/hearth/response.rb +36 -0
  74. data/lib/hearth/retry/adaptive.rb +60 -0
  75. data/lib/hearth/retry/capacity_not_available_error.rb +9 -0
  76. data/lib/hearth/retry/client_rate_limiter.rb +145 -0
  77. data/lib/hearth/retry/exponential_backoff.rb +15 -0
  78. data/lib/hearth/retry/retry_quota.rb +56 -0
  79. data/lib/hearth/retry/standard.rb +46 -0
  80. data/lib/hearth/retry.rb +29 -0
  81. data/lib/hearth/signers/anonymous.rb +16 -0
  82. data/lib/hearth/signers/http_api_key.rb +29 -0
  83. data/lib/hearth/signers/http_basic.rb +23 -0
  84. data/lib/hearth/signers/http_bearer.rb +19 -0
  85. data/lib/hearth/signers/http_digest.rb +19 -0
  86. data/lib/hearth/signers.rb +23 -0
  87. data/lib/hearth/structure.rb +7 -3
  88. data/lib/hearth/stubs.rb +38 -0
  89. data/lib/hearth/time_helper.rb +6 -5
  90. data/lib/hearth/validator.rb +60 -5
  91. data/lib/hearth/waiters/poller.rb +10 -9
  92. data/lib/hearth/waiters/waiter.rb +23 -9
  93. data/lib/hearth/xml/formatter.rb +11 -2
  94. data/lib/hearth/xml/node.rb +2 -3
  95. data/lib/hearth/xml/node_matcher.rb +0 -1
  96. data/lib/hearth.rb +37 -6
  97. data/sig/lib/hearth/aliases.rbs +6 -0
  98. data/sig/lib/hearth/anonymous_auth_resolver.rbs +5 -0
  99. data/sig/lib/hearth/api_error.rbs +13 -0
  100. data/sig/lib/hearth/auth_option.rbs +11 -0
  101. data/sig/lib/hearth/auth_schemes/anonymous.rbs +7 -0
  102. data/sig/lib/hearth/auth_schemes/http_api_key.rbs +7 -0
  103. data/sig/lib/hearth/auth_schemes/http_basic.rbs +7 -0
  104. data/sig/lib/hearth/auth_schemes/http_bearer.rbs +7 -0
  105. data/sig/lib/hearth/auth_schemes/http_digest.rbs +7 -0
  106. data/sig/lib/hearth/auth_schemes.rbs +13 -0
  107. data/sig/lib/hearth/block_io.rbs +9 -0
  108. data/sig/lib/hearth/client.rbs +9 -0
  109. data/sig/lib/hearth/client_stubs.rbs +5 -0
  110. data/sig/lib/hearth/configuration.rbs +7 -0
  111. data/sig/lib/hearth/dns/host_address.rbs +11 -0
  112. data/sig/lib/hearth/dns/host_resolver.rbs +19 -0
  113. data/sig/lib/hearth/endpoint_rules.rbs +17 -0
  114. data/sig/lib/hearth/http/api_error.rbs +13 -0
  115. data/sig/lib/hearth/http/client.rbs +9 -0
  116. data/sig/lib/hearth/http/field.rbs +19 -0
  117. data/sig/lib/hearth/http/fields.rbs +43 -0
  118. data/sig/lib/hearth/http/header_list_builder.rbs +15 -0
  119. data/sig/lib/hearth/http/header_list_parser.rbs +19 -0
  120. data/sig/lib/hearth/http/networking_error.rbs +6 -0
  121. data/sig/lib/hearth/http/request.rbs +25 -0
  122. data/sig/lib/hearth/http/response.rbs +21 -0
  123. data/sig/lib/hearth/identities/anonymous.rbs +6 -0
  124. data/sig/lib/hearth/identities/http_api_key.rbs +9 -0
  125. data/sig/lib/hearth/identities/http_bearer.rbs +9 -0
  126. data/sig/lib/hearth/identities/http_login.rbs +11 -0
  127. data/sig/lib/hearth/identities.rbs +9 -0
  128. data/sig/lib/hearth/identity_provider.rbs +7 -0
  129. data/sig/lib/hearth/interceptor.rbs +9 -0
  130. data/sig/lib/hearth/interceptor_context.rbs +17 -0
  131. data/sig/lib/hearth/interceptor_list.rbs +16 -0
  132. data/sig/lib/hearth/interfaces.rbs +87 -0
  133. data/sig/lib/hearth/json/parse_error.rbs +9 -0
  134. data/sig/lib/hearth/networking_error.rbs +7 -0
  135. data/sig/lib/hearth/output.rbs +11 -0
  136. data/sig/lib/hearth/plugin_list.rbs +13 -0
  137. data/sig/lib/hearth/query/param.rbs +17 -0
  138. data/sig/lib/hearth/query/param_list.rbs +25 -0
  139. data/sig/lib/hearth/refreshing_identity_provider.rbs +10 -0
  140. data/sig/lib/hearth/request.rbs +9 -0
  141. data/sig/lib/hearth/response.rbs +11 -0
  142. data/sig/lib/hearth/retry/adaptive.rbs +13 -0
  143. data/sig/lib/hearth/retry/exponential_backoff.rbs +7 -0
  144. data/sig/lib/hearth/retry/standard.rbs +13 -0
  145. data/sig/lib/hearth/retry/strategy.rbs +11 -0
  146. data/sig/lib/hearth/retry.rbs +9 -0
  147. data/sig/lib/hearth/signers/anonymous.rbs +9 -0
  148. data/sig/lib/hearth/signers/http_api_key.rbs +9 -0
  149. data/sig/lib/hearth/signers/http_basic.rbs +9 -0
  150. data/sig/lib/hearth/signers/http_bearer.rbs +9 -0
  151. data/sig/lib/hearth/signers/http_digest.rbs +9 -0
  152. data/sig/lib/hearth/signers.rbs +9 -0
  153. data/sig/lib/hearth/structure.rbs +6 -0
  154. data/sig/lib/hearth/stubs.rbs +9 -0
  155. data/sig/lib/hearth/union.rbs +5 -0
  156. data/sig/lib/hearth/waiters/waiter.rbs +17 -0
  157. data/sig/lib/hearth/xml/parse_error.rbs +9 -0
  158. metadata +151 -25
  159. data/lib/hearth/http/headers.rb +0 -70
  160. data/lib/hearth/middleware/around_handler.rb +0 -24
  161. data/lib/hearth/middleware/request_handler.rb +0 -24
  162. data/lib/hearth/middleware/response_handler.rb +0 -25
  163. data/lib/hearth/middleware_builder.rb +0 -246
  164. data/lib/hearth/stubbing/client_stubs.rb +0 -115
  165. data/lib/hearth/stubbing/stubs.rb +0 -32
  166. data/lib/hearth/waiters/errors.rb +0 -15
  167. data/sig/lib/seahorse/api_error.rbs +0 -10
  168. data/sig/lib/seahorse/document.rbs +0 -2
  169. data/sig/lib/seahorse/http/api_error.rbs +0 -21
  170. data/sig/lib/seahorse/http/headers.rbs +0 -47
  171. data/sig/lib/seahorse/http/response.rbs +0 -21
  172. data/sig/lib/seahorse/simple_delegator.rbs +0 -3
  173. data/sig/lib/seahorse/structure.rbs +0 -18
  174. data/sig/lib/seahorse/stubbing/client_stubs.rbs +0 -103
  175. data/sig/lib/seahorse/stubbing/stubs.rbs +0 -14
  176. 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 load(json)
16
- # rubocop:disable Security/JSONLoad
17
- ::JSON.load(json)
18
- # rubocop:enable Security/JSONLoad
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(/\{.+?\}/) do |host_label|
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
- parse_error(context.response, output) unless output.error
27
- parse_data(context.response, output) unless output.error
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(response, output)
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(response, output)
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