patient_http 1.6.1 → 1.8.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 +18 -8
  3. data/CHANGELOG.md +43 -0
  4. data/README.md +564 -508
  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 +53 -26
  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 +405 -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 +35 -29
  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 +117 -46
  36. data/lib/patient_http/request_error.rb +47 -42
  37. data/lib/patient_http/request_helper.rb +178 -119
  38. data/lib/patient_http/request_preparer.rb +13 -10
  39. data/lib/patient_http/request_task.rb +115 -84
  40. data/lib/patient_http/request_template.rb +119 -64
  41. data/lib/patient_http/response.rb +58 -52
  42. data/lib/patient_http/response_reader.rb +166 -118
  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 +84 -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 +327 -186
  49. data/patient_http.gemspec +3 -2
  50. metadata +21 -5
@@ -1,43 +1,45 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PatientHttp
4
- # Represents an HTTP response from an async request.
4
+ # The HTTP response to an async request.
5
5
  #
6
- # This class encapsulates the response data including status, headers, body,
7
- # and metadata about the request that generated it.
6
+ # A response holds the status, headers, and body, and details about the
7
+ # request that returned it. Responses can be serialized to JSON, so they can
8
+ # go through a job queue.
8
9
  class Response
9
10
  UNDEFINED = Object.new.freeze
10
11
  private_constant :UNDEFINED
11
12
 
12
- # @return [Integer] HTTP status code
13
+ # @return [Integer] The HTTP status code.
13
14
  attr_reader :status
14
15
 
15
- # Response headers. Headers that appeared multiple times in the response
16
- # (such as set-cookie) are flattened into a single joined string value.
16
+ # The response headers. A header that occurs more than one time in the
17
+ # response, such as `set-cookie`, becomes one string with the values joined.
17
18
  #
18
- # @return [HttpHeaders] response headers
19
+ # @return [HttpHeaders] The response headers.
19
20
  attr_reader :headers
20
21
 
21
- # @return [Float] request duration in seconds
22
+ # @return [Float] The request duration in seconds.
22
23
  attr_reader :duration
23
24
 
24
- # @return [String] request ID
25
+ # @return [String] The request ID.
25
26
  attr_reader :request_id
26
27
 
27
- # @return [String] request URL
28
+ # @return [String] The request URL.
28
29
  attr_reader :url
29
30
 
30
- # @return [Symbol] HTTP method
31
+ # @return [Symbol] The HTTP method.
31
32
  attr_reader :http_method
32
33
 
33
- # @return [Array<String>] URLs visited during redirect chain (empty if no redirects)
34
+ # @return [Array<String>] The URLs of the redirects that were followed, in
35
+ # order. Empty if no redirects were followed.
34
36
  attr_reader :redirects
35
37
 
36
38
  class << self
37
- # Reconstruct a Response from a hash
39
+ # Creates a response from its serialized form.
38
40
  #
39
- # @param hash [Hash] hash representation
40
- # @return [Response] reconstructed response
41
+ # @param hash [Hash] The hash from {#as_json}.
42
+ # @return [Response] The response.
41
43
  def load(hash)
42
44
  new(
43
45
  status: hash["status"],
@@ -53,17 +55,18 @@ module PatientHttp
53
55
  end
54
56
  end
55
57
 
56
- # Initialize a Response from an Async::HTTP::Response
58
+ # Creates a response.
57
59
  #
58
- # @param status [Integer] HTTP status code
59
- # @param headers [Hash, HttpHeaders] response headers
60
- # @param body [String, nil] response body
61
- # @param duration [Float] request duration in seconds
62
- # @param request_id [String] the request ID
63
- # @param url [String] the request URL
64
- # @param http_method [Symbol] the HTTP method
65
- # @param callback_args [Hash, nil] callback arguments (string keys)
66
- # @param redirects [Array<String>, nil] URLs visited during redirect chain
60
+ # @param status [Integer] The HTTP status code.
61
+ # @param headers [Hash, HttpHeaders] The response headers.
62
+ # @param body [String, nil] The response body.
63
+ # @param duration [Float] The request duration in seconds.
64
+ # @param request_id [String] The request ID.
65
+ # @param url [String] The request URL.
66
+ # @param http_method [Symbol] The HTTP method.
67
+ # @param callback_args [Hash, nil] The callback arguments, with string keys.
68
+ # @param redirects [Array<String>, nil] The URLs of the redirects that were
69
+ # followed.
67
70
  def initialize(status:, headers:, body:, duration:, request_id:, url:, http_method:, callback_args: nil, redirects: nil)
68
71
  @status = status
69
72
  @headers = HttpHeaders.new(headers)
@@ -80,76 +83,78 @@ module PatientHttp
80
83
  @redirects = redirects || []
81
84
  end
82
85
 
83
- # Returns the callback arguments as a CallbackArgs object.
86
+ # Returns the callback arguments that were passed with the request.
84
87
  #
85
- # @return [CallbackArgs] the callback arguments
88
+ # @return [CallbackArgs] The callback arguments.
86
89
  def callback_args
87
90
  @callback_args ||= CallbackArgs.load(@callback_args_data)
88
91
  end
89
92
 
90
- # Returns the response body, decoding it from the payload if necessary.
93
+ # Returns the response body. The body is decoded on first access.
91
94
  #
92
- # @return [String, nil] The decoded response body or nil if there was no body.
95
+ # @return [String, nil] The response body, or `nil` if the response has no
96
+ # body.
93
97
  def body
94
98
  @body = @payload&.value if @body.equal?(UNDEFINED)
95
99
  @body
96
100
  end
97
101
 
98
- # Check if response is successful (2xx status)
102
+ # Returns whether the status is 2xx.
99
103
  #
100
- # @return [Boolean]
104
+ # @return [Boolean] `true` if the request succeeded.
101
105
  def success?
102
106
  status >= 200 && status < 300
103
107
  end
104
108
 
105
- # Check if response is a redirect (3xx status)
109
+ # Returns whether the status is 3xx.
106
110
  #
107
- # @return [Boolean]
111
+ # @return [Boolean] `true` if the response is a redirect.
108
112
  def redirect?
109
113
  status >= 300 && status < 400
110
114
  end
111
115
 
112
- # Check if response is a client error (4xx status)
116
+ # Returns whether the status is 4xx.
113
117
  #
114
- # @return [Boolean]
118
+ # @return [Boolean] `true` if the response is a client error.
115
119
  def client_error?
116
120
  status >= 400 && status < 500
117
121
  end
118
122
 
119
- # Check if response is a server error (5xx status)
123
+ # Returns whether the status is 5xx.
120
124
  #
121
- # @return [Boolean]
125
+ # @return [Boolean] `true` if the response is a server error.
122
126
  def server_error?
123
127
  status >= 500 && status < 600
124
128
  end
125
129
 
126
- # Check if response is any error (4xx or 5xx status)
130
+ # Returns whether the status is 4xx or 5xx.
127
131
  #
128
- # @return [Boolean]
132
+ # @return [Boolean] `true` if the response is an error.
129
133
  def error?
130
134
  status >= 400 && status < 600
131
135
  end
132
136
 
133
- # Get the Content-Type header
137
+ # Returns the value of the `content-type` header.
134
138
  #
135
- # @return [String, nil]
139
+ # @return [String, nil] The content type, or `nil` if the header isn't set.
136
140
  def content_type
137
141
  headers["content-type"]
138
142
  end
139
143
 
140
- # Return true if Content-Type indicates JSON.
144
+ # Returns whether the `content-type` header identifies a JSON body.
141
145
  #
142
- # @return [Boolean]
146
+ # @return [Boolean] `true` if the body is JSON.
143
147
  def json?
144
148
  type = content_type.to_s.downcase
145
149
  type.match?(%r{\Aapplication/[^ ]*json\b}) || type == "text/json"
146
150
  end
147
151
 
148
- # Parse response body as JSON
152
+ # Parses the response body as JSON.
149
153
  #
150
- # @return [Hash, Array] parsed JSON
151
- # @raise [RuntimeError] if Content-Type is not application/json
152
- # @raise [JSON::ParserError] if body is not valid JSON
154
+ # @return [Hash, Array] The parsed body.
155
+ # @raise [RuntimeError] If the `content-type` header doesn't identify a JSON
156
+ # body.
157
+ # @raise [JSON::ParserError] If the body isn't valid JSON.
153
158
  def json
154
159
  unless json?
155
160
  raise "Response Content-Type is not application/json (got: #{content_type.inspect})"
@@ -158,9 +163,9 @@ module PatientHttp
158
163
  JSON.parse(body)
159
164
  end
160
165
 
161
- # Serialize to JSON hash.
166
+ # Returns the response as a JSON-compatible hash.
162
167
  #
163
- # @return [Hash]
168
+ # @return [Hash] The serialized response.
164
169
  def as_json
165
170
  {
166
171
  "status" => status,
@@ -175,10 +180,11 @@ module PatientHttp
175
180
  }
176
181
  end
177
182
 
178
- # Serialize to JSON string.
183
+ # Returns the response as a JSON string.
179
184
  #
180
- # @param options [Hash] options to pass to JSON.generate (for ActiveSupport compatibility)
181
- # @return [String] JSON representation
185
+ # @param options [Hash, nil] The options for `JSON.generate`. This parameter
186
+ # makes the method compatible with Active Support.
187
+ # @return [String] The JSON string.
182
188
  def to_json(options = nil)
183
189
  JSON.generate(as_json, options)
184
190
  end