patient_http 1.1.2 → 1.3.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.
data/lib/patient_http.rb CHANGED
@@ -51,7 +51,9 @@ module PatientHttp
51
51
  autoload :ExternalStorage, File.join(__dir__, "patient_http/external_storage")
52
52
  autoload :HttpError, File.join(__dir__, "patient_http/http_error")
53
53
  autoload :HttpHeaders, File.join(__dir__, "patient_http/http_headers")
54
+ autoload :InlineTaskHandler, File.join(__dir__, "patient_http/inline_task_handler")
54
55
  autoload :LifecycleManager, File.join(__dir__, "patient_http/lifecycle_manager")
56
+ autoload :OutgoingRequest, File.join(__dir__, "patient_http/outgoing_request")
55
57
  autoload :Payload, File.join(__dir__, "patient_http/payload")
56
58
  autoload :PayloadStore, File.join(__dir__, "patient_http/payload_store")
57
59
  autoload :Processor, File.join(__dir__, "patient_http/processor")
@@ -62,6 +64,7 @@ module PatientHttp
62
64
  autoload :Request, File.join(__dir__, "patient_http/request")
63
65
  autoload :RequestError, File.join(__dir__, "patient_http/request_error")
64
66
  autoload :RequestHelper, File.join(__dir__, "patient_http/request_helper")
67
+ autoload :RequestPreparer, File.join(__dir__, "patient_http/request_preparer")
65
68
  autoload :RequestTask, File.join(__dir__, "patient_http/request_task")
66
69
  autoload :RequestTemplate, File.join(__dir__, "patient_http/request_template")
67
70
  autoload :Response, File.join(__dir__, "patient_http/response")
@@ -76,6 +79,11 @@ module PatientHttp
76
79
  @testing = %w[RAILS_ENV RACK_ENV APP_ENV].any? { |var| ENV[var] == "test" }
77
80
  @handler = nil
78
81
  @handler_mutex = Monitor.new
82
+ @inline_handler = nil
83
+ @default_configuration = nil
84
+ @inline_configuration = nil
85
+ @module_secrets = {}
86
+ @config_mutex = Monitor.new
79
87
 
80
88
  class << self
81
89
  # Check if running in testing mode.
@@ -151,6 +159,81 @@ module PatientHttp
151
159
  end
152
160
  end
153
161
 
162
+ # Registers a request handler that executes requests inline (synchronously,
163
+ # in-process) instead of dispatching them to a job system.
164
+ #
165
+ # This is intended for consoles, tests, and development environments where no
166
+ # job-system integration gem is configured. Each request runs through
167
+ # {SynchronousExecutor} and the callback is invoked on the calling thread
168
+ # before the handler returns.
169
+ #
170
+ # @param config [Configuration, nil] configuration to execute requests against.
171
+ # Defaults to {.default_configuration}, or a lazily created configuration that
172
+ # includes any secrets registered with {.register_secret}.
173
+ # @return [void]
174
+ def inline!(config: nil)
175
+ handler = lambda do |request:, callback:, callback_args: nil, raise_error_responses: nil|
176
+ execute_inline(
177
+ request: request,
178
+ callback: callback,
179
+ callback_args: callback_args,
180
+ raise_error_responses: raise_error_responses,
181
+ config: config
182
+ )
183
+ end
184
+
185
+ @handler_mutex.synchronize do
186
+ register_handler(handler)
187
+ @inline_handler = handler
188
+ end
189
+ end
190
+
191
+ # Check if the currently registered handler is the inline handler registered
192
+ # by {.inline!}.
193
+ #
194
+ # @return [Boolean]
195
+ def inline?
196
+ @handler_mutex.synchronize { !@handler.nil? && @handler.equal?(@inline_handler) }
197
+ end
198
+
199
+ # Check if a request handler is registered.
200
+ #
201
+ # @return [Boolean]
202
+ def handler_registered?
203
+ @handler_mutex.synchronize { !@handler.nil? }
204
+ end
205
+
206
+ # Executes a request inline (synchronously, in-process) through
207
+ # {SynchronousExecutor}, invoking the callback with the response or error
208
+ # before returning.
209
+ #
210
+ # @param request [Request] the HTTP request to execute
211
+ # @param callback [Class, String] the callback class or name
212
+ # @param callback_args [Hash, nil] JSON-compatible callback arguments
213
+ # @param raise_error_responses [Boolean, nil] when true, non-success responses are
214
+ # reported as errors; defaults to the configuration's setting
215
+ # @param config [Configuration, nil] configuration to execute the request against.
216
+ # Defaults to {.default_configuration}, or a lazily created configuration that
217
+ # includes any secrets registered with {.register_secret}.
218
+ # @return [String] the request id
219
+ def execute_inline(request:, callback:, callback_args: nil, raise_error_responses: nil, config: nil)
220
+ config ||= default_configuration || inline_configuration
221
+ raise_error_responses = config.raise_error_responses if raise_error_responses.nil?
222
+
223
+ task = RequestTask.new(
224
+ request: request,
225
+ task_handler: InlineTaskHandler.new,
226
+ callback: callback,
227
+ callback_args: callback_args,
228
+ raise_error_responses: raise_error_responses,
229
+ default_max_redirects: config.max_redirects
230
+ )
231
+
232
+ SynchronousExecutor.new(task, config: config).call
233
+
234
+ task.id
235
+ end
236
+
154
237
  # Executes the registered request handler with the given request parameters.
155
238
  #
156
239
  # @param request [Request] the HTTP request to handle
@@ -238,6 +321,8 @@ module PatientHttp
238
321
  # @param raise_error_responses [Boolean, nil] when true, non-success responses are
239
322
  # reported as errors
240
323
  # @param callback_args [Hash, nil] JSON-compatible callback arguments
324
+ # @param preprocessors [String, Symbol, Array<String, Symbol>, nil] names of preprocessors
325
+ # registered on the configuration to apply to the request when it is sent
241
326
  # @return [Object] return value from the registered request handler
242
327
  def request(
243
328
  method,
@@ -249,9 +334,19 @@ module PatientHttp
249
334
  params: nil,
250
335
  timeout: nil,
251
336
  raise_error_responses: nil,
252
- callback_args: nil
337
+ callback_args: nil,
338
+ preprocessors: nil
253
339
  )
254
- request = Request.new(method, url, body: body, json: json, headers: headers, params: params, timeout: timeout)
340
+ request = Request.new(
341
+ method,
342
+ url,
343
+ body: body,
344
+ json: json,
345
+ headers: headers,
346
+ params: params,
347
+ timeout: timeout,
348
+ preprocessors: preprocessors
349
+ )
255
350
  execute(
256
351
  request: request,
257
352
  callback: callback,
@@ -273,8 +368,93 @@ module PatientHttp
273
368
  SecretReference.new(name)
274
369
  end
275
370
 
371
+ # Register a named secret at the module level, independent of any configuration.
372
+ #
373
+ # Secrets registered here are applied to the {.default_configuration} (immediately
374
+ # if one is already set, or when one is set later) and to the configuration used
375
+ # for inline execution. This makes boot order irrelevant: application code can
376
+ # register secrets before or after the job-system integration gem configures the
377
+ # processor.
378
+ #
379
+ # @param name [String, Symbol] the secret name
380
+ # @param value [Object, nil] the secret value (omit when providing a block)
381
+ # @yield [name] a block that returns the secret value (omit when providing a value)
382
+ # @raise [ArgumentError] if neither or both of value and block are provided
383
+ # @return [void]
384
+ # @see Configuration#register_secret
385
+ def register_secret(name, value = nil, &block)
386
+ if value.nil? && block.nil?
387
+ raise ArgumentError.new("register_secret requires a value or a block")
388
+ end
389
+
390
+ if !value.nil? && block
391
+ raise ArgumentError.new("register_secret accepts either a value or a block, not both")
392
+ end
393
+
394
+ @config_mutex.synchronize do
395
+ secret_value = block || value
396
+ @module_secrets[name.to_s] = secret_value
397
+ @default_configuration&.register_secret(name, secret_value)
398
+ @inline_configuration&.register_secret(name, secret_value)
399
+ end
400
+ end
401
+
402
+ # Check if a secret name is registered, either at the module level via
403
+ # {.register_secret} or on the {.default_configuration}.
404
+ #
405
+ # @param name [String, Symbol] the secret name
406
+ # @return [Boolean]
407
+ def secret_registered?(name)
408
+ @config_mutex.synchronize do
409
+ return true if @module_secrets.include?(name.to_s)
410
+
411
+ !@default_configuration.nil? && @default_configuration.secret_manager.include?(name)
412
+ end
413
+ end
414
+
415
+ # The default configuration used for inline execution when none is provided.
416
+ # Job-system integration gems should set this at the end of their configure
417
+ # step so that module-level secrets registered with {.register_secret} are
418
+ # applied to the configuration the processor runs with.
419
+ #
420
+ # @return [Configuration, nil] the default configuration
421
+ def default_configuration
422
+ @config_mutex.synchronize { @default_configuration }
423
+ end
424
+
425
+ # Set the default configuration. Any secrets registered with {.register_secret}
426
+ # are applied to it; the module-level registry is retained, so re-assigning a
427
+ # new configuration re-applies the same secrets.
428
+ #
429
+ # @param config [Configuration, nil] the configuration to use as the default
430
+ # @return [void]
431
+ def default_configuration=(config)
432
+ @config_mutex.synchronize do
433
+ @default_configuration = config
434
+ apply_module_secrets(config) if config
435
+ end
436
+ end
437
+
276
438
  private
277
439
 
440
+ # The lazily created configuration used for inline execution when no explicit
441
+ # or default configuration is available. Module-level secrets are applied to it.
442
+ #
443
+ # @return [Configuration]
444
+ def inline_configuration
445
+ @config_mutex.synchronize do
446
+ @inline_configuration ||= Configuration.new.tap { |config| apply_module_secrets(config) }
447
+ end
448
+ end
449
+
450
+ # Apply all module-level secrets to the given configuration.
451
+ #
452
+ # @param config [Configuration] the configuration to apply secrets to
453
+ # @return [void]
454
+ def apply_module_secrets(config)
455
+ @module_secrets.each { |name, value| config.register_secret(name, value) }
456
+ end
457
+
278
458
  # Validates that the handler accepts the required keyword arguments.
279
459
  #
280
460
  # @param handler [#call] the handler to validate
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: patient_http
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand
@@ -109,7 +109,9 @@ files:
109
109
  - lib/patient_http/external_storage.rb
110
110
  - lib/patient_http/http_error.rb
111
111
  - lib/patient_http/http_headers.rb
112
+ - lib/patient_http/inline_task_handler.rb
112
113
  - lib/patient_http/lifecycle_manager.rb
114
+ - lib/patient_http/outgoing_request.rb
113
115
  - lib/patient_http/payload.rb
114
116
  - lib/patient_http/payload_store.rb
115
117
  - lib/patient_http/payload_store/active_record_store.rb
@@ -125,6 +127,7 @@ files:
125
127
  - lib/patient_http/request.rb
126
128
  - lib/patient_http/request_error.rb
127
129
  - lib/patient_http/request_helper.rb
130
+ - lib/patient_http/request_preparer.rb
128
131
  - lib/patient_http/request_task.rb
129
132
  - lib/patient_http/request_template.rb
130
133
  - lib/patient_http/response.rb
@@ -156,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
156
159
  - !ruby/object:Gem::Version
157
160
  version: '0'
158
161
  requirements: []
159
- rubygems_version: 3.6.9
162
+ rubygems_version: 4.0.3
160
163
  specification_version: 4
161
164
  summary: Generic async HTTP connection pool for Ruby applications using Fiber-based
162
165
  concurrency