aitch 1.0.1 → 1.2.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 (63) hide show
  1. checksums.yaml +5 -5
  2. data/.github/FUNDING.yml +3 -0
  3. data/.github/workflows/tests.yml +50 -0
  4. data/.rubocop.yml +26 -0
  5. data/Gemfile +2 -2
  6. data/README.md +54 -14
  7. data/Rakefile +5 -1
  8. data/aitch.gemspec +12 -7
  9. data/lib/aitch/configuration.rb +5 -0
  10. data/lib/aitch/dsl.rb +4 -3
  11. data/lib/aitch/engines/json.rb +15 -0
  12. data/lib/aitch/errors.rb +1 -0
  13. data/lib/aitch/ext/to_query.rb +6 -10
  14. data/lib/aitch/location.rb +4 -3
  15. data/lib/aitch/namespace.rb +38 -14
  16. data/lib/aitch/redirect.rb +1 -0
  17. data/lib/aitch/request.rb +40 -28
  18. data/lib/aitch/response/body.rb +1 -0
  19. data/lib/aitch/response/description.rb +3 -2
  20. data/lib/aitch/response/errors.rb +2 -1
  21. data/lib/aitch/response.rb +9 -5
  22. data/lib/aitch/response_parser/default_parser.rb +2 -1
  23. data/lib/aitch/response_parser/html_parser.rb +1 -0
  24. data/lib/aitch/response_parser/json_parser.rb +2 -1
  25. data/lib/aitch/response_parser/xml_parser.rb +1 -0
  26. data/lib/aitch/response_parser.rb +8 -5
  27. data/lib/aitch/uri.rb +5 -2
  28. data/lib/aitch/utils.rb +3 -1
  29. data/lib/aitch/version.rb +2 -1
  30. data/lib/aitch.rb +13 -13
  31. data/test/aitch/aitch_test.rb +1 -0
  32. data/test/aitch/configuration_test.rb +2 -1
  33. data/test/aitch/dsl_test.rb +1 -0
  34. data/test/aitch/execute_test.rb +1 -0
  35. data/test/aitch/namespace_test.rb +2 -1
  36. data/test/aitch/request/client_https_test.rb +1 -0
  37. data/test/aitch/request/follow_redirect_test.rb +4 -3
  38. data/test/aitch/request/json_request_test.rb +1 -0
  39. data/test/aitch/request/request_class_test.rb +3 -2
  40. data/test/aitch/request/status_code_validation_test.rb +3 -2
  41. data/test/aitch/request_test.rb +45 -8
  42. data/test/aitch/response/custom_response_parser_test.rb +1 -4
  43. data/test/aitch/response/default_response_parser_test.rb +14 -0
  44. data/test/aitch/response/errors_test.rb +1 -0
  45. data/test/aitch/response/html_response_test.rb +1 -0
  46. data/test/aitch/response/json_response_test.rb +2 -1
  47. data/test/aitch/response/raw_response_test.rb +1 -0
  48. data/test/aitch/response/status_3xx_test.rb +1 -0
  49. data/test/aitch/response/status_4xx_test.rb +1 -0
  50. data/test/aitch/response/status_5xx_test.rb +1 -0
  51. data/test/aitch/response/xml_response_test.rb +1 -0
  52. data/test/aitch/response_parser/html_parser_test.rb +1 -0
  53. data/test/aitch/response_parser/json_parser_test.rb +2 -1
  54. data/test/aitch/response_parser/xml_parser_test.rb +1 -1
  55. data/test/aitch/response_test.rb +1 -1
  56. data/test/aitch/to_query_test.rb +28 -0
  57. data/test/aitch/uri_test.rb +2 -1
  58. data/test/aitch/utils/symbolize_keys_test.rb +1 -0
  59. data/test/aitch/utils/underscore_test.rb +1 -0
  60. data/test/support/helpers.rb +5 -4
  61. data/test/test_helper.rb +3 -2
  62. metadata +77 -15
  63. data/.travis.yml +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 1608bb2a19cd498230af4c13ff13f5470c038b8c
4
- data.tar.gz: 1dd7c3cde77b473060edfccb1865eed448de4cda
2
+ SHA256:
3
+ metadata.gz: a3f4cd3269e5b709f85e34148b2ed6aad4a9080f9903dc625d4840d8ffc2293a
4
+ data.tar.gz: d97a344ec31e5f6ba528f1d359b96aa2860ab51e1cdde507d5cbc0557e9a75a4
5
5
  SHA512:
6
- metadata.gz: 00029220787908d61ed6b1d64a8dcbe9e0dffdc3f450c3ce67d56b836d9967919fc12a1f18298919876478c5ef916146d529992d1008752b33eff1e36622573c
7
- data.tar.gz: ff9e4b489b83f083d172e035a5440bd4db7ef36404c8f6fe728c408124a8a86927069e18183eea312038b7fb581668fe7e729d39a5e90bd4131811f46c0ee618
6
+ metadata.gz: a11acc6e165677b372cd4c9a07b9fd33640611a056fb1c437917ffa1d89ab0520bcaf9943cd5e8f72a3f088942a8f7b9d968a59ec7207dc6a061f54364216aba
7
+ data.tar.gz: a0228f9d4719ee0fddd2932fece69c454062d40f4526612b41044168198f7ec91478267163e343a995ceb5f8bbcdc1d4d228d8edb73a9845b5aa0d08145581c9
@@ -0,0 +1,3 @@
1
+ ---
2
+ github: [fnando]
3
+ custom: ["https://www.paypal.me/nandovieira/🍕"]
@@ -0,0 +1,50 @@
1
+ ---
2
+ name: Tests
3
+
4
+ on:
5
+ pull_request:
6
+ branches:
7
+ - main
8
+ push:
9
+ branches:
10
+ - main
11
+ jobs:
12
+ build:
13
+ name: Tests with Ruby ${{ matrix.ruby }}
14
+ runs-on: "ubuntu-latest"
15
+ strategy:
16
+ fail-fast: false
17
+ matrix:
18
+ ruby: ["2.7.x", "2.6.x", "2.5.x", "3.0.x", "3.1.x"]
19
+
20
+ steps:
21
+ - uses: actions/checkout@v1
22
+
23
+ - uses: actions/cache@v2
24
+ with:
25
+ path: vendor/bundle
26
+ key: >
27
+ ${{ runner.os }}-${{ matrix.ruby }}-gems-${{
28
+ hashFiles('aitch.gemspec') }}
29
+ restore-keys: >
30
+ ${{ runner.os }}-${{ matrix.ruby }}-gems-${{
31
+ hashFiles('aitch.gemspec') }}
32
+
33
+ - name: Set up Ruby
34
+ uses: actions/setup-ruby@v1
35
+ with:
36
+ ruby-version: ${{ matrix.ruby }}
37
+
38
+ - name: Install gem dependencies
39
+ env:
40
+ RAILS_ENV: test
41
+ run: |
42
+ gem install bundler
43
+ bundle config path vendor/bundle
44
+ bundle update --jobs 4 --retry 3
45
+
46
+ - name: Run Tests
47
+ env:
48
+ RAILS_ENV: test
49
+ run: |
50
+ bundle exec rake
data/.rubocop.yml ADDED
@@ -0,0 +1,26 @@
1
+ ---
2
+ inherit_gem:
3
+ rubocop-fnando: .rubocop.yml
4
+
5
+ AllCops:
6
+ TargetRubyVersion: 2.5
7
+
8
+ Layout/LineLength:
9
+ Exclude:
10
+ - test/**/*.rb
11
+
12
+ Naming/AccessorMethodName:
13
+ Exclude:
14
+ - lib/aitch/request.rb
15
+
16
+ Metrics/ClassLength:
17
+ Enabled: false
18
+
19
+ Metrics/AbcSize:
20
+ Enabled: false
21
+
22
+ Metrics/MethodLength:
23
+ Enabled: false
24
+
25
+ Metrics/ParameterLists:
26
+ Enabled: false
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source "https://rubygems.org"
2
4
  gemspec
3
-
4
- gem "pry-meta", platforms: [:ruby_20, :ruby_21, :ruby_22]
data/README.md CHANGED
@@ -1,24 +1,24 @@
1
1
  # Aitch
2
2
 
3
- [![Build Status](https://travis-ci.org/fnando/aitch.png)](https://travis-ci.org/fnando/aitch)
3
+ [![Tests](https://github.com/fnando/aitch/workflows/Tests/badge.svg)](https://github.com/fnando/aitch)
4
4
  [![Code Climate](https://codeclimate.com/github/fnando/aitch/badges/gpa.svg)](https://codeclimate.com/github/fnando/aitch)
5
- [![Test Coverage](https://codeclimate.com/github/fnando/aitch/badges/coverage.svg)](https://codeclimate.com/github/fnando/aitch)
6
- [![RubyGems](https://badge.fury.io/rb/aitch.png)](https://rubygems.org/gems/aitch)
5
+ [![Gem Version](https://img.shields.io/gem/v/aitch.svg)](https://rubygems.org/gems/aitch)
6
+ [![Gem Downloads](https://img.shields.io/gem/dt/aitch.svg)](https://rubygems.org/gems/aitch)
7
7
 
8
8
  A simple HTTP client.
9
9
 
10
10
  Features:
11
11
 
12
- * Supports Gzip|Deflate response
13
- * Automatically parses JSON, HTML and XML responses
14
- * Automatically follows redirect
12
+ - Supports Gzip|Deflate response
13
+ - Automatically parses JSON, HTML and XML responses
14
+ - Automatically follows redirect
15
15
 
16
16
  ## Installation
17
17
 
18
18
  Add this line to your application's Gemfile:
19
19
 
20
20
  ```ruby
21
- gem 'aitch'
21
+ gem "aitch"
22
22
  ```
23
23
 
24
24
  And then execute:
@@ -54,6 +54,9 @@ Aitch.configure do |config|
54
54
 
55
55
  # Set the logger.
56
56
  config.logger = nil
57
+
58
+ # Set the base url.
59
+ config.base_url = nil
57
60
  end
58
61
  ```
59
62
 
@@ -83,6 +86,17 @@ response = Aitch.get do
83
86
  end
84
87
  ```
85
88
 
89
+ Finally, you can use keyword arguments:
90
+
91
+ ```ruby
92
+ Aitch.get(
93
+ url: "http://example.org",
94
+ params: {a: 1, b: 2},
95
+ headers: {Authorization: "Token token=abc"},
96
+ options: {follow_redirect: false}
97
+ )
98
+ ```
99
+
86
100
  ### Response
87
101
 
88
102
  The response object:
@@ -104,7 +118,8 @@ response.data # Parsed response body
104
118
 
105
119
  #### Parsing JSON, XML and HTML with Nokogiri
106
120
 
107
- If your response is a JSON, XML or a HTML content type, we'll automatically convert the response into the appropriate object.
121
+ If your response is a JSON, XML or a HTML content type, we'll automatically
122
+ convert the response into the appropriate object.
108
123
 
109
124
  ```ruby
110
125
  response = Aitch.get("http://simplesideias.com.br")
@@ -148,7 +163,8 @@ The request:
148
163
  Aitch.get("http://example.org")
149
164
  ```
150
165
 
151
- If the redirect limit is exceeded, then the `Aitch::TooManyRedirectsError` exception is raised.
166
+ If the redirect limit is exceeded, then the `Aitch::TooManyRedirectsError`
167
+ exception is raised.
152
168
 
153
169
  ### Basic auth
154
170
 
@@ -196,7 +212,8 @@ Request.get("http://example.org")
196
212
 
197
213
  ### Validating responses
198
214
 
199
- When you know the kind of response you're expecting, you can validate it by specifying the `expect` option.
215
+ When you know the kind of response you're expecting, you can validate it by
216
+ specifying the `expect` option.
200
217
 
201
218
  ```ruby
202
219
  Aitch.get do
@@ -205,7 +222,8 @@ Aitch.get do
205
222
  end
206
223
  ```
207
224
 
208
- If this request receives anything other than `200`, it will raise a `Aitch::StatusCodeError` exception.
225
+ If this request receives anything other than `200`, it will raise a
226
+ `Aitch::StatusCodeError` exception.
209
227
 
210
228
  ```
211
229
  Expect(200 OK) <=> Actual(404 Not Found)
@@ -215,7 +233,10 @@ You can also provide a list of accepted statuses, like `expect: [200, 201]`.
215
233
 
216
234
  ### Response Parsers
217
235
 
218
- You can register new response parsers by using `Aitch::ResponseParser.register(name, parser)`, where parser must implement the methods `match?(content_type)` and `load(response_body)`. This is how you could load CSV values.
236
+ You can register new response parsers by using
237
+ `Aitch::ResponseParser.register(name, parser)`, where parser must implement the
238
+ methods `match?(content_type)` and `load(response_body)`. This is how you could
239
+ load CSV values.
219
240
 
220
241
  ```ruby
221
242
  require "csv"
@@ -237,7 +258,8 @@ end
237
258
  Aitch::ResponseParser.prepend(:csv, CSVParser)
238
259
  ```
239
260
 
240
- The default behavior is returning the response body. You can replace it as the following:
261
+ The default behavior is returning the response body. You can replace it as the
262
+ following:
241
263
 
242
264
  ```ruby
243
265
  module DefaultParser
@@ -261,13 +283,31 @@ Aitch::ResponseParser.append(:default, DefaultParser)
261
283
 
262
284
  Aitch comes with response parsers for HTML, XML and JSON.
263
285
 
264
- By default, the JSON parser will be `JSON`. To set it to something else, use `Aitch::ResponseParser::JSONParser.engine`.
286
+ By default, the JSON parser will be `JSON`. To set it to something else, use
287
+ `Aitch::ResponseParser::JSONParser.engine`.
265
288
 
266
289
  ```ruby
267
290
  require "oj"
268
291
  Aitch::ResponseParser::JSONParser.engine = Oj
269
292
  ```
270
293
 
294
+ ### Setting the base url
295
+
296
+ When you're creating a wrapper for an API, usually the hostname is the same for
297
+ the whole API. In this case, you can avoid having to pass it around all the time
298
+ by setting `Aitch::Configuration#base_url`. This option is meant to be used when
299
+ you instantiate a new namespace.
300
+
301
+ ```ruby
302
+ Client = Aitch::Namespace.new
303
+
304
+ Client.configure do |config|
305
+ config.base_url = "https://api.example.com"
306
+ end
307
+
308
+ Client.get("/users")
309
+ ```
310
+
271
311
  ## Contributing
272
312
 
273
313
  1. Fork it
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "bundler/gem_tasks"
3
4
  require "rake/testtask"
5
+ require "rubocop/rake_task"
4
6
 
5
7
  Rake::TestTask.new(:test) do |t|
6
8
  t.libs << "test"
@@ -8,4 +10,6 @@ Rake::TestTask.new(:test) do |t|
8
10
  t.warning = false
9
11
  end
10
12
 
11
- task default: :test
13
+ RuboCop::RakeTask.new
14
+
15
+ task default: %i[test rubocop]
data/aitch.gemspec CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "./lib/aitch/version"
2
4
 
3
5
  Gem::Specification.new do |spec|
@@ -9,20 +11,23 @@ Gem::Specification.new do |spec|
9
11
  spec.summary = spec.description
10
12
  spec.homepage = "http://rubygems.org/gems/aitch"
11
13
  spec.license = "MIT"
14
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
12
15
 
13
- spec.files = `git ls-files`.split($/)
14
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
17
+ spec.executables = spec.files.grep(%r{^bin/}) {|f| File.basename(f) }
15
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
16
19
  spec.require_paths = ["lib"]
17
20
 
18
- spec.required_ruby_version = ">= 2.0"
19
-
20
- spec.add_dependency "nokogiri", ">= 1.6.0"
21
+ spec.add_dependency "nokogiri"
21
22
 
22
- spec.add_development_dependency "codeclimate-test-reporter"
23
23
  spec.add_development_dependency "bundler"
24
- spec.add_development_dependency "rake"
24
+ spec.add_development_dependency "minitest"
25
25
  spec.add_development_dependency "minitest-utils"
26
26
  spec.add_development_dependency "mocha"
27
+ spec.add_development_dependency "pry-meta"
28
+ spec.add_development_dependency "rake"
29
+ spec.add_development_dependency "rubocop"
30
+ spec.add_development_dependency "rubocop-fnando"
31
+ spec.add_development_dependency "simplecov"
27
32
  spec.add_development_dependency "webmock"
28
33
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Aitch
3
4
  class Configuration
4
5
  # Set proxy.
@@ -22,12 +23,16 @@ module Aitch
22
23
  # Set the logger.
23
24
  attr_accessor :logger
24
25
 
26
+ # Set the base url.
27
+ attr_accessor :base_url
28
+
25
29
  def initialize
26
30
  @timeout = 10
27
31
  @redirect_limit = 5
28
32
  @follow_redirect = true
29
33
  @user_agent = "Aitch/#{Aitch::VERSION} (http://rubygems.org/gems/aitch)"
30
34
  @default_headers = {}
35
+ @base_url = nil
31
36
  end
32
37
 
33
38
  def to_h
data/lib/aitch/dsl.rb CHANGED
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Aitch
3
4
  class DSL
4
5
  %w[url options headers data].each do |name|
5
- class_eval <<-RUBY
6
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
6
7
  attr_writer :#{name}
7
8
 
8
9
  def #{name}(*args)
@@ -12,8 +13,8 @@ module Aitch
12
13
  RUBY
13
14
  end
14
15
 
15
- alias_method :params, :data
16
- alias_method :body, :data
16
+ alias params data
17
+ alias body data
17
18
 
18
19
  def to_h
19
20
  {
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aitch
4
+ module Engines
5
+ module JSON
6
+ def self.load(data)
7
+ data && ::JSON.parse(data)
8
+ end
9
+
10
+ def self.dump(data)
11
+ ::JSON.dump(data)
12
+ end
13
+ end
14
+ end
15
+ end
data/lib/aitch/errors.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Aitch
3
4
  InvalidURIError = Class.new(StandardError)
4
5
  InvalidHTTPMethodError = Class.new(StandardError)
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "cgi"
3
4
 
4
5
  class Object
@@ -36,23 +37,18 @@ class FalseClass
36
37
  end
37
38
 
38
39
  class Array
39
- # Calls <tt>to_param</tt> on all its elements and joins the result with
40
- # slashes. This is used by <tt>url_for</tt> in Action Pack.
41
- def to_param
42
- collect { |e| e.to_param }.join '/'
43
- end
44
-
45
40
  # Converts an array into a string suitable for use as a URL query string,
46
41
  # using the given +key+ as the param name.
47
42
  #
48
- # ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding"
43
+ # ["Rails", "coding"].to_query("hobbies")
44
+ # # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding"
49
45
  def to_query(key)
50
46
  prefix = "#{key}[]"
51
47
 
52
48
  if empty?
53
49
  nil.to_query(prefix)
54
50
  else
55
- collect { |value| value.to_query(prefix) }.join '&'
51
+ collect {|value| value.to_query(prefix) }.join "&"
56
52
  end
57
53
  end
58
54
  end
@@ -78,8 +74,8 @@ class Hash
78
74
  unless (value.is_a?(Hash) || value.is_a?(Array)) && value.empty?
79
75
  value.to_query(namespace ? "#{namespace}[#{key}]" : key)
80
76
  end
81
- end.compact.sort! * '&'
77
+ end.compact.sort! * "&"
82
78
  end
83
79
 
84
- alias_method :to_param, :to_query
80
+ alias to_param to_query
85
81
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Aitch
3
4
  class Location
4
5
  attr_reader :redirect_stack, :current_url
@@ -9,7 +10,7 @@ module Aitch
9
10
  end
10
11
 
11
12
  def location
12
- return current_url unless current_url.match(%r[\A/])
13
+ return current_url unless current_url.match?(%r{\A/}) # rubocop:disable Performance/StartWith
13
14
 
14
15
  uri = find_uri_with_host
15
16
  url = ["#{uri.scheme}://#{uri.hostname}"]
@@ -20,8 +21,8 @@ module Aitch
20
21
 
21
22
  def find_uri_with_host
22
23
  redirect_stack.reverse
23
- .map {|url| ::URI.parse(url) }
24
- .find {|uri| uri.scheme }
24
+ .map {|url| ::URI.parse(url) }
25
+ .find(&:scheme)
25
26
  end
26
27
  end
27
28
  end
@@ -1,17 +1,31 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Aitch
3
4
  class Namespace
4
- def configure(&block)
5
+ def configure
5
6
  yield config
6
7
  end
7
8
 
8
9
  def config
9
10
  @config ||= Configuration.new
10
11
  end
11
- alias_method :configuration, :config
12
+ alias configuration config
13
+
14
+ def execute(
15
+ request_method: nil,
16
+ url: nil,
17
+ params: nil,
18
+ data: nil,
19
+ body: nil,
20
+ headers: nil,
21
+ options: nil,
22
+ &block
23
+ )
24
+ data = data || params || body || {}
25
+ headers ||= {}
26
+ options ||= {}
12
27
 
13
- def execute(request_method = nil, url = nil, data = {}, headers = {}, options = {}, &block)
14
- if block_given?
28
+ if block
15
29
  dsl = DSL.new
16
30
  dsl.instance_eval(&block)
17
31
  args = dsl.to_h
@@ -24,17 +38,18 @@ module Aitch
24
38
  }
25
39
  end
26
40
 
27
- args.merge!(
28
- request_method: request_method,
29
- options: config.to_h.merge(Utils.symbolize_keys(args[:options]))
30
- )
41
+ args[:request_method] = request_method
42
+ args[:options] = config.to_h.merge(Utils.symbolize_keys(args[:options]))
31
43
 
32
44
  Request.new(args).perform
33
45
  end
34
46
 
35
47
  def execute!(*args, &block)
36
- response = execute(*args, &block)
48
+ options = extract_args!(args)
49
+ response = execute(**options, &block)
50
+
37
51
  raise response.error if response.error?
52
+
38
53
  response
39
54
  end
40
55
 
@@ -47,14 +62,23 @@ module Aitch
47
62
  options
48
63
  trace
49
64
  head
50
- ].each do |method_name|
51
- define_method(method_name) do |url = nil, data = {}, headers = {}, options = {}, &block|
52
- execute(method_name, url, data, headers, options, &block)
65
+ ].each do |request_method|
66
+ define_method(request_method) do |*args, &block|
67
+ options = extract_args!(args)
68
+ execute(**options.merge(request_method: request_method), &block)
53
69
  end
54
70
 
55
- define_method("#{method_name}!") do |url = nil, data = {}, headers = {}, options = {}, &block|
56
- execute!(method_name, url, data, headers, options, &block)
71
+ define_method("#{request_method}!") do |*args, &block|
72
+ options = extract_args!(args)
73
+
74
+ execute!(**options.merge(request_method: request_method), &block)
57
75
  end
58
76
  end
77
+
78
+ private def extract_args!(args)
79
+ return args.first if args.size == 1 && args.first.is_a?(Hash)
80
+
81
+ %i[url data headers options].zip(args).to_h
82
+ end
59
83
  end
60
84
  end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Aitch
3
4
  class Redirect
4
5
  attr_reader :tries