httpx 0.7.0 → 0.10.0

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.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +48 -0
  3. data/README.md +9 -5
  4. data/doc/release_notes/0_0_1.md +7 -0
  5. data/doc/release_notes/0_0_2.md +9 -0
  6. data/doc/release_notes/0_0_3.md +9 -0
  7. data/doc/release_notes/0_0_4.md +7 -0
  8. data/doc/release_notes/0_0_5.md +5 -0
  9. data/doc/release_notes/0_10_0.md +66 -0
  10. data/doc/release_notes/0_1_0.md +9 -0
  11. data/doc/release_notes/0_2_0.md +5 -0
  12. data/doc/release_notes/0_2_1.md +16 -0
  13. data/doc/release_notes/0_3_0.md +12 -0
  14. data/doc/release_notes/0_3_1.md +6 -0
  15. data/doc/release_notes/0_4_0.md +51 -0
  16. data/doc/release_notes/0_4_1.md +3 -0
  17. data/doc/release_notes/0_5_0.md +15 -0
  18. data/doc/release_notes/0_5_1.md +14 -0
  19. data/doc/release_notes/0_6_0.md +5 -0
  20. data/doc/release_notes/0_6_1.md +6 -0
  21. data/doc/release_notes/0_6_2.md +6 -0
  22. data/doc/release_notes/0_6_3.md +13 -0
  23. data/doc/release_notes/0_6_4.md +21 -0
  24. data/doc/release_notes/0_6_5.md +22 -0
  25. data/doc/release_notes/0_6_6.md +19 -0
  26. data/doc/release_notes/0_6_7.md +5 -0
  27. data/doc/release_notes/0_7_0.md +46 -0
  28. data/doc/release_notes/0_8_0.md +27 -0
  29. data/doc/release_notes/0_8_1.md +8 -0
  30. data/doc/release_notes/0_8_2.md +7 -0
  31. data/doc/release_notes/0_9_0.md +38 -0
  32. data/lib/httpx.rb +2 -0
  33. data/lib/httpx/adapters/faraday.rb +1 -1
  34. data/lib/httpx/altsvc.rb +18 -2
  35. data/lib/httpx/chainable.rb +9 -8
  36. data/lib/httpx/connection.rb +177 -72
  37. data/lib/httpx/connection/http1.rb +44 -13
  38. data/lib/httpx/connection/http2.rb +77 -34
  39. data/lib/httpx/domain_name.rb +440 -0
  40. data/lib/httpx/errors.rb +1 -0
  41. data/lib/httpx/extensions.rb +23 -3
  42. data/lib/httpx/headers.rb +2 -2
  43. data/lib/httpx/io/ssl.rb +11 -4
  44. data/lib/httpx/io/tcp.rb +16 -5
  45. data/lib/httpx/io/udp.rb +4 -1
  46. data/lib/httpx/loggable.rb +6 -6
  47. data/lib/httpx/options.rb +22 -15
  48. data/lib/httpx/parser/http1.rb +14 -17
  49. data/lib/httpx/plugins/compression.rb +49 -64
  50. data/lib/httpx/plugins/compression/brotli.rb +10 -14
  51. data/lib/httpx/plugins/compression/deflate.rb +7 -6
  52. data/lib/httpx/plugins/compression/gzip.rb +45 -17
  53. data/lib/httpx/plugins/cookies.rb +21 -60
  54. data/lib/httpx/plugins/cookies/cookie.rb +173 -0
  55. data/lib/httpx/plugins/cookies/jar.rb +74 -0
  56. data/lib/httpx/plugins/cookies/set_cookie_parser.rb +142 -0
  57. data/lib/httpx/plugins/digest_authentication.rb +2 -0
  58. data/lib/httpx/plugins/expect.rb +12 -1
  59. data/lib/httpx/plugins/follow_redirects.rb +20 -2
  60. data/lib/httpx/plugins/h2c.rb +1 -1
  61. data/lib/httpx/plugins/multipart.rb +0 -8
  62. data/lib/httpx/plugins/persistent.rb +6 -1
  63. data/lib/httpx/plugins/proxy.rb +16 -12
  64. data/lib/httpx/plugins/proxy/http.rb +7 -2
  65. data/lib/httpx/plugins/proxy/socks4.rb +4 -2
  66. data/lib/httpx/plugins/proxy/socks5.rb +5 -1
  67. data/lib/httpx/plugins/push_promise.rb +2 -2
  68. data/lib/httpx/plugins/rate_limiter.rb +51 -0
  69. data/lib/httpx/plugins/retries.rb +13 -6
  70. data/lib/httpx/plugins/stream.rb +109 -13
  71. data/lib/httpx/pool.rb +13 -15
  72. data/lib/httpx/registry.rb +2 -1
  73. data/lib/httpx/request.rb +14 -19
  74. data/lib/httpx/resolver.rb +7 -8
  75. data/lib/httpx/resolver/https.rb +22 -5
  76. data/lib/httpx/resolver/native.rb +27 -33
  77. data/lib/httpx/resolver/options.rb +2 -2
  78. data/lib/httpx/resolver/resolver_mixin.rb +1 -1
  79. data/lib/httpx/response.rb +22 -17
  80. data/lib/httpx/selector.rb +96 -97
  81. data/lib/httpx/session.rb +32 -24
  82. data/lib/httpx/timeout.rb +7 -1
  83. data/lib/httpx/transcoder/chunker.rb +0 -2
  84. data/lib/httpx/transcoder/form.rb +0 -6
  85. data/lib/httpx/transcoder/json.rb +0 -4
  86. data/lib/httpx/utils.rb +45 -0
  87. data/lib/httpx/version.rb +1 -1
  88. data/sig/buffer.rbs +24 -0
  89. data/sig/callbacks.rbs +14 -0
  90. data/sig/chainable.rbs +37 -0
  91. data/sig/connection.rbs +2 -0
  92. data/sig/connection/http2.rbs +4 -0
  93. data/sig/domain_name.rbs +17 -0
  94. data/sig/errors.rbs +3 -0
  95. data/sig/headers.rbs +42 -0
  96. data/sig/httpx.rbs +14 -0
  97. data/sig/loggable.rbs +11 -0
  98. data/sig/missing.rbs +12 -0
  99. data/sig/options.rbs +118 -0
  100. data/sig/parser/http1.rbs +50 -0
  101. data/sig/plugins/authentication.rbs +11 -0
  102. data/sig/plugins/basic_authentication.rbs +13 -0
  103. data/sig/plugins/compression.rbs +55 -0
  104. data/sig/plugins/compression/brotli.rbs +21 -0
  105. data/sig/plugins/compression/deflate.rbs +17 -0
  106. data/sig/plugins/compression/gzip.rbs +29 -0
  107. data/sig/plugins/cookies.rbs +26 -0
  108. data/sig/plugins/cookies/cookie.rbs +50 -0
  109. data/sig/plugins/cookies/jar.rbs +27 -0
  110. data/sig/plugins/digest_authentication.rbs +33 -0
  111. data/sig/plugins/expect.rbs +19 -0
  112. data/sig/plugins/follow_redirects.rbs +37 -0
  113. data/sig/plugins/h2c.rbs +26 -0
  114. data/sig/plugins/multipart.rbs +19 -0
  115. data/sig/plugins/persistent.rbs +17 -0
  116. data/sig/plugins/proxy.rbs +47 -0
  117. data/sig/plugins/proxy/http.rbs +14 -0
  118. data/sig/plugins/proxy/socks4.rbs +33 -0
  119. data/sig/plugins/proxy/socks5.rbs +36 -0
  120. data/sig/plugins/proxy/ssh.rbs +18 -0
  121. data/sig/plugins/push_promise.rbs +22 -0
  122. data/sig/plugins/rate_limiter.rbs +11 -0
  123. data/sig/plugins/retries.rbs +48 -0
  124. data/sig/plugins/stream.rbs +39 -0
  125. data/sig/pool.rbs +2 -0
  126. data/sig/registry.rbs +9 -0
  127. data/sig/request.rbs +61 -0
  128. data/sig/response.rbs +87 -0
  129. data/sig/session.rbs +49 -0
  130. data/sig/test.rbs +9 -0
  131. data/sig/timeout.rbs +29 -0
  132. data/sig/transcoder.rbs +16 -0
  133. data/sig/transcoder/body.rbs +18 -0
  134. data/sig/transcoder/chunker.rbs +32 -0
  135. data/sig/transcoder/form.rbs +16 -0
  136. data/sig/transcoder/json.rbs +14 -0
  137. metadata +120 -21
@@ -5,7 +5,9 @@ module HTTPX
5
5
  include Loggable
6
6
  include Chainable
7
7
 
8
- def initialize(options = {}, &blk)
8
+ EMPTY_HASH = {}.freeze
9
+
10
+ def initialize(options = EMPTY_HASH, &blk)
9
11
  @options = self.class.default_options.merge(options)
10
12
  @responses = {}
11
13
  @persistent = @options.persistent
@@ -29,13 +31,21 @@ module HTTPX
29
31
  end
30
32
 
31
33
  def request(*args, **options)
32
- requests = build_requests(*args, options)
34
+ requests = args.first.is_a?(Request) ? args : build_requests(*args, options)
33
35
  responses = send_requests(*requests, options)
34
36
  return responses.first if responses.size == 1
35
37
 
36
38
  responses
37
39
  end
38
40
 
41
+ def build_request(verb, uri, options = EMPTY_HASH)
42
+ rklass = @options.request_class
43
+ request = rklass.new(verb, uri, @options.merge(options).merge(persistent: @persistent))
44
+ request.on(:response, &method(:on_response).curry[request])
45
+ request.on(:promise, &method(:on_promise))
46
+ request
47
+ end
48
+
39
49
  private
40
50
 
41
51
  def pool
@@ -47,7 +57,7 @@ module HTTPX
47
57
  end
48
58
 
49
59
  def on_promise(_, stream)
50
- log(level: 2, label: "#{stream.id}: ") { "refusing stream!" }
60
+ log(level: 2) { "#{stream.id}: refusing stream!" }
51
61
  stream.refuse
52
62
  end
53
63
 
@@ -56,7 +66,8 @@ module HTTPX
56
66
  end
57
67
 
58
68
  def find_connection(request, connections, options)
59
- uri = URI(request.uri)
69
+ uri = request.uri
70
+
60
71
  connection = pool.find_connection(uri, options) || build_connection(uri, options)
61
72
  unless connections.nil? || connections.include?(connection)
62
73
  connections << connection
@@ -122,13 +133,13 @@ module HTTPX
122
133
  requests = case args.size
123
134
  when 1
124
135
  reqs = args.first
125
- reqs.map do |verb, uri|
126
- build_request(verb, uri, request_options)
136
+ reqs.map do |verb, uri, opts = EMPTY_HASH|
137
+ build_request(verb, uri, request_options.merge(opts))
127
138
  end
128
- when 2, 3
139
+ when 2
129
140
  verb, uris = args
130
141
  if uris.respond_to?(:each)
131
- uris.map do |uri, **opts|
142
+ uris.enum_for(:each).map do |uri, opts = EMPTY_HASH|
132
143
  build_request(verb, uri, request_options.merge(opts))
133
144
  end
134
145
  else
@@ -179,15 +190,13 @@ module HTTPX
179
190
  begin
180
191
  # guarantee ordered responses
181
192
  loop do
182
- begin
183
- request = requests.first
184
- pool.next_tick until (response = fetch_response(request, connections, request_options))
193
+ request = requests.first
194
+ pool.next_tick until (response = fetch_response(request, connections, request_options))
185
195
 
186
- responses << response
187
- requests.shift
196
+ responses << response
197
+ requests.shift
188
198
 
189
- break if requests.empty? || pool.empty?
190
- end
199
+ break if requests.empty? || pool.empty?
191
200
  end
192
201
  responses
193
202
  ensure
@@ -195,14 +204,6 @@ module HTTPX
195
204
  end
196
205
  end
197
206
 
198
- def build_request(verb, uri, options)
199
- rklass = @options.request_class
200
- request = rklass.new(verb, uri, @options.merge(options))
201
- request.on(:response, &method(:on_response).curry[request])
202
- request.on(:promise, &method(:on_promise))
203
- request
204
- end
205
-
206
207
  @default_options = Options.new
207
208
  @default_options.freeze
208
209
  @plugins = []
@@ -219,7 +220,7 @@ module HTTPX
219
220
  def plugin(pl, options = nil, &block)
220
221
  # raise Error, "Cannot add a plugin to a frozen config" if frozen?
221
222
  pl = Plugins.load_plugin(pl) if pl.is_a?(Symbol)
222
- unless @plugins.include?(pl)
223
+ if !@plugins.include?(pl)
223
224
  @plugins << pl
224
225
  pl.load_dependencies(self, &block) if pl.respond_to?(:load_dependencies)
225
226
  @default_options = @default_options.dup
@@ -243,6 +244,13 @@ module HTTPX
243
244
  opts.connection_class.__send__(:include, pl::ConnectionMethods) if defined?(pl::ConnectionMethods)
244
245
  pl.configure(self, &block) if pl.respond_to?(:configure)
245
246
 
247
+ @default_options.freeze
248
+ elsif options
249
+ # this can happen when two plugins are loaded, an one of them calls the other under the hood,
250
+ # albeit changing some default.
251
+ @default_options = @default_options.dup
252
+ @default_options = @default_options.merge(options)
253
+
246
254
  @default_options.freeze
247
255
  end
248
256
  self
@@ -6,6 +6,7 @@ module HTTPX
6
6
  class Timeout
7
7
  CONNECT_TIMEOUT = 60
8
8
  OPERATION_TIMEOUT = 60
9
+ KEEP_ALIVE_TIMEOUT = 20
9
10
 
10
11
  def self.new(opts = {})
11
12
  return opts if opts.is_a?(Timeout)
@@ -13,14 +14,16 @@ module HTTPX
13
14
  super(**opts)
14
15
  end
15
16
 
16
- attr_reader :connect_timeout, :operation_timeout, :total_timeout
17
+ attr_reader :connect_timeout, :operation_timeout, :keep_alive_timeout, :total_timeout
17
18
 
18
19
  def initialize(connect_timeout: CONNECT_TIMEOUT,
19
20
  operation_timeout: OPERATION_TIMEOUT,
21
+ keep_alive_timeout: KEEP_ALIVE_TIMEOUT,
20
22
  total_timeout: nil,
21
23
  loop_timeout: nil)
22
24
  @connect_timeout = connect_timeout
23
25
  @operation_timeout = operation_timeout
26
+ @keep_alive_timeout = keep_alive_timeout
24
27
  @total_timeout = total_timeout
25
28
 
26
29
  return unless loop_timeout
@@ -35,6 +38,7 @@ module HTTPX
35
38
  if other.is_a?(Timeout)
36
39
  @connect_timeout == other.instance_variable_get(:@connect_timeout) &&
37
40
  @operation_timeout == other.instance_variable_get(:@operation_timeout) &&
41
+ @keep_alive_timeout == other.instance_variable_get(:@keep_alive_timeout) &&
38
42
  @total_timeout == other.instance_variable_get(:@total_timeout)
39
43
  else
40
44
  super
@@ -49,9 +53,11 @@ module HTTPX
49
53
  when Timeout
50
54
  connect_timeout = other.instance_variable_get(:@connect_timeout) || @connect_timeout
51
55
  operation_timeout = other.instance_variable_get(:@operation_timeout) || @operation_timeout
56
+ keep_alive_timeout = other.instance_variable_get(:@keep_alive_timeout) || @keep_alive_timeout
52
57
  total_timeout = other.instance_variable_get(:@total_timeout) || @total_timeout
53
58
  Timeout.new(connect_timeout: connect_timeout,
54
59
  operation_timeout: operation_timeout,
60
+ keep_alive_timeout: keep_alive_timeout,
55
61
  total_timeout: total_timeout)
56
62
  else
57
63
  raise ArgumentError, "can't merge with #{other.class}"
@@ -10,8 +10,6 @@ module HTTPX::Transcoder
10
10
  class Encoder
11
11
  extend Forwardable
12
12
 
13
- def_delegator :@raw, :readpartial
14
-
15
13
  def initialize(body)
16
14
  @raw = body
17
15
  end
@@ -14,8 +14,6 @@ module HTTPX::Transcoder
14
14
 
15
15
  def_delegator :@raw, :bytesize
16
16
 
17
- def_delegator :@raw, :force_encoding
18
-
19
17
  def initialize(form)
20
18
  @raw = URI.encode_www_form(form)
21
19
  end
@@ -23,10 +21,6 @@ module HTTPX::Transcoder
23
21
  def content_type
24
22
  "application/x-www-form-urlencoded"
25
23
  end
26
-
27
- def to_str
28
- @raw.to_s
29
- end
30
24
  end
31
25
 
32
26
  def encode(form)
@@ -10,14 +10,10 @@ module HTTPX::Transcoder
10
10
  class Encoder
11
11
  extend Forwardable
12
12
 
13
- def_delegator :@raw, :to_str
14
-
15
13
  def_delegator :@raw, :to_s
16
14
 
17
15
  def_delegator :@raw, :bytesize
18
16
 
19
- def_delegator :@raw, :force_encoding
20
-
21
17
  def initialize(json)
22
18
  @raw = ::JSON.dump(json)
23
19
  @charset = @raw.encoding.name.downcase
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTPX
4
+ module Utils
5
+ using URIExtensions
6
+
7
+ module_function
8
+
9
+ # The value of this field can be either an HTTP-date or a number of
10
+ # seconds to delay after the response is received.
11
+ def parse_retry_after(retry_after)
12
+ # first: bet on it being an integer
13
+ Integer(retry_after)
14
+ rescue ArgumentError
15
+ # Then it's a datetime
16
+ time = Time.httpdate(retry_after)
17
+ time - Time.now
18
+ end
19
+
20
+ if RUBY_VERSION < "2.3"
21
+ def uri(*args)
22
+ URI(*args)
23
+ end
24
+ else
25
+
26
+ URIParser = URI::RFC2396_Parser.new
27
+
28
+ def uri(uri)
29
+ return Kernel.URI(uri) unless uri.is_a?(String) && !uri.ascii_only?
30
+
31
+ uri = Kernel.URI(URIParser.escape(uri))
32
+
33
+ non_ascii_hostname = URIParser.unescape(uri.host)
34
+
35
+ non_ascii_hostname.force_encoding(Encoding::UTF_8)
36
+
37
+ idna_hostname = DomainName.new(non_ascii_hostname).hostname
38
+
39
+ uri.host = idna_hostname
40
+ uri.non_ascii_hostname = non_ascii_hostname
41
+ uri
42
+ end
43
+ end
44
+ end
45
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTPX
4
- VERSION = "0.7.0"
4
+ VERSION = "0.10.0"
5
5
  end
@@ -0,0 +1,24 @@
1
+ module HTTPX
2
+ class Buffer
3
+ include _ToS
4
+ include _ToStr
5
+
6
+ @buffer: String
7
+
8
+ attr_reader limit: Integer
9
+
10
+ def full?: () -> bool
11
+ def shift!: (Integer) -> void
12
+
13
+ # delegated
14
+ def <<: (string data) -> void
15
+ def empty?: () -> bool
16
+ def bytesize: () -> Integer
17
+ def clear: () -> void
18
+ def replace: (string) -> void
19
+
20
+ private
21
+
22
+ def initialize: (Integer limit) -> untyped
23
+ end
24
+ end
@@ -0,0 +1,14 @@
1
+ module HTTPX
2
+ interface _Callable
3
+ def call: (*untyped) -> void
4
+ end
5
+
6
+ module Callbacks
7
+ def on: (Symbol) { (*untyped) -> void } -> void
8
+ def once: (Symbol) { (*untyped) -> void } -> void
9
+ def emit: (Symbol, *untyped) -> void
10
+
11
+ def callbacks: () -> Hash[Symbol, Array[_Callable]]
12
+ | (Symbol) -> Array[_Callable]
13
+ end
14
+ end
@@ -0,0 +1,37 @@
1
+ module HTTPX
2
+ module Chainable
3
+ def request: (*untyped, **untyped) -> (response | Array[response])
4
+ def accept: (String) -> Session
5
+ def wrap: () { (Session) -> void } -> void
6
+ | () -> void
7
+
8
+ def with: (options) -> Session
9
+ | (options) { (Session) -> Session} -> Session
10
+
11
+
12
+
13
+
14
+ def plugin: (:authentication) -> Plugins::sessionAuthentication
15
+ | (:basic_authentication) -> Plugins::sessionBasicAuthentication
16
+ | (:digest_authentication) -> Plugins::sessionDigestAuthentication
17
+ | (:compression) -> Session
18
+ | (:cookies) -> Plugins::sessionCookies
19
+ | (:expect) -> Session
20
+ | (:follow_redirects) -> Plugins::sessionFollowRedirects
21
+ | (:h2c) -> Plugins::sessionH2C
22
+ | (:multipart) -> Session
23
+ | (:persistent) -> Plugins::sessionPersistent
24
+ | (:proxy) -> Plugins::sessionProxy
25
+ | (:push_promise) -> Plugins::sessionPushPromise
26
+ | (:retries) -> Plugins::sessionRetries
27
+ | (:stream) -> Plugins::sessionStream
28
+ | (Symbol | Module, ?options?) { (Class) -> void } -> Session
29
+ | (Symbol | Module, ?options?) -> Session
30
+
31
+ private
32
+
33
+ def default_options: () -> Options
34
+ def branch: (options) -> Session
35
+ | (options) { (Session) -> Session } -> Session
36
+ end
37
+ end
@@ -0,0 +1,2 @@
1
+ class HTTPX::Connection
2
+ end
@@ -0,0 +1,4 @@
1
+ module HTTPX
2
+ class Connection::HTTP2
3
+ end
4
+ end
@@ -0,0 +1,17 @@
1
+ module HTTPX
2
+ class DomainName
3
+ type domain = string | DomainName
4
+
5
+ include Comparable
6
+
7
+ def normalize: (String) -> String
8
+
9
+ def cookie_domain?: (domain, ?bool?) -> bool
10
+
11
+ def self.new: (domain) -> untyped
12
+
13
+ private
14
+
15
+ def initialize: (string) -> untyped
16
+ end
17
+ end
@@ -0,0 +1,3 @@
1
+ module HTTPX
2
+ Error: singleton(StandardError)
3
+ end
@@ -0,0 +1,42 @@
1
+ module HTTPX
2
+ class Headers
3
+ include _ToS
4
+
5
+ @headers: headers_hash
6
+
7
+ def self.new: (?untyped headers) -> Headers
8
+
9
+ def ==: (untyped other) -> bool
10
+
11
+ def []: (headers_key field) -> _ToS?
12
+ def []=: (headers_key field, headers_value value) -> void
13
+
14
+ def add: (headers_key field, string value) -> void
15
+ def delete: (headers_key field) -> void
16
+
17
+ def each: () { (headers_key, String) -> void } -> void
18
+ | () -> Enumerable[[headers_key, String], void]
19
+
20
+ def get: (headers_key field) -> Array[String]
21
+ def key?: (headers_key downcased_key) -> bool
22
+
23
+ def merge: (_Each[headers_key, headers_value] other) -> Headers
24
+
25
+ def same_headers?: (untyped headers) -> bool
26
+
27
+ def to_a: () -> Array[[headers_key, String]]
28
+ def inspect: () -> String
29
+
30
+ private
31
+
32
+ def initialize: (headers?) -> untyped
33
+ def array_value: (headers_value) -> Array[String]
34
+ def downcased: (headers_key) -> String
35
+ end
36
+
37
+ type headers_key = String | Symbol
38
+ type headers_value = _ToS | Array[_ToS]
39
+ type headers_hash = Hash[headers_key, headers_value]
40
+ type headers_input = headers_hash | Array[[headers_key, string]]
41
+ type headers = Headers | headers_input
42
+ end
@@ -0,0 +1,14 @@
1
+ module HTTPX
2
+ extend Chainable
3
+
4
+ VERSION: String
5
+
6
+ type uri = URI::HTTP | URI::HTTPS | string
7
+
8
+ type verb = :options | :get | :head | :post | :put | :delete | :trace | :connect |
9
+ :propfind | :proppatch | :mkcol | :copy | :move | :lock | :unlock | :orderpatch |
10
+ :acl | :report | :patch | :search
11
+
12
+ module Plugins
13
+ end
14
+ end
@@ -0,0 +1,11 @@
1
+ module HTTPX
2
+ interface _IOLogger
3
+ def <<: (string?) -> void
4
+ end
5
+
6
+ module Loggable
7
+ def log: (?level: Integer?, ?color: Symbol?) { () -> String } -> void
8
+
9
+ def log_exception: (StandardError, **untyped) -> void
10
+ end
11
+ end