hearth 1.0.0.pre2 → 1.0.0.pre3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/VERSION +1 -1
  4. data/lib/hearth/anonymous_auth_resolver.rb +11 -0
  5. data/lib/hearth/auth_schemes/anonymous.rb +3 -3
  6. data/lib/hearth/auth_schemes.rb +3 -3
  7. data/lib/hearth/client.rb +66 -0
  8. data/lib/hearth/client_stubs.rb +1 -3
  9. data/lib/hearth/config/resolver.rb +6 -5
  10. data/lib/hearth/context.rb +1 -0
  11. data/lib/hearth/dns/host_address.rb +20 -16
  12. data/lib/hearth/endpoint_rules.rb +154 -0
  13. data/lib/hearth/http/client.rb +5 -7
  14. data/lib/hearth/http/error_inspector.rb +2 -2
  15. data/lib/hearth/http/field.rb +4 -19
  16. data/lib/hearth/http/header_list_builder.rb +42 -0
  17. data/lib/hearth/http/header_list_parser.rb +92 -0
  18. data/lib/hearth/http/middleware/content_length.rb +3 -3
  19. data/lib/hearth/http/middleware/content_md5.rb +0 -1
  20. data/lib/hearth/http/middleware/request_compression.rb +7 -10
  21. data/lib/hearth/http.rb +2 -0
  22. data/lib/hearth/{identity_resolver.rb → identity_provider.rb} +1 -1
  23. data/lib/hearth/interceptor_context.rb +8 -4
  24. data/lib/hearth/interceptors.rb +2 -1
  25. data/lib/hearth/json.rb +4 -4
  26. data/lib/hearth/middleware/auth.rb +9 -6
  27. data/lib/hearth/middleware/build.rb +0 -1
  28. data/lib/hearth/middleware/endpoint.rb +79 -0
  29. data/lib/hearth/middleware/host_prefix.rb +1 -2
  30. data/lib/hearth/middleware/initialize.rb +0 -1
  31. data/lib/hearth/middleware/parse.rb +0 -1
  32. data/lib/hearth/middleware/retry.rb +9 -2
  33. data/lib/hearth/middleware/send.rb +0 -1
  34. data/lib/hearth/middleware.rb +1 -0
  35. data/lib/hearth/middleware_stack.rb +1 -1
  36. data/lib/hearth/number_helper.rb +1 -1
  37. data/lib/hearth/query/param.rb +7 -3
  38. data/lib/hearth/query/param_matcher.rb +5 -6
  39. data/lib/hearth/{refreshing_identity_resolver.rb → refreshing_identity_provider.rb} +2 -2
  40. data/lib/hearth/request.rb +2 -2
  41. data/lib/hearth/response.rb +5 -2
  42. data/lib/hearth/retry/adaptive.rb +2 -2
  43. data/lib/hearth/retry/client_rate_limiter.rb +8 -6
  44. data/lib/hearth/retry/exponential_backoff.rb +1 -1
  45. data/lib/hearth/retry/standard.rb +2 -2
  46. data/lib/hearth/retry.rb +16 -3
  47. data/lib/hearth/structure.rb +7 -3
  48. data/lib/hearth/stubs.rb +12 -4
  49. data/lib/hearth/time_helper.rb +1 -2
  50. data/lib/hearth/validator.rb +37 -21
  51. data/lib/hearth/waiters/poller.rb +4 -2
  52. data/lib/hearth/waiters/waiter.rb +6 -5
  53. data/lib/hearth/xml/node.rb +0 -1
  54. data/lib/hearth/xml/node_matcher.rb +0 -1
  55. data/lib/hearth.rb +8 -4
  56. data/sig/lib/hearth/aliases.rbs +5 -3
  57. data/sig/lib/hearth/anonymous_auth_resolver.rbs +5 -0
  58. data/sig/lib/hearth/auth_schemes.rbs +1 -1
  59. data/sig/lib/hearth/client.rbs +9 -0
  60. data/sig/lib/hearth/configuration.rbs +2 -2
  61. data/sig/lib/hearth/dns/host_address.rbs +1 -3
  62. data/sig/lib/hearth/dns/host_resolver.rbs +3 -3
  63. data/sig/lib/hearth/endpoint_rules.rbs +17 -0
  64. data/sig/lib/hearth/http/field.rbs +1 -1
  65. data/sig/lib/hearth/http/fields.rbs +1 -1
  66. data/sig/lib/hearth/http/header_list_builder.rbs +15 -0
  67. data/sig/lib/hearth/http/header_list_parser.rbs +19 -0
  68. data/sig/lib/hearth/http/networking_error.rbs +6 -0
  69. data/sig/lib/hearth/http/response.rbs +1 -1
  70. data/sig/lib/hearth/identities.rbs +1 -1
  71. data/sig/lib/hearth/{identity_resolver.rbs → identity_provider.rbs} +1 -1
  72. data/sig/lib/hearth/interceptor_context.rbs +4 -2
  73. data/sig/lib/hearth/interfaces.rbs +52 -30
  74. data/sig/lib/hearth/json/parse_error.rbs +9 -0
  75. data/sig/lib/hearth/networking_error.rbs +7 -0
  76. data/sig/lib/hearth/output.rbs +4 -4
  77. data/sig/lib/hearth/plugin_list.rbs +5 -7
  78. data/sig/lib/hearth/query/param.rbs +2 -2
  79. data/sig/lib/hearth/refreshing_identity_provider.rbs +10 -0
  80. data/sig/lib/hearth/request.rbs +2 -2
  81. data/sig/lib/hearth/response.rbs +2 -2
  82. data/sig/lib/hearth/retry/exponential_backoff.rbs +1 -1
  83. data/sig/lib/hearth/retry.rbs +1 -1
  84. data/sig/lib/hearth/structure.rbs +1 -2
  85. data/sig/lib/hearth/stubs.rbs +9 -0
  86. data/sig/lib/hearth/union.rbs +1 -1
  87. data/sig/lib/hearth/xml/parse_error.rbs +9 -0
  88. metadata +26 -10
  89. data/lib/hearth/retry/strategy.rb +0 -20
@@ -5,7 +5,6 @@ module Hearth
5
5
  module Middleware
6
6
  # A middleware that compresses the request body and
7
7
  # adds the Content-Encoding header
8
- # @api private
9
8
  class RequestCompression
10
9
  include Hearth::Middleware::Logging
11
10
 
@@ -62,17 +61,16 @@ module Hearth
62
61
  def update_content_encoding(encoding, request)
63
62
  headers = request.headers
64
63
  if headers['Content-Encoding']
65
- headers['Content-Encoding'] += ",#{encoding}"
64
+ headers['Content-Encoding'] += ", #{encoding}"
66
65
  else
67
66
  headers['Content-Encoding'] = encoding
68
67
  end
69
68
  end
70
69
 
71
70
  def compress_body(encoding, request)
72
- case encoding
73
- when 'gzip'
74
- gzip_compress(request)
75
- end
71
+ return unless encoding == 'gzip'
72
+
73
+ gzip_compress(request)
76
74
  update_content_encoding(encoding, request)
77
75
  end
78
76
 
@@ -100,10 +98,9 @@ module Hearth
100
98
  end
101
99
 
102
100
  def compress_streaming_body(encoding, request)
103
- case encoding
104
- when 'gzip'
105
- request.body = GzipIO.new(request.body)
106
- end
101
+ return unless encoding == 'gzip'
102
+
103
+ request.body = GzipIO.new(request.body)
107
104
  update_content_encoding(encoding, request)
108
105
  end
109
106
 
data/lib/hearth/http.rb CHANGED
@@ -7,6 +7,8 @@ require_relative 'http/error_inspector'
7
7
  require_relative 'http/error_parser'
8
8
  require_relative 'http/field'
9
9
  require_relative 'http/fields'
10
+ require_relative 'http/header_list_builder'
11
+ require_relative 'http/header_list_parser'
10
12
  require_relative 'http/middleware'
11
13
  require_relative 'http/networking_error'
12
14
  require_relative 'http/request'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Hearth
4
4
  # Basic Identity resolver that uses a proc to resolve an Identity.
5
- class IdentityResolver
5
+ class IdentityProvider
6
6
  # @param [Proc] proc A proc that takes identity properties (Hash)
7
7
  # and returns a {Hearth::Identities::Base}.
8
8
  def initialize(proc)
@@ -5,17 +5,18 @@ module Hearth
5
5
  # context to read and modify the input, request, response, and output.
6
6
  # Attributes can be used to pass additional data between interceptors.
7
7
  class InterceptorContext
8
- # @param [Struct] input
8
+ # @param [Hearth::Structure] input
9
9
  # @param [Hearth::Request] request
10
10
  # @param [Hearth::Response] response
11
11
  # @param [Hearth::Output] output
12
- # @param [Hash] attributes ({}) Additional interceptor data
13
- def initialize(input:, request:, response:, output:, attributes: {})
12
+ # @param [Logger] logger
13
+ def initialize(input:, request:, response:, output:, logger:)
14
14
  @input = input
15
15
  @request = request
16
16
  @response = response
17
17
  @output = output
18
- @attributes = attributes
18
+ @logger = logger
19
+ @attributes = {}
19
20
  end
20
21
 
21
22
  # @return [Struct] Modeled input, i.e. Types::<Operation>Input
@@ -30,6 +31,9 @@ module Hearth
30
31
  # @return [Hearth::Output] Operation output
31
32
  attr_reader :output
32
33
 
34
+ # @return [Logger] logger
35
+ attr_reader :logger
36
+
33
37
  # @return [Hash] attributes Additional interceptor data
34
38
  attr_reader :attributes
35
39
  end
@@ -42,7 +42,8 @@ module Hearth
42
42
  input: input,
43
43
  request: context.request,
44
44
  response: context.response,
45
- output: output
45
+ output: output,
46
+ logger: context.logger
46
47
  )
47
48
  end
48
49
 
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
@@ -3,7 +3,6 @@
3
3
  module Hearth
4
4
  module Middleware
5
5
  # A middleware that resolves identities for signing requests.
6
- # @api private
7
6
  class Auth
8
7
  include Middleware::Logging
9
8
 
@@ -23,11 +22,11 @@ module Hearth
23
22
  @auth_params = auth_params
24
23
  @auth_schemes = auth_schemes.to_h { |s| [s.scheme_id, s] }
25
24
 
26
- @identity_resolvers = {}
25
+ @identity_providers = {}
27
26
  kwargs.each do |key, value|
28
27
  next unless key.superclass == Hearth::Identities::Base
29
28
 
30
- @identity_resolvers[key] = value
29
+ @identity_providers[key] = value
31
30
  end
32
31
  end
33
32
 
@@ -46,6 +45,7 @@ module Hearth
46
45
  private
47
46
 
48
47
  ResolvedAuth = Struct.new(
48
+ :scheme_id,
49
49
  :signer,
50
50
  :signer_properties,
51
51
  :identity,
@@ -56,6 +56,8 @@ module Hearth
56
56
  def resolve_auth(auth_options)
57
57
  failures = []
58
58
 
59
+ raise 'No auth options were resolved' if auth_options.empty?
60
+
59
61
  auth_options.each do |auth_option|
60
62
  auth_scheme = @auth_schemes[auth_option.scheme_id]
61
63
  resolved_auth = try_load_auth_scheme(
@@ -78,17 +80,18 @@ module Hearth
78
80
  return
79
81
  end
80
82
 
81
- identity_resolver = auth_scheme.identity_resolver(@identity_resolvers)
82
- unless identity_resolver
83
+ identity_provider = auth_scheme.identity_provider(@identity_providers)
84
+ unless identity_provider
83
85
  failures << "Auth scheme #{scheme_id} did not have an " \
84
86
  'identity resolver configured'
85
87
  return
86
88
  end
87
89
 
88
90
  identity_properties = auth_option.identity_properties
89
- identity = identity_resolver.identity(identity_properties)
91
+ identity = identity_provider.identity(identity_properties)
90
92
 
91
93
  ResolvedAuth.new(
94
+ scheme_id: scheme_id,
92
95
  identity: identity,
93
96
  identity_properties: auth_option.identity_properties,
94
97
  signer: auth_scheme.signer,
@@ -3,7 +3,6 @@
3
3
  module Hearth
4
4
  module Middleware
5
5
  # A middleware that builds a request object.
6
- # @api private
7
6
  class Build
8
7
  include Middleware::Logging
9
8
 
@@ -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,7 +3,6 @@
3
3
  module Hearth
4
4
  module Middleware
5
5
  # A middleware that prefixes the host.
6
- # @api private
7
6
  class HostPrefix
8
7
  include Middleware::Logging
9
8
 
@@ -37,7 +36,7 @@ module Hearth
37
36
  end
38
37
 
39
38
  def apply_labels(host_prefix, input)
40
- host_prefix.gsub(/\{.+?\}/) do |host_label|
39
+ host_prefix.gsub(/\{.+?}/) do |host_label|
41
40
  key = host_label.delete('{}')
42
41
  value = input[key.to_sym]
43
42
  if value.nil? || value.empty?
@@ -3,7 +3,6 @@
3
3
  module Hearth
4
4
  module Middleware
5
5
  # A middleware used to initialize the request, called first in the stack
6
- # @api private
7
6
  class Initialize
8
7
  include Middleware::Logging
9
8
 
@@ -3,7 +3,6 @@
3
3
  module Hearth
4
4
  module Middleware
5
5
  # A middleware that parses a response object.
6
- # @api private
7
6
  class Parse
8
7
  include Middleware::Logging
9
8
 
@@ -3,7 +3,6 @@
3
3
  module Hearth
4
4
  module Middleware
5
5
  # A middleware that retries the request using a retry strategy.
6
- # @api private
7
6
  class Retry
8
7
  include Middleware::Logging
9
8
 
@@ -75,6 +74,8 @@ module Hearth
75
74
 
76
75
  if (error = output.error)
77
76
  log_debug(context, "Request failed with error: #{error}")
77
+ break unless retryable?(context.request)
78
+
78
79
  error_info = @error_inspector_class.new(error, context.response)
79
80
  token = @retry_strategy.refresh_retry_token(token, error_info)
80
81
  break unless token
@@ -93,14 +94,20 @@ module Hearth
93
94
  @retries += 1
94
95
  end
95
96
  log_debug(context, 'Finished retry loop')
97
+ log_debug(context, "Total retries: #{@retries}")
96
98
  output
97
99
  end
98
100
 
99
101
  private
100
102
 
103
+ def retryable?(request)
104
+ # IO responds to #rewind however it returns an illegal seek error
105
+ request.body.respond_to?(:rewind) && !request.body.instance_of?(IO)
106
+ end
107
+
101
108
  def reset_request(context)
102
109
  request = context.request
103
- request.body.rewind if request.body.respond_to?(:rewind)
110
+ request.body.rewind
104
111
 
105
112
  context.auth.signer.reset(
106
113
  request: request,
@@ -3,7 +3,6 @@
3
3
  module Hearth
4
4
  module Middleware
5
5
  # A middleware used to send the request.
6
- # @api private
7
6
  class Send
8
7
  include Middleware::Logging
9
8
 
@@ -18,6 +18,7 @@ end
18
18
 
19
19
  require_relative 'middleware/auth'
20
20
  require_relative 'middleware/build'
21
+ require_relative 'middleware/endpoint'
21
22
  require_relative 'middleware/host_prefix'
22
23
  require_relative 'middleware/parse'
23
24
  require_relative 'middleware/retry'
@@ -15,7 +15,7 @@ module Hearth
15
15
  # @param context
16
16
  # @return [Output]
17
17
  def run(input, context)
18
- stack.call(input, context)
18
+ stack&.call(input, context) || Output.new
19
19
  end
20
20
 
21
21
  private
@@ -18,7 +18,7 @@ module Hearth
18
18
  end
19
19
 
20
20
  # @param [String] str
21
- # @return [Number] The input as a number
21
+ # @return [Float, nil] The input as a number
22
22
  def deserialize(str)
23
23
  case str
24
24
  when 'Infinity' then ::Float::INFINITY
@@ -5,7 +5,7 @@ module Hearth
5
5
  # A class used to represent a query parameter before serialization.
6
6
  class Param
7
7
  # @param [String] name
8
- # @param [String, Array<String>] value (nil)
8
+ # @param [String, Array<String>, nil] value (nil)
9
9
  def initialize(name, value = nil)
10
10
  @name = name
11
11
  @value = value
@@ -20,7 +20,7 @@ module Hearth
20
20
  # @return [String]
21
21
  def to_s
22
22
  if value.is_a?(Array)
23
- value.map { |v| serialize(name, v) }.join('&')
23
+ serialize_array(name, value)
24
24
  else
25
25
  serialize(name, value)
26
26
  end
@@ -33,7 +33,7 @@ module Hearth
33
33
  other.value == value
34
34
  end
35
35
 
36
- # @return [Boolean]
36
+ # @return [Integer]
37
37
  def <=>(other)
38
38
  name <=> other.name
39
39
  end
@@ -44,6 +44,10 @@ module Hearth
44
44
  value.nil? ? escape(name) : "#{escape(name)}=#{escape(value)}"
45
45
  end
46
46
 
47
+ def serialize_array(name, values)
48
+ values.map { |v| serialize(name, v) }.join('&')
49
+ end
50
+
47
51
  def escape(value)
48
52
  Hearth::HTTP.uri_escape(value.to_s)
49
53
  end
@@ -3,7 +3,6 @@
3
3
  require 'rspec/expectations'
4
4
 
5
5
  # Provides an rspec matcher for CGI.parse to check Float precision.
6
- # @api private
7
6
  RSpec::Matchers.define :match_query_params do |expected|
8
7
  match do |actual|
9
8
  return true if actual == expected
@@ -19,11 +18,11 @@ RSpec::Matchers.define :match_query_params do |expected|
19
18
 
20
19
  a.zip(e).each do |a0, e0|
21
20
  # Timestamps can have optional precision.
22
- if (float = Float(a0) rescue false)
23
- expect(float).to eq(e0.to_f)
24
- else
25
- expect(a0).to eq(e0)
26
- end
21
+
22
+ float = Float(a0)
23
+ expect(float).to eq(e0.to_f)
24
+ rescue StandardError
25
+ expect(a0).to eq(e0)
27
26
  end
28
27
  end
29
28
  end
@@ -5,7 +5,7 @@ module Hearth
5
5
  # The class must implement #refresh(properties) that sets @identity. The
6
6
  # refresh method will be called when #identity is called and the identity
7
7
  # is nil or near expiration.
8
- module RefreshingIdentityResolver
8
+ module RefreshingIdentityProvider
9
9
  SYNC_EXPIRATION_LENGTH = 300 # 5 minutes
10
10
  ASYNC_EXPIRATION_LENGTH = 600 # 10 minutes
11
11
 
@@ -13,7 +13,7 @@ module Hearth
13
13
  @mutex = Mutex.new
14
14
  end
15
15
 
16
- # @return [Identity]
16
+ # @return [Identities::Base]
17
17
  def identity(properties = {})
18
18
  if @identity
19
19
  refresh_if_near_expiration!(properties)
@@ -7,7 +7,7 @@ module Hearth
7
7
  # Represents a base request.
8
8
  class Request
9
9
  # @param [URI] uri (URI(''))
10
- # @param [IO] body (StringIO.new)
10
+ # @param [IO, StringIO] body (StringIO.new)
11
11
  def initialize(uri: URI(''), body: StringIO.new)
12
12
  @uri = uri
13
13
  @body = body
@@ -16,7 +16,7 @@ module Hearth
16
16
  # @return [URI]
17
17
  attr_accessor :uri
18
18
 
19
- # @return [IO]
19
+ # @return [IO, StringIO]
20
20
  attr_accessor :body
21
21
  end
22
22
  end
@@ -5,12 +5,12 @@ require 'stringio'
5
5
  module Hearth
6
6
  # Represents a base response.
7
7
  class Response
8
- # @param [IO] body (StringIO.new)
8
+ # @param [IO, StringIO] body (StringIO.new)
9
9
  def initialize(body: StringIO.new)
10
10
  @body = body
11
11
  end
12
12
 
13
- # @return [IO]
13
+ # @return [IO, StringIO]
14
14
  attr_accessor :body
15
15
 
16
16
  # Replace attributes from other response
@@ -26,7 +26,10 @@ module Hearth
26
26
  # Resets the response.
27
27
  # @return [Response]
28
28
  def reset
29
+ # IO does not respond to #truncate but it does respond to #rewind
30
+ # however it returns an illegal seek error.
29
31
  @body.truncate(0) if @body.respond_to?(:truncate)
32
+ @body.rewind if @body.respond_to?(:rewind) && !@body.instance_of?(IO)
30
33
  self
31
34
  end
32
35
  end
@@ -3,7 +3,7 @@
3
3
  module Hearth
4
4
  module Retry
5
5
  # Adaptive retry strategy for retrying requests.
6
- class Adaptive < Strategy
6
+ class Adaptive
7
7
  # @param [#call] backoff (ExponentialBackoff) A callable object that
8
8
  # calculates a backoff delay for a retry attempt.
9
9
  # @param [Integer] max_attempts (3) The maximum number of attempts that
@@ -22,7 +22,7 @@ module Hearth
22
22
  # instance state
23
23
  @client_rate_limiter = ClientRateLimiter.new
24
24
  @retry_quota = RetryQuota.new
25
- @capacity_amount = nil
25
+ @capacity_amount = 0
26
26
  end
27
27
 
28
28
  def acquire_initial_retry_token(_token_scope = nil)
@@ -52,12 +52,6 @@ module Hearth
52
52
  update_measured_rate
53
53
 
54
54
  if is_throttling_error
55
- rate_to_use = if @enabled
56
- [@measured_tx_rate, @fill_rate].min
57
- else
58
- @measured_tx_rate
59
- end
60
-
61
55
  # The fill_rate is from the token bucket
62
56
  @last_max_rate = rate_to_use
63
57
  calculate_time_window
@@ -138,6 +132,14 @@ module Hearth
138
132
  def cubic_throttle(rate_to_use)
139
133
  rate_to_use * BETA
140
134
  end
135
+
136
+ def rate_to_use
137
+ if @enabled
138
+ [@measured_tx_rate, @fill_rate].min
139
+ else
140
+ @measured_tx_rate
141
+ end
142
+ end
141
143
  end
142
144
  end
143
145
  end
@@ -8,7 +8,7 @@ module Hearth
8
8
  MAX_BACKOFF = 20
9
9
 
10
10
  def call(attempts)
11
- [Kernel.rand * (2**attempts), MAX_BACKOFF].min
11
+ [Kernel.rand * (2**attempts), MAX_BACKOFF].min || 0
12
12
  end
13
13
  end
14
14
  end
@@ -3,7 +3,7 @@
3
3
  module Hearth
4
4
  module Retry
5
5
  # Standard retry strategy for retrying requests.
6
- class Standard < Strategy
6
+ class Standard
7
7
  # @param [#call] backoff (ExponentialBackoff) A callable object that
8
8
  # calculates a backoff delay for a retry attempt.
9
9
  # @param [Integer] max_attempts (3) The maximum number of attempts that
@@ -15,7 +15,7 @@ module Hearth
15
15
 
16
16
  # instance state
17
17
  @retry_quota = RetryQuota.new
18
- @capacity_amount = nil
18
+ @capacity_amount = 0
19
19
  end
20
20
 
21
21
  def acquire_initial_retry_token(_token_scope = nil)
data/lib/hearth/retry.rb CHANGED
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'retry/strategy'
4
-
5
3
  require_relative 'retry/adaptive'
6
4
  require_relative 'retry/capacity_not_available_error'
7
5
  require_relative 'retry/client_rate_limiter'
@@ -11,6 +9,21 @@ require_relative 'retry/standard'
11
9
 
12
10
  module Hearth
13
11
  module Retry
14
- Token = Struct.new(:retry_count, :retry_delay, keyword_init: true)
12
+ # Represents a token that can be used to retry an operation.
13
+ # @!attribute retry_count
14
+ # The number of times the operation has been retried.
15
+ # @return [Integer]
16
+ # @!attribute retry_delay
17
+ # The delay before the next retry.
18
+ # @return [Numeric]
19
+ Token = Struct.new(:retry_count, :retry_delay, keyword_init: true) do
20
+ # @option args [Integer] :retry_count (0)
21
+ # @option args [Numeric] :retry_delay (0)
22
+ def initialize(*args)
23
+ super
24
+ self.retry_count ||= 0
25
+ self.retry_delay ||= 0
26
+ end
27
+ end
15
28
  end
16
29
  end
@@ -6,15 +6,15 @@ module Hearth
6
6
  # Deeply converts the Struct into a hash. Structure members that
7
7
  # are `nil` are omitted from the resultant hash.
8
8
  #
9
- # @return [Hash]
9
+ # @return [Hash, Structure]
10
10
  def to_h(obj = self)
11
11
  case obj
12
12
  when Struct
13
13
  _to_h_struct(obj)
14
14
  when Hash
15
15
  _to_h_hash(obj)
16
- when Array, Set
17
- obj.collect { |value| to_hash(value) }
16
+ when Array
17
+ _to_h_array(obj)
18
18
  when Union
19
19
  obj.to_h
20
20
  else
@@ -36,5 +36,9 @@ module Hearth
36
36
  hash[key] = to_hash(value)
37
37
  end
38
38
  end
39
+
40
+ def _to_h_array(obj)
41
+ obj.collect { |value| to_hash(value) }
42
+ end
39
43
  end
40
44
  end