patient_http 1.6.0 → 1.7.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/ARCHITECTURE.md +8 -7
  3. data/CHANGELOG.md +39 -0
  4. data/README.md +540 -514
  5. data/VERSION +1 -1
  6. data/lib/patient_http/callback_args.rb +53 -48
  7. data/lib/patient_http/callback_validator.rb +11 -7
  8. data/lib/patient_http/class_helper.rb +6 -7
  9. data/lib/patient_http/client.rb +28 -22
  10. data/lib/patient_http/client_pool.rb +134 -39
  11. data/lib/patient_http/completion_executor.rb +20 -20
  12. data/lib/patient_http/configuration.rb +367 -119
  13. data/lib/patient_http/connection_endpoint.rb +150 -0
  14. data/lib/patient_http/encryptor.rb +28 -18
  15. data/lib/patient_http/error.rb +24 -18
  16. data/lib/patient_http/external_storage.rb +42 -38
  17. data/lib/patient_http/http_error.rb +30 -26
  18. data/lib/patient_http/http_headers.rb +57 -26
  19. data/lib/patient_http/immediate_retries.rb +98 -0
  20. data/lib/patient_http/inline_task_handler.rb +15 -10
  21. data/lib/patient_http/lifecycle_manager.rb +39 -40
  22. data/lib/patient_http/outgoing_request.rb +25 -23
  23. data/lib/patient_http/payload.rb +28 -26
  24. data/lib/patient_http/payload_store/active_record_store.rb +31 -34
  25. data/lib/patient_http/payload_store/base.rb +42 -46
  26. data/lib/patient_http/payload_store/file_store.rb +22 -26
  27. data/lib/patient_http/payload_store/redis_store.rb +28 -34
  28. data/lib/patient_http/payload_store/s3_store.rb +25 -28
  29. data/lib/patient_http/payload_store.rb +2 -0
  30. data/lib/patient_http/processor.rb +111 -79
  31. data/lib/patient_http/processor_observer.rb +65 -59
  32. data/lib/patient_http/rails/engine.rb +13 -8
  33. data/lib/patient_http/redirect_error.rb +50 -41
  34. data/lib/patient_http/redirect_helper.rb +38 -38
  35. data/lib/patient_http/request.rb +70 -46
  36. data/lib/patient_http/request_error.rb +47 -42
  37. data/lib/patient_http/request_helper.rb +142 -119
  38. data/lib/patient_http/request_preparer.rb +13 -10
  39. data/lib/patient_http/request_task.rb +113 -84
  40. data/lib/patient_http/request_template.rb +87 -64
  41. data/lib/patient_http/response.rb +58 -52
  42. data/lib/patient_http/response_reader.rb +66 -65
  43. data/lib/patient_http/secret_manager.rb +34 -30
  44. data/lib/patient_http/secret_reference.rb +33 -26
  45. data/lib/patient_http/synchronous_executor.rb +67 -95
  46. data/lib/patient_http/task_handler.rb +23 -19
  47. data/lib/patient_http/time_helper.rb +8 -8
  48. data/lib/patient_http.rb +311 -186
  49. data/patient_http.gemspec +3 -2
  50. metadata +20 -4
@@ -1,57 +1,77 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PatientHttp
4
- # A wrapper around {Request} that includes callback and job context for the Processor.
5
- # This class allows HTTP requests to be enqueued and processed asynchronously,
6
- # tracking their lifecycle and providing methods to handle success and error callbacks.
4
+ # A {Request} with the callback service and the task handler that the
5
+ # processor needs to run it. The task tracks the request from enqueue to
6
+ # completion, and delivers the result through its task handler.
7
+ #
8
+ # @example Create a task and enqueue it
9
+ # task = PatientHttp::RequestTask.new(
10
+ # request: PatientHttp::Request.new(:get, "https://api.example.com/users/123"),
11
+ # task_handler: MyTaskHandler.new("job-123"),
12
+ # callback: "FetchUserCallback",
13
+ # callback_args: {user_id: 123}
14
+ # )
15
+ # processor.enqueue(task)
7
16
  class RequestTask
8
17
  include TimeHelper
9
18
 
10
- # Headers that are sensitive to origin and should be stripped on cross-origin redirects
19
+ # The headers that are removed on a cross-origin redirect.
11
20
  SENSITIVE_HEADERS = %w[authorization cookie].freeze
12
21
 
13
- # Headers that describe a request body. They are removed when a redirect drops the body.
22
+ # The headers that describe a request body. They're removed when a redirect
23
+ # drops the body.
14
24
  BODY_HEADERS = %w[content-type content-length content-encoding content-language content-location].freeze
15
25
 
16
- # @return [String] Unique UUID for tracking the task
26
+ # @return [String] The unique task ID. A task that follows a redirect has
27
+ # the ID of the original task with a suffix.
17
28
  attr_reader :id
18
29
 
19
- # @return [Request] The HTTP request details
30
+ # @return [Request] The HTTP request.
20
31
  attr_reader :request
21
32
 
22
- # @return [TaskHandler] The handler for job lifecycle operations
33
+ # @return [TaskHandler] The task handler that delivers the result.
23
34
  attr_reader :task_handler
24
35
 
25
- # @return [String] Class name for the callback service
36
+ # @return [String] The callback service class name.
26
37
  attr_reader :callback
27
38
 
28
- # @return [Hash] Callback arguments to include in Response/Error objects (never nil, defaults to empty hash)
39
+ # @return [Hash] The callback arguments for the {Response} or {Error}. Never
40
+ # `nil`.
29
41
  attr_reader :callback_args
30
42
 
31
- # @return [Boolean] Whether to raise HttpError for non-2xx responses
43
+ # @return [Boolean] Whether non-2xx responses go to the `on_error` callback
44
+ # as an {HttpError}.
32
45
  attr_reader :raise_error_responses
33
46
 
34
- # @return [Array<String>] URLs visited during redirect chain
47
+ # @return [Array<String>] The URLs of the redirects that were followed.
35
48
  attr_reader :redirects
36
49
 
37
- # @return [Response, nil] The HTTP response, set on success
50
+ # @return [Response, nil] The HTTP response, after the request succeeds.
38
51
  attr_reader :response
39
52
 
40
- # @return [Exception, nil] The error, set on failure
53
+ # @return [Exception, nil] The error, after the request fails.
41
54
  attr_reader :error
42
55
 
43
- # Initializes a new RequestTask.
56
+ # Creates a task.
44
57
  #
45
- # @param request [Request] The HTTP request to wrap.
46
- # @param task_handler [TaskHandler] The handler for job lifecycle operations.
47
- # @param callback [String, Class] Class name or class for the callback service.
48
- # @param callback_args [Hash] Callback arguments (with string keys) to include
49
- # in Response/Error objects. These will be accessible via response.callback_args
50
- # or error.callback_args.
51
- # @param raise_error_responses [Boolean] Whether to raise HttpError for non-2xx responses.
52
- # @param redirects [Array<String>] URLs visited during redirect chain.
53
- # @param id [String, nil] Unique UUID for tracking the task. If nil, a new UUID will be generated.
54
- # @param default_max_redirects [Integer] Fallback max_redirects when request doesn't specify one.
58
+ # @param request [Request] The HTTP request.
59
+ # @param task_handler [TaskHandler] The task handler that delivers the
60
+ # result.
61
+ # @param callback [String, Class] The callback service class, or its name.
62
+ # @param callback_args [Hash] The JSON-compatible arguments to pass to the
63
+ # callback. The callback reads them from `response.callback_args` or
64
+ # `error.callback_args`.
65
+ # @param raise_error_responses [Boolean] Whether non-2xx responses go to the
66
+ # `on_error` callback as an {HttpError}.
67
+ # @param redirects [Array<String>] The URLs of the redirects that were
68
+ # followed.
69
+ # @param id [String, nil] The unique task ID. If `nil`, a new UUID is
70
+ # generated.
71
+ # @param default_max_redirects [Integer] The maximum number of redirects to
72
+ # follow if the request doesn't set one.
73
+ # @raise [ArgumentError] If a required argument is missing, or if the
74
+ # callback service or callback arguments aren't valid.
55
75
  def initialize(
56
76
  request:,
57
77
  task_handler:,
@@ -83,74 +103,81 @@ module PatientHttp
83
103
  CallbackValidator.validate!(@callback)
84
104
  end
85
105
 
86
- # Mark task as enqueued
106
+ # Records the time that the task was enqueued.
107
+ #
87
108
  # @return [void]
88
109
  def enqueued!
89
110
  @enqueued_at = monotonic_time
90
111
  end
91
112
 
92
- # Mark task as started
113
+ # Records the time that the request started.
114
+ #
93
115
  # @return [void]
94
116
  def started!
95
117
  @started_at = monotonic_time
96
118
  end
97
119
 
98
- # Return true if the task has started processing (i.e. {#started!} was
99
- # called). Used to keep observer request_start/request_end notifications
100
- # balanced when a task is re-enqueued during shutdown.
120
+ # Returns whether the request started. The processor uses this value to
121
+ # send `request_end` to observers only for tasks that got `request_start`.
101
122
  #
102
- # @return [Boolean]
123
+ # @return [Boolean] `true` if {#started!} was called.
103
124
  def started?
104
125
  !@started_at.nil?
105
126
  end
106
127
 
107
- # Returns the wall clock time when the task was enqueued.
128
+ # Returns the time that the task was enqueued.
108
129
  #
109
- # @return [Time, nil] The enqueued time or nil if not enqueued.
130
+ # @return [Time, nil] The time, or `nil` if the task isn't enqueued.
110
131
  def enqueued_at
111
132
  wall_clock_time(@enqueued_at) if @enqueued_at
112
133
  end
113
134
 
114
- # Returns the wall clock time when the task was started.
135
+ # Returns the time that the request started.
115
136
  #
116
- # @return [Time, nil] The started time or nil if not started.
137
+ # @return [Time, nil] The time, or `nil` if the request didn't start.
117
138
  def started_at
118
139
  wall_clock_time(@started_at) if @started_at
119
140
  end
120
141
 
121
- # Returns the wall clock time when the task was completed.
142
+ # Returns the time that the request finished.
122
143
  #
123
- # @return [Time, nil] The completed time or nil if not completed.
144
+ # @return [Time, nil] The time, or `nil` if the request didn't finish.
124
145
  def completed_at
125
146
  wall_clock_time(@completed_at) if @completed_at
126
147
  end
127
148
 
128
- # Enqueued duration in seconds.
129
- # @return [Float, nil] duration or nil if not enqueued yet.
149
+ # Returns the time that the task waited in the queue before the request
150
+ # started.
151
+ #
152
+ # @return [Float, nil] The duration in seconds, or `nil` if the task isn't
153
+ # enqueued.
130
154
  def enqueued_duration
131
155
  return nil unless @enqueued_at
132
156
 
133
157
  (@started_at || monotonic_time) - @enqueued_at
134
158
  end
135
159
 
136
- # Execution duration in seconds.
137
- # @return [Float, nil] duration or nil if not started yet.
160
+ # Returns the time that the request ran.
161
+ #
162
+ # @return [Float, nil] The duration in seconds, or `nil` if the request
163
+ # didn't start.
138
164
  def duration
139
165
  return nil unless @started_at
140
166
 
141
167
  ((@completed_at || monotonic_time) - @started_at).round(9)
142
168
  end
143
169
 
144
- # Re-enqueue the original job via the task handler.
145
- # @return [String] job ID
170
+ # Re-enqueues the original job through the task handler.
171
+ #
172
+ # @return [String] The new job ID.
146
173
  def retry
147
174
  @task_handler.retry
148
175
  end
149
176
 
150
- # Called with the HTTP response on a completed request. Note that
151
- # the response may represent an HTTP error (4xx or 5xx status).
177
+ # Records the response, and delivers it through the task handler. The
178
+ # response can have an HTTP error status (4xx or 5xx).
152
179
  #
153
- # @param response [Response] the HTTP response
180
+ # @param response [Response] The HTTP response.
154
181
  # @return [void]
155
182
  def completed!(response)
156
183
  @completed_at = monotonic_time
@@ -159,9 +186,10 @@ module PatientHttp
159
186
  @task_handler.on_complete(response, @callback)
160
187
  end
161
188
 
162
- # Called with the HTTP error on a failed request.
189
+ # Records the error, and delivers it through the task handler. An exception
190
+ # that isn't an {Error} is wrapped in a {RequestError}.
163
191
  #
164
- # @param exception [Exception] the error that occurred
192
+ # @param exception [Exception] The error.
165
193
  # @return [void]
166
194
  def error!(exception)
167
195
  @completed_at = monotonic_time
@@ -182,45 +210,45 @@ module PatientHttp
182
210
  @task_handler.on_error(wrapped_error, @callback)
183
211
  end
184
212
 
185
- # Return true if the task successfully received a response from the server.
186
- # Note that the response may represent an HTTP error (4xx or 5xx status).
213
+ # Returns whether the server sent a response. The response can have an HTTP
214
+ # error status (4xx or 5xx).
187
215
  #
188
- # @return [Boolean]
216
+ # @return [Boolean] `true` if a response was received.
189
217
  def success?
190
218
  !@response.nil?
191
219
  end
192
220
 
193
- # Return true if an error was raised during the request.
221
+ # Returns whether the request raised an error.
194
222
  #
195
- # @return [Boolean]
223
+ # @return [Boolean] `true` if the request failed.
196
224
  def error?
197
225
  !@error.nil?
198
226
  end
199
227
 
200
- # Returns the maximum number of redirects to follow.
201
- # Uses the request's max_redirects if set, otherwise falls back to the default.
228
+ # Returns the maximum number of redirects to follow. The request value
229
+ # applies if it's set. Otherwise, the default applies.
202
230
  #
203
- # @return [Integer] maximum number of redirects
231
+ # @return [Integer] The maximum number of redirects.
204
232
  def max_redirects
205
233
  request.max_redirects || @default_max_redirects
206
234
  end
207
235
 
208
- # Create a new RequestTask for following a redirect.
236
+ # Creates a task that follows a redirect.
209
237
  #
210
- # The HTTP method follows RFC 9110: 301 and 302 change POST to GET, 303
211
- # changes everything except GET and HEAD to GET, and 300, 307, and 308
212
- # preserve the method. The body and the headers that describe it are
213
- # dropped whenever the method changes.
238
+ # The HTTP method follows RFC 9110. A 301 or 302 changes POST to GET. A 303
239
+ # changes all methods except GET and HEAD to GET. A 300, 307, or 308 keeps
240
+ # the method. When the method changes, the body and the headers that
241
+ # describe it are removed.
214
242
  #
215
- # Headers named in the request's own redirect_strip_headers or in the given
216
- # list are removed from the redirected request. Authorization and Cookie
217
- # headers and preprocessors are removed on cross-origin redirects.
243
+ # The headers named in the request's `redirect_strip_headers` or in
244
+ # `strip_headers` are removed. On a cross-origin redirect, the
245
+ # `Authorization` and `Cookie` headers and the preprocessors are removed.
218
246
  #
219
- # @param location [String] The redirect URL from the Location header
220
- # @param status [Integer] The HTTP status code of the redirect response
221
- # @param strip_headers [Array<String>] Additional header names to strip,
222
- # typically from the {Configuration}
223
- # @return [RequestTask] A new task configured for the redirect
247
+ # @param location [String] The URL from the `Location` header.
248
+ # @param status [Integer] The HTTP status of the redirect response.
249
+ # @param strip_headers [Array<String>] More header names to remove, usually
250
+ # from the {Configuration}.
251
+ # @return [RequestTask] The task for the redirect.
224
252
  def redirect_task(location:, status:, strip_headers: [])
225
253
  redirect_method = RedirectHelper.redirect_method(request.http_method, status)
226
254
  method_changed = (redirect_method != request.http_method)
@@ -268,12 +296,12 @@ module PatientHttp
268
296
  )
269
297
  end
270
298
 
271
- # Build a Response object from async response data.
299
+ # Builds a {Response} for this task.
272
300
  #
273
- # @param status [Integer] HTTP status code
274
- # @param headers [Hash] HTTP response headers
275
- # @param body [String, nil] HTTP response body
276
- # @return [Response] the response object
301
+ # @param status [Integer] The HTTP status code.
302
+ # @param headers [Hash] The response headers.
303
+ # @param body [String, nil] The response body.
304
+ # @return [Response] The response.
277
305
  # @api private
278
306
  def build_response(status:, headers:, body:)
279
307
  original_id = id.split("/").first
@@ -291,21 +319,22 @@ module PatientHttp
291
319
  )
292
320
  end
293
321
 
294
- # Get the id of the first request task before any redirects. This is useful for tracking
295
- # the overall request across multiple redirect tasks.
322
+ # Returns the ID of the first task, before any redirects. Use it to track a
323
+ # request across its redirect tasks.
296
324
  #
297
- # @return [String] the original request id
325
+ # @return [String] The original task ID.
298
326
  def original_id
299
327
  id.split("/").first
300
328
  end
301
329
 
302
330
  private
303
331
 
304
- # Check if two URLs have different origins (scheme + host + port).
332
+ # Returns whether two URLs have different origins. The origin is the
333
+ # scheme, host, and port.
305
334
  #
306
- # @param original_url [String] The original request URL
307
- # @param target_url [String] The redirect target URL
308
- # @return [Boolean] true if the origins differ
335
+ # @param original_url [String] The original request URL.
336
+ # @param target_url [String] The redirect URL.
337
+ # @return [Boolean] `true` if the origins are different.
309
338
  def cross_origin?(original_url, target_url)
310
339
  original = URI.parse(original_url)
311
340
  target = URI.parse(target_url)
@@ -315,10 +344,10 @@ module PatientHttp
315
344
  original.port != target.port
316
345
  end
317
346
 
318
- # Resolve a redirect URL, handling relative URLs.
347
+ # Resolves a redirect URL. A relative URL is joined with the request URL.
319
348
  #
320
- # @param location [String] The Location header value
321
- # @return [String] The resolved absolute URL
349
+ # @param location [String] The `Location` header value.
350
+ # @return [String] The absolute URL.
322
351
  def resolve_redirect_url(location)
323
352
  base_uri = URI.parse(request.url)
324
353
  redirect_uri = URI.parse(location)
@@ -1,41 +1,45 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PatientHttp
4
- # The RequestTemplate is used to build HTTP requests with shared configuration.
4
+ # Builds requests that share settings, such as a base URL, headers, and a
5
+ # timeout. Use a template to make many requests to the same API.
5
6
  #
6
- # Use RequestTemplate when you need to make multiple requests to the same API with shared
7
- # configuration (base URL, headers, timeout).
7
+ # The template joins each path with the base URL, and merges the headers and
8
+ # query parameters of each request with its own.
8
9
  #
9
- # @example Basic usage
10
+ # @example Build a request
10
11
  # template = PatientHttp::RequestTemplate.new(
11
12
  # base_url: "https://api.example.com",
12
13
  # headers: {"Authorization" => "Bearer token"},
13
14
  # timeout: 60
14
15
  # )
15
16
  # request = template.get("/users/123")
16
- #
17
- # The RequestTemplate handles building HTTP requests with proper URL joining, header merging,
18
- # and parameter encoding.
17
+ # PatientHttp.execute(request: request, callback: FetchUserCallback)
19
18
  class RequestTemplate
20
- # @return [String, URI::HTTP, nil] Base URL for relative URIs
19
+ # @return [String, URI::HTTP, nil] The base URL that relative paths are
20
+ # joined with.
21
21
  attr_accessor :base_url
22
22
 
23
- # @return [HttpHeaders] Default headers for all requests
23
+ # @return [HttpHeaders] The default headers for all requests.
24
24
  attr_accessor :headers
25
25
 
26
- # @return [Float] Default request timeout in seconds
26
+ # @return [Numeric, nil] The default request timeout in seconds. If `nil`,
27
+ # the configured `request_timeout` applies.
27
28
  attr_accessor :timeout
28
29
 
29
- # Initializes a new RequestTemplate.
30
+ # Creates a template.
30
31
  #
31
- # @param base_url [String, URI::HTTP, nil] Base URL for relative URIs
32
- # @param headers [Hash] Default headers for all requests
33
- # @param params [Hash, nil] Default query parameters to add to all requests
34
- # @param timeout [Float] Default request timeout in seconds
35
- # @param preprocessors [String, Symbol, Array<String, Symbol>, nil] Default preprocessors
36
- # to apply to all requests
37
- # @param processor [String, Symbol, nil] Default processor name for all requests
38
- def initialize(base_url: nil, headers: {}, params: nil, timeout: 30, preprocessors: nil, processor: nil)
32
+ # @param base_url [String, URI::HTTP, nil] The base URL that relative paths
33
+ # are joined with.
34
+ # @param headers [Hash] The default headers for all requests.
35
+ # @param params [Hash, nil] The default query parameters for all requests.
36
+ # @param timeout [Numeric, nil] The default request timeout in seconds. If
37
+ # `nil`, the configured `request_timeout` applies.
38
+ # @param preprocessors [String, Symbol, Array<String, Symbol>, nil] The names
39
+ # of the default preprocessors for all requests.
40
+ # @param processor [String, Symbol, nil] The name of the default processor for
41
+ # all requests.
42
+ def initialize(base_url: nil, headers: {}, params: nil, timeout: nil, preprocessors: nil, processor: nil)
39
43
  @base_url = base_url
40
44
  @headers = HttpHeaders.new(headers)
41
45
  @params = params
@@ -44,24 +48,34 @@ module PatientHttp
44
48
  @processor = processor
45
49
  end
46
50
 
47
- # Build an async HTTP request. Returns a Request object.
51
+ # Builds a request.
48
52
  #
49
- # @param method [Symbol] HTTP method (:get, :head, :post, :put, :patch, :delete, :query)
50
- # @param uri [String, URI::HTTP] URI path to request (joined with base_url if relative)
51
- # @param body [String, nil] request body
52
- # @param json [Object, nil] JSON object to serialize (cannot use with body)
53
- # @param headers [Hash] additional headers to merge with client headers
54
- # @param params [Hash, nil] query parameters to add to URL
55
- # @param timeout [Numeric, nil] request timeout in seconds (overrides the template default)
56
- # @param follow_method_changing_redirects [Boolean, nil] whether to follow a redirect that changes the
57
- # HTTP method (nil uses the configuration default)
58
- # @param redirect_strip_headers [String, Array<String>, nil] header names (case insensitive)
59
- # to strip from redirected requests, in addition to the configured names
60
- # @param preprocessors [String, Symbol, Array<String, Symbol>, nil] preprocessors to apply
61
- # to the request (overrides the template default)
62
- # @param processor [String, Symbol, nil] processor name for the request (overrides the
63
- # template default)
64
- # @return [Request] request object
53
+ # @param method [Symbol] The HTTP method: `:get`, `:head`, `:post`, `:put`,
54
+ # `:patch`, `:delete`, or `:query`.
55
+ # @param uri [String, URI::HTTP] The URL. A relative path is joined with the
56
+ # base URL.
57
+ # @param body [String, nil] The request body.
58
+ # @param json [Object, nil] An object to send as a JSON body. Can't be combined
59
+ # with `body`.
60
+ # @param headers [Hash, nil] The headers to merge with the template headers.
61
+ # @param params [Hash, nil] The query parameters to merge with the template
62
+ # parameters.
63
+ # @param timeout [Numeric, nil] The request timeout in seconds. Overrides the
64
+ # template timeout.
65
+ # @param max_redirects [Integer, nil] The maximum number of redirects to
66
+ # follow. If `0`, redirects aren't followed. If `nil`, the configuration
67
+ # value applies.
68
+ # @param follow_method_changing_redirects [Boolean, nil] Whether to follow a
69
+ # redirect that changes the HTTP method. If `nil`, the configuration value
70
+ # applies.
71
+ # @param redirect_strip_headers [String, Array<String>, nil] The names of headers
72
+ # to remove from redirected requests, in addition to the configured names.
73
+ # Names are case insensitive.
74
+ # @param preprocessors [String, Symbol, Array<String, Symbol>, nil] The names of
75
+ # the preprocessors for the request. Overrides the template preprocessors.
76
+ # @param processor [String, Symbol, nil] The name of the processor for the
77
+ # request. Overrides the template processor.
78
+ # @return [Request] The request.
65
79
  def request(
66
80
  method,
67
81
  uri,
@@ -70,6 +84,7 @@ module PatientHttp
70
84
  headers: nil,
71
85
  params: nil,
72
86
  timeout: nil,
87
+ max_redirects: nil,
73
88
  follow_method_changing_redirects: nil,
74
89
  redirect_strip_headers: nil,
75
90
  preprocessors: nil,
@@ -89,6 +104,7 @@ module PatientHttp
89
104
  json: json,
90
105
  params: merged_params,
91
106
  timeout: timeout || @timeout,
107
+ max_redirects: max_redirects,
92
108
  follow_method_changing_redirects: follow_method_changing_redirects,
93
109
  redirect_strip_headers: redirect_strip_headers,
94
110
  preprocessors: preprocessors || @preprocessors,
@@ -96,65 +112,72 @@ module PatientHttp
96
112
  )
97
113
  end
98
114
 
99
- # Convenience method for GET requests.
115
+ # Builds a GET request.
100
116
  #
101
- # @param uri [String, URI::HTTP] URI path to request
102
- # @param kwargs [Hash] additional options (see #request)
103
- # @return [Request] request object
117
+ # @param uri [String, URI::HTTP] The URL. A relative path is joined with the
118
+ # base URL.
119
+ # @param kwargs [Hash] The request options. See {#request}.
120
+ # @return [Request] The request.
104
121
  def get(uri, **kwargs)
105
122
  request(:get, uri, **kwargs)
106
123
  end
107
124
 
108
- # Convenience method for HEAD requests.
125
+ # Builds a HEAD request.
109
126
  #
110
- # @param uri [String, URI::HTTP] URI path to request
111
- # @param kwargs [Hash] additional options (see #request)
112
- # @return [Request] request object
127
+ # @param uri [String, URI::HTTP] The URL. A relative path is joined with the
128
+ # base URL.
129
+ # @param kwargs [Hash] The request options. See {#request}.
130
+ # @return [Request] The request.
113
131
  def head(uri, **kwargs)
114
132
  request(:head, uri, **kwargs)
115
133
  end
116
134
 
117
- # Convenience method for POST requests.
135
+ # Builds a POST request.
118
136
  #
119
- # @param uri [String, URI::HTTP] URI path to request
120
- # @param kwargs [Hash] additional options (see #request)
121
- # @return [Request] request object
137
+ # @param uri [String, URI::HTTP] The URL. A relative path is joined with the
138
+ # base URL.
139
+ # @param kwargs [Hash] The request options. See {#request}.
140
+ # @return [Request] The request.
122
141
  def post(uri, **kwargs)
123
142
  request(:post, uri, **kwargs)
124
143
  end
125
144
 
126
- # Convenience method for PUT requests.
145
+ # Builds a PUT request.
127
146
  #
128
- # @param uri [String, URI::HTTP] URI path to request
129
- # @param kwargs [Hash] additional options (see #request)
130
- # @return [Request] request object
147
+ # @param uri [String, URI::HTTP] The URL. A relative path is joined with the
148
+ # base URL.
149
+ # @param kwargs [Hash] The request options. See {#request}.
150
+ # @return [Request] The request.
131
151
  def put(uri, **kwargs)
132
152
  request(:put, uri, **kwargs)
133
153
  end
134
154
 
135
- # Convenience method for PATCH requests.
155
+ # Builds a PATCH request.
136
156
  #
137
- # @param uri [String, URI::HTTP] URI path to request
138
- # @param kwargs [Hash] additional options (see #request)
139
- # @return [Request] request object
157
+ # @param uri [String, URI::HTTP] The URL. A relative path is joined with the
158
+ # base URL.
159
+ # @param kwargs [Hash] The request options. See {#request}.
160
+ # @return [Request] The request.
140
161
  def patch(uri, **kwargs)
141
162
  request(:patch, uri, **kwargs)
142
163
  end
143
164
 
144
- # Convenience method for DELETE requests.
165
+ # Builds a DELETE request.
145
166
  #
146
- # @param uri [String, URI::HTTP] URI path to request
147
- # @param kwargs [Hash] additional options (see #request)
148
- # @return [Request] request object
167
+ # @param uri [String, URI::HTTP] The URL. A relative path is joined with the
168
+ # base URL.
169
+ # @param kwargs [Hash] The request options. See {#request}.
170
+ # @return [Request] The request.
149
171
  def delete(uri, **kwargs)
150
172
  request(:delete, uri, **kwargs)
151
173
  end
152
174
 
153
- # Convenience method for QUERY requests.
175
+ # Builds a QUERY request.
154
176
  #
155
- # @param uri [String, URI::HTTP] URI path to request
156
- # @param kwargs [Hash] additional options (see #request)
157
- # @return [Request] request object
177
+ # @param uri [String, URI::HTTP] The URL. A relative path is joined with the
178
+ # base URL.
179
+ # @param kwargs [Hash] The request options. See {#request}.
180
+ # @return [Request] The request.
158
181
  def query(uri, **kwargs)
159
182
  request(:query, uri, **kwargs)
160
183
  end