zizq 0.5.0 → 0.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.
- checksums.yaml +4 -4
- data/README.md +84 -12
- data/lib/active_job/queue_adapters/zizq_adapter.rb +27 -24
- data/lib/zizq/ack_processor.rb +17 -11
- data/lib/zizq/active_job_config.rb +30 -10
- data/lib/zizq/backoff.rb +5 -1
- data/lib/zizq/bulk_enqueue.rb +67 -14
- data/lib/zizq/client.rb +845 -239
- data/lib/zizq/configuration.rb +15 -14
- data/lib/zizq/crontab.rb +70 -35
- data/lib/zizq/crontab_builder.rb +11 -8
- data/lib/zizq/crontab_entry.rb +36 -35
- data/lib/zizq/crontab_entry_builder.rb +80 -31
- data/lib/zizq/enqueue_request.rb +67 -53
- data/lib/zizq/enqueue_with.rb +66 -11
- data/lib/zizq/error.rb +22 -6
- data/lib/zizq/job.rb +31 -13
- data/lib/zizq/job_config.rb +303 -16
- data/lib/zizq/lifecycle.rb +10 -2
- data/lib/zizq/middleware.rb +7 -6
- data/lib/zizq/query.rb +175 -85
- data/lib/zizq/resources/budget.rb +109 -0
- data/lib/zizq/resources/cron_entry.rb +7 -7
- data/lib/zizq/resources/cron_group.rb +9 -5
- data/lib/zizq/resources/error_enumerator.rb +11 -9
- data/lib/zizq/resources/error_page.rb +2 -1
- data/lib/zizq/resources/error_record.rb +5 -5
- data/lib/zizq/resources/job.rb +149 -26
- data/lib/zizq/resources/job_page.rb +19 -8
- data/lib/zizq/resources/job_template.rb +31 -8
- data/lib/zizq/resources/page.rb +2 -1
- data/lib/zizq/resources/resource.rb +5 -4
- data/lib/zizq/resources.rb +10 -9
- data/lib/zizq/router.rb +3 -2
- data/lib/zizq/test/client.rb +82 -58
- data/lib/zizq/test.rb +8 -8
- data/lib/zizq/tls_configuration.rb +7 -6
- data/lib/zizq/version.rb +1 -1
- data/lib/zizq/worker.rb +68 -50
- data/lib/zizq/worker_configuration.rb +11 -10
- data/lib/zizq.rb +239 -55
- data/sig/generated/zizq/active_job_config.rbs +16 -5
- data/sig/generated/zizq/bulk_enqueue.rbs +4 -15
- data/sig/generated/zizq/client.rbs +378 -8
- data/sig/generated/zizq/crontab.rbs +17 -1
- data/sig/generated/zizq/crontab_builder.rbs +9 -0
- data/sig/generated/zizq/crontab_entry_builder.rbs +4 -16
- data/sig/generated/zizq/enqueue_request.rbs +16 -4
- data/sig/generated/zizq/enqueue_with.rbs +4 -9
- data/sig/generated/zizq/error.rbs +10 -0
- data/sig/generated/zizq/job.rbs +6 -0
- data/sig/generated/zizq/job_config.rbs +163 -0
- data/sig/generated/zizq/query.rbs +79 -2
- data/sig/generated/zizq/resources/budget.rbs +97 -0
- data/sig/generated/zizq/resources/cron_group.rbs +3 -0
- data/sig/generated/zizq/resources/job.rbs +96 -0
- data/sig/generated/zizq/resources/job_template.rbs +16 -2
- data/sig/generated/zizq.rbs +111 -32
- data/sig/zizq.rbs +111 -7
- metadata +4 -2
data/lib/zizq/client.rb
CHANGED
|
@@ -30,10 +30,35 @@ module Zizq
|
|
|
30
30
|
# the async reactor that produced it.
|
|
31
31
|
RawResponse = Data.define(:status, :body, :content_type)
|
|
32
32
|
|
|
33
|
-
CONTENT_TYPES = {
|
|
33
|
+
CONTENT_TYPES = {
|
|
34
34
|
msgpack: "application/msgpack",
|
|
35
35
|
json: "application/json"
|
|
36
|
-
}.freeze
|
|
36
|
+
}.freeze #: Hash[Zizq::format, String]
|
|
37
|
+
|
|
38
|
+
# Filter keys the server accepts as comma-delimited lists, matching
|
|
39
|
+
# any of the named values. An empty value matches nothing, which every
|
|
40
|
+
# filtered call short-circuits on rather than sending a request that
|
|
41
|
+
# would have matched everything. Callers should omit the key entirely
|
|
42
|
+
# if what they really mean is "match everything".
|
|
43
|
+
#
|
|
44
|
+
# These are the Ruby keyword argument names; see `API_FILTER_NAMES` for
|
|
45
|
+
# alternative names used in the API.
|
|
46
|
+
MULTI_FILTER_KEYS = %i[id status queue type budgets_key].freeze #: Array[Symbol]
|
|
47
|
+
|
|
48
|
+
# Filter arguments whose query parameter is spelled differently in
|
|
49
|
+
# the API.
|
|
50
|
+
#
|
|
51
|
+
# `budgets.key` is dotted in the API to mirror the path into the
|
|
52
|
+
# `budgets` array on a job, which is not a valid Ruby keyword, so
|
|
53
|
+
# `validate_where` takes it as `budgets_key:`.
|
|
54
|
+
#
|
|
55
|
+
# The rename happens here, in value position, rather than as a key
|
|
56
|
+
# in `validate_where`'s hash: syntax_tree rewrites a quoted symbol
|
|
57
|
+
# *key* into a string key when the same hash uses Ruby 3.1 omitted
|
|
58
|
+
# values, and a string key silently stops matching
|
|
59
|
+
# `MULTI_FILTER_KEYS` — so an empty filter would be sent as no
|
|
60
|
+
# filter, matching everything.
|
|
61
|
+
API_FILTER_NAMES = { budgets_key: :"budgets.key" }.freeze #: Hash[Symbol, Symbol]
|
|
37
62
|
|
|
38
63
|
STREAM_ACCEPT = { #: Hash[Zizq::format, String]
|
|
39
64
|
msgpack: "application/vnd.zizq.msgpack-stream",
|
|
@@ -62,24 +87,23 @@ module Zizq
|
|
|
62
87
|
# @rbs read_timeout: Numeric
|
|
63
88
|
# @rbs stream_idle_timeout: Numeric
|
|
64
89
|
# @rbs return: void
|
|
65
|
-
def initialize(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
90
|
+
def initialize(
|
|
91
|
+
url:,
|
|
92
|
+
format: :msgpack,
|
|
93
|
+
ssl_context: nil,
|
|
94
|
+
read_timeout: 30,
|
|
95
|
+
stream_idle_timeout: 30
|
|
96
|
+
)
|
|
70
97
|
@url = url.chomp("/")
|
|
71
98
|
@format = format
|
|
72
99
|
|
|
73
100
|
endpoint_options = {
|
|
74
101
|
protocol: Async::HTTP::Protocol::HTTP2,
|
|
75
|
-
timeout: read_timeout
|
|
102
|
+
timeout: read_timeout
|
|
76
103
|
} #: Hash[Symbol, untyped]
|
|
77
104
|
endpoint_options[:ssl_context] = ssl_context if ssl_context
|
|
78
105
|
|
|
79
|
-
@endpoint = Async::HTTP::Endpoint.parse(
|
|
80
|
-
@url,
|
|
81
|
-
**endpoint_options,
|
|
82
|
-
)
|
|
106
|
+
@endpoint = Async::HTTP::Endpoint.parse(@url, **endpoint_options)
|
|
83
107
|
|
|
84
108
|
# Streaming take uses a dedicated HTTP/1.1 endpoint. The take
|
|
85
109
|
# connection is long-lived and carries only one request, so HTTP/2's
|
|
@@ -93,14 +117,13 @@ module Zizq
|
|
|
93
117
|
# timeout so server heartbeats (~3s) keep it alive while only
|
|
94
118
|
# genuinely dead connections (no data for the full window)
|
|
95
119
|
# trigger a reconnect.
|
|
96
|
-
stream_endpoint_options =
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
**stream_endpoint_options
|
|
103
|
-
)
|
|
120
|
+
stream_endpoint_options =
|
|
121
|
+
endpoint_options.merge(
|
|
122
|
+
protocol: Async::HTTP::Protocol::HTTP11,
|
|
123
|
+
timeout: stream_idle_timeout
|
|
124
|
+
)
|
|
125
|
+
@stream_endpoint =
|
|
126
|
+
Async::HTTP::Endpoint.parse(@url, **stream_endpoint_options)
|
|
104
127
|
|
|
105
128
|
@mutex = Mutex.new
|
|
106
129
|
|
|
@@ -154,6 +177,9 @@ module Zizq
|
|
|
154
177
|
# using the Zizq API's expected inputs. Callers should generally use
|
|
155
178
|
# [`Zizq::enqueue`] instead.
|
|
156
179
|
#
|
|
180
|
+
# Durations and timestamps are fractional seconds throughout to align with
|
|
181
|
+
# Ruby, as opposed to the server's integer milliseconds representation.
|
|
182
|
+
#
|
|
157
183
|
# Returns a resource instance of the new job wrapping the API response.
|
|
158
184
|
#
|
|
159
185
|
# @rbs queue: String
|
|
@@ -166,26 +192,34 @@ module Zizq
|
|
|
166
192
|
# @rbs retention: Zizq::retention?
|
|
167
193
|
# @rbs unique_key: String?
|
|
168
194
|
# @rbs unique_while: Zizq::unique_scope?
|
|
195
|
+
# @rbs batch: Zizq::batch?
|
|
196
|
+
# @rbs budgets: Array[Zizq::budget_binding_params]?
|
|
169
197
|
# @rbs return: Resources::Job
|
|
170
|
-
def enqueue(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
198
|
+
def enqueue(
|
|
199
|
+
queue:,
|
|
200
|
+
type:,
|
|
201
|
+
payload:,
|
|
202
|
+
priority: nil,
|
|
203
|
+
ready_at: nil,
|
|
204
|
+
retry_limit: nil,
|
|
205
|
+
backoff: nil,
|
|
206
|
+
retention: nil,
|
|
207
|
+
unique_key: nil,
|
|
208
|
+
unique_while: nil,
|
|
209
|
+
batch: nil,
|
|
210
|
+
budgets: nil
|
|
211
|
+
)
|
|
180
212
|
body = { queue:, type:, payload: } #: Hash[Symbol, untyped]
|
|
181
213
|
body[:priority] = priority if priority
|
|
182
214
|
# ready_at is fractional seconds in Ruby; the server expects ms.
|
|
183
215
|
body[:ready_at] = (ready_at.to_f * 1000).to_i if ready_at
|
|
184
216
|
body[:retry_limit] = retry_limit if retry_limit
|
|
185
|
-
body[:backoff] = backoff if backoff
|
|
186
|
-
body[:retention] = retention if retention
|
|
217
|
+
body[:backoff] = wire_backoff(backoff) if backoff
|
|
218
|
+
body[:retention] = wire_retention(retention) if retention
|
|
187
219
|
body[:unique_key] = unique_key if unique_key
|
|
188
220
|
body[:unique_while] = unique_while.to_s if unique_while
|
|
221
|
+
body[:batch] = batch if batch
|
|
222
|
+
body[:budgets] = budgets.map { |b| wire_binding(b) } if budgets&.any?
|
|
189
223
|
|
|
190
224
|
response = post("/jobs", body)
|
|
191
225
|
data = handle_response!(response, expected: [200, 201])
|
|
@@ -204,18 +238,31 @@ module Zizq
|
|
|
204
238
|
# @rbs return: Array[Resources::Job]
|
|
205
239
|
def enqueue_bulk(jobs:)
|
|
206
240
|
body = {
|
|
207
|
-
jobs:
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
241
|
+
jobs:
|
|
242
|
+
jobs.map do |job|
|
|
243
|
+
wire = {
|
|
244
|
+
type: job[:type],
|
|
245
|
+
queue: job[:queue],
|
|
246
|
+
payload: job[:payload]
|
|
247
|
+
} #: Hash[Symbol, untyped]
|
|
248
|
+
wire[:priority] = job[:priority] if job[:priority]
|
|
249
|
+
# ready_at is fractional seconds in Ruby; the server expects ms.
|
|
250
|
+
wire[:ready_at] = (job[:ready_at].to_f * 1000).to_i if job[
|
|
251
|
+
:ready_at
|
|
252
|
+
]
|
|
253
|
+
wire[:retry_limit] = job[:retry_limit] if job[:retry_limit]
|
|
254
|
+
wire[:backoff] = wire_backoff(job[:backoff]) if job[:backoff]
|
|
255
|
+
wire[:retention] = wire_retention(job[:retention]) if job[
|
|
256
|
+
:retention
|
|
257
|
+
]
|
|
258
|
+
wire[:unique_key] = job[:unique_key] if job[:unique_key]
|
|
259
|
+
wire[:unique_while] = job[:unique_while].to_s if job[:unique_while]
|
|
260
|
+
wire[:batch] = job[:batch] if job[:batch]
|
|
261
|
+
if (budgets = job[:budgets])&.any?
|
|
262
|
+
wire[:budgets] = budgets.map { |b| wire_binding(b) }
|
|
263
|
+
end
|
|
264
|
+
wire
|
|
265
|
+
end
|
|
219
266
|
}
|
|
220
267
|
|
|
221
268
|
response = post("/jobs/bulk", body)
|
|
@@ -253,39 +300,39 @@ module Zizq
|
|
|
253
300
|
# @rbs order: Zizq::sort_direction?
|
|
254
301
|
# @rbs limit: Integer?
|
|
255
302
|
# @rbs return: Resources::JobPage
|
|
256
|
-
def list_jobs(
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
303
|
+
def list_jobs(
|
|
304
|
+
id: nil,
|
|
305
|
+
status: nil,
|
|
306
|
+
queue: nil,
|
|
307
|
+
type: nil,
|
|
308
|
+
filter: nil,
|
|
309
|
+
priority: nil,
|
|
310
|
+
ready_at: nil,
|
|
311
|
+
attempts: nil,
|
|
312
|
+
budgets_key: nil,
|
|
313
|
+
from: nil,
|
|
314
|
+
order: nil,
|
|
315
|
+
limit: nil
|
|
316
|
+
)
|
|
317
|
+
where = {
|
|
268
318
|
id:,
|
|
269
319
|
status:,
|
|
270
320
|
queue:,
|
|
271
321
|
type:,
|
|
272
322
|
filter:,
|
|
273
|
-
priority
|
|
274
|
-
ready_at
|
|
275
|
-
attempts
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
}.compact #: Hash[Symbol, untyped]
|
|
280
|
-
|
|
281
|
-
multi_keys = %i[id status queue type]
|
|
282
|
-
params = build_where_params(options, multi_keys:)
|
|
323
|
+
priority:,
|
|
324
|
+
ready_at:,
|
|
325
|
+
attempts:,
|
|
326
|
+
budgets_key:
|
|
327
|
+
}.compact #: Zizq::where_params
|
|
328
|
+
params = filter_params(where)
|
|
283
329
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
return Resources::JobPage.new(self, { "jobs" => [], "pages" => {} }) if params[key] == ""
|
|
330
|
+
if params.nil?
|
|
331
|
+
return Resources::JobPage.new(self, { "jobs" => [], "pages" => {} })
|
|
287
332
|
end
|
|
288
333
|
|
|
334
|
+
params = params.merge({ from:, order:, limit: }.compact)
|
|
335
|
+
|
|
289
336
|
response = get("/jobs", params:)
|
|
290
337
|
data = handle_response!(response, expected: 200)
|
|
291
338
|
Resources::JobPage.new(self, data)
|
|
@@ -305,32 +352,30 @@ module Zizq
|
|
|
305
352
|
# @rbs ready_at: (Zizq::to_f | Range[Zizq::to_f?])?
|
|
306
353
|
# @rbs attempts: (Integer | Range[Integer?])?
|
|
307
354
|
# @rbs return: Integer
|
|
308
|
-
def count_jobs(
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
355
|
+
def count_jobs(
|
|
356
|
+
id: nil,
|
|
357
|
+
status: nil,
|
|
358
|
+
queue: nil,
|
|
359
|
+
type: nil,
|
|
360
|
+
filter: nil,
|
|
361
|
+
priority: nil,
|
|
362
|
+
ready_at: nil,
|
|
363
|
+
attempts: nil,
|
|
364
|
+
budgets_key: nil
|
|
365
|
+
)
|
|
366
|
+
where = {
|
|
317
367
|
id:,
|
|
318
368
|
status:,
|
|
319
369
|
queue:,
|
|
320
370
|
type:,
|
|
321
371
|
filter:,
|
|
322
|
-
priority
|
|
323
|
-
ready_at
|
|
324
|
-
attempts
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
# An empty filter ([] or "") matches nothing — short-circuit.
|
|
331
|
-
multi_keys.each do |key|
|
|
332
|
-
return 0 if params[key] == ""
|
|
333
|
-
end
|
|
372
|
+
priority:,
|
|
373
|
+
ready_at:,
|
|
374
|
+
attempts:,
|
|
375
|
+
budgets_key:
|
|
376
|
+
}.compact #: Zizq::where_params
|
|
377
|
+
params = filter_params(where)
|
|
378
|
+
return 0 if params.nil?
|
|
334
379
|
|
|
335
380
|
response = get("/jobs/count", params:)
|
|
336
381
|
data = handle_response!(response, expected: 200)
|
|
@@ -357,15 +402,8 @@ module Zizq
|
|
|
357
402
|
# @rbs where: Zizq::where_params
|
|
358
403
|
# @rbs return: Integer
|
|
359
404
|
def delete_all_jobs(where: {})
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
multi_keys = %i[id status queue type]
|
|
363
|
-
params = build_where_params(filter_params, multi_keys:)
|
|
364
|
-
|
|
365
|
-
# An empty multi-value filter matches nothing — short-circuit.
|
|
366
|
-
multi_keys.each do |key|
|
|
367
|
-
return 0 if params[key] == ""
|
|
368
|
-
end
|
|
405
|
+
params = filter_params(where)
|
|
406
|
+
return 0 if params.nil?
|
|
369
407
|
|
|
370
408
|
response = delete("/jobs", params:)
|
|
371
409
|
data = handle_response!(response, expected: 200)
|
|
@@ -408,21 +446,24 @@ module Zizq
|
|
|
408
446
|
# @rbs backoff: (Zizq::backoff | singleton(Zizq::RESET) | singleton(Zizq::UNCHANGED))?
|
|
409
447
|
# @rbs retention: (Zizq::retention | singleton(Zizq::RESET) | singleton(Zizq::UNCHANGED))?
|
|
410
448
|
# @rbs return: Resources::Job
|
|
411
|
-
def update_job(
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
449
|
+
def update_job(
|
|
450
|
+
id,
|
|
451
|
+
queue: UNCHANGED,
|
|
452
|
+
priority: UNCHANGED,
|
|
453
|
+
ready_at: UNCHANGED,
|
|
454
|
+
retry_limit: UNCHANGED,
|
|
455
|
+
backoff: UNCHANGED,
|
|
456
|
+
retention: UNCHANGED
|
|
457
|
+
)
|
|
458
|
+
body =
|
|
459
|
+
build_set_body(
|
|
460
|
+
queue:,
|
|
461
|
+
priority:,
|
|
462
|
+
ready_at:,
|
|
463
|
+
retry_limit:,
|
|
464
|
+
backoff:,
|
|
465
|
+
retention:
|
|
466
|
+
)
|
|
426
467
|
response = patch("/jobs/#{enc(id)}", body)
|
|
427
468
|
data = handle_response!(response, expected: 200)
|
|
428
469
|
Resources::Job.new(self, data)
|
|
@@ -442,15 +483,8 @@ module Zizq
|
|
|
442
483
|
# @rbs apply: Zizq::apply_params
|
|
443
484
|
# @rbs return: Integer
|
|
444
485
|
def update_all_jobs(where: {}, apply: {})
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
multi_keys = %i[id status queue type]
|
|
448
|
-
params = build_where_params(filter_params, multi_keys:)
|
|
449
|
-
|
|
450
|
-
# An empty multi-value filter matches nothing — short-circuit.
|
|
451
|
-
multi_keys.each do |key|
|
|
452
|
-
return 0 if params[key] == ""
|
|
453
|
-
end
|
|
486
|
+
params = filter_params(where)
|
|
487
|
+
return 0 if params.nil?
|
|
454
488
|
|
|
455
489
|
body = validate_and_build_set(**apply)
|
|
456
490
|
response = patch("/jobs", body, params:)
|
|
@@ -503,6 +537,336 @@ module Zizq
|
|
|
503
537
|
data["queues"]
|
|
504
538
|
end
|
|
505
539
|
|
|
540
|
+
# List every defined budget, with its policy.
|
|
541
|
+
#
|
|
542
|
+
# Requires a Pro license on the server, else a `Zizq::ClientError`
|
|
543
|
+
# is raised.
|
|
544
|
+
#
|
|
545
|
+
# @rbs return: Array[Zizq::Resources::Budget]
|
|
546
|
+
def list_budgets
|
|
547
|
+
response = get("/budgets")
|
|
548
|
+
data = handle_response!(response, expected: 200)
|
|
549
|
+
(data["budgets"] || []).map { |b| Resources::Budget.new(self, b) }
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
# Fetch one budget's policy.
|
|
553
|
+
#
|
|
554
|
+
# Raises `Zizq::NotFoundError` if no budget exists under this key.
|
|
555
|
+
#
|
|
556
|
+
# @rbs key: String
|
|
557
|
+
# @rbs return: Zizq::Resources::Budget
|
|
558
|
+
def get_budget(key)
|
|
559
|
+
response = get("/budgets/#{enc(key)}")
|
|
560
|
+
data = handle_response!(response, expected: 200)
|
|
561
|
+
Resources::Budget.new(self, data)
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
# Create a budget, raising if the key would be overwritten.
|
|
565
|
+
#
|
|
566
|
+
# Raises a `Zizq::ConflictError` (409) if a budget already
|
|
567
|
+
# exists under this key, leaving the stored policy untouched.
|
|
568
|
+
#
|
|
569
|
+
# See `#put_budget` for the version that would overwrite the existing
|
|
570
|
+
# key.
|
|
571
|
+
#
|
|
572
|
+
# `strategy` is one of:
|
|
573
|
+
#
|
|
574
|
+
# { type: :time_based, duration: 60 }
|
|
575
|
+
# { type: :time_based, duration: 60, burst: 5 }
|
|
576
|
+
# { type: :while_in_flight }
|
|
577
|
+
#
|
|
578
|
+
# `duration` is fractional seconds and is also read back as fractional
|
|
579
|
+
# seconds.
|
|
580
|
+
#
|
|
581
|
+
# A newly created bucket starts full, so a budget with no burst set
|
|
582
|
+
# hands out an entire allocation the moment work arrives and only
|
|
583
|
+
# then settles to its continuous drip rate — `10` per minute really
|
|
584
|
+
# does permit twenty in the first minute. Setting a burst caps that
|
|
585
|
+
# spike without changing the long-run rate; a burst of `1` paces
|
|
586
|
+
# dispatches evenly at all times. The burst rule also applies to
|
|
587
|
+
# budgets that have not been consumed for an entire duration's worth
|
|
588
|
+
# of time.
|
|
589
|
+
#
|
|
590
|
+
# It is valid for `burst` to be set higher than the `allocation`. In
|
|
591
|
+
# this case the meaning is to continue filling the bucket after the
|
|
592
|
+
# duration has elapsed, so a `burst` of 200 on a 100 token allocation
|
|
593
|
+
# would continue accruing across two consecutive periods, but no more
|
|
594
|
+
# than that.
|
|
595
|
+
#
|
|
596
|
+
# This is designed to allow applications to absorb short-lived
|
|
597
|
+
# spikes.
|
|
598
|
+
#
|
|
599
|
+
# @rbs key: String
|
|
600
|
+
# @rbs allocation: Integer
|
|
601
|
+
# @rbs strategy: Zizq::budget_strategy
|
|
602
|
+
# @rbs return: Zizq::Resources::Budget
|
|
603
|
+
def create_budget(key, allocation:, strategy:)
|
|
604
|
+
body = { allocation:, strategy: wire_strategy(strategy) }
|
|
605
|
+
response = post("/budgets/#{enc(key)}", body)
|
|
606
|
+
data = handle_response!(response, expected: 201)
|
|
607
|
+
Resources::Budget.new(self, data)
|
|
608
|
+
end
|
|
609
|
+
|
|
610
|
+
# Create a budget, or replace an existing budgets policy outright.
|
|
611
|
+
#
|
|
612
|
+
# Unlike `create_budget` this never conflicts. `created_at` is
|
|
613
|
+
# preserved when the budget already exists — a replace changes the
|
|
614
|
+
# policy, not the budget's identity.
|
|
615
|
+
#
|
|
616
|
+
# Raises a `Zizq::ClientError` (422) if the new allocation is below
|
|
617
|
+
# a cost already committed to the budget by a queued job or a cron
|
|
618
|
+
# entry, since those could then never be afforded.
|
|
619
|
+
#
|
|
620
|
+
# `strategy` takes the same shape as `create_budget`.
|
|
621
|
+
#
|
|
622
|
+
# @rbs key: String
|
|
623
|
+
# @rbs allocation: Integer
|
|
624
|
+
# @rbs strategy: Zizq::budget_strategy
|
|
625
|
+
# @rbs return: Zizq::Resources::Budget
|
|
626
|
+
def put_budget(key, allocation:, strategy:)
|
|
627
|
+
body = { allocation:, strategy: wire_strategy(strategy) }
|
|
628
|
+
response = put("/budgets/#{enc(key)}", body)
|
|
629
|
+
data = handle_response!(response, expected: 200)
|
|
630
|
+
Resources::Budget.new(self, data)
|
|
631
|
+
end
|
|
632
|
+
|
|
633
|
+
# Update specified fields of a budget's policy, leaving the rest
|
|
634
|
+
# unchanged.
|
|
635
|
+
#
|
|
636
|
+
# Follows JSON merge patch semantics, and recurses into `strategy`, so
|
|
637
|
+
# e.g. the burst can be changed without restating the type and period:
|
|
638
|
+
#
|
|
639
|
+
# update_budget("emails", strategy: {burst: 5})
|
|
640
|
+
#
|
|
641
|
+
# Within `strategy`, an absent field is left alone and `burst: nil`
|
|
642
|
+
# clears it back to the default value of the allocation. `burst` is
|
|
643
|
+
# the only field with a meaningful "unset": a strategy with no type, or
|
|
644
|
+
# a `time_based` strategy with no period, is not a valid strategy.
|
|
645
|
+
#
|
|
646
|
+
# Raises `Zizq::NotFoundError` if no budget exists under this key.
|
|
647
|
+
#
|
|
648
|
+
# @rbs key: String
|
|
649
|
+
# @rbs allocation: Integer?
|
|
650
|
+
# @rbs strategy: Zizq::budget_strategy_patch?
|
|
651
|
+
# @rbs return: Zizq::Resources::Budget
|
|
652
|
+
def update_budget(key, allocation: nil, strategy: nil)
|
|
653
|
+
body = {
|
|
654
|
+
allocation:,
|
|
655
|
+
strategy: strategy && wire_strategy(strategy)
|
|
656
|
+
}.compact
|
|
657
|
+
response = patch("/budgets/#{enc(key)}", body)
|
|
658
|
+
data = handle_response!(response, expected: 200)
|
|
659
|
+
Resources::Budget.new(self, data)
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
# Delete a budget.
|
|
663
|
+
#
|
|
664
|
+
# Raises `Zizq::NotFoundError` if no budget exists under this key.
|
|
665
|
+
#
|
|
666
|
+
# Raises a `Zizq::ConflictError` (409) while anything still
|
|
667
|
+
# references it.
|
|
668
|
+
#
|
|
669
|
+
# @rbs key: String
|
|
670
|
+
# @rbs return: void
|
|
671
|
+
def delete_budget(key)
|
|
672
|
+
response = delete("/budgets/#{enc(key)}")
|
|
673
|
+
handle_response!(response, expected: 204)
|
|
674
|
+
nil
|
|
675
|
+
end
|
|
676
|
+
|
|
677
|
+
# Replace the whole set of budgets a job is bound to.
|
|
678
|
+
#
|
|
679
|
+
# Budgets omitted from `budgets` will be unbound. Passing `[]` is
|
|
680
|
+
# the same as `clear_job_budgets`.
|
|
681
|
+
#
|
|
682
|
+
# Only queued (scheduled, ready) jobs may have their budgets changed —
|
|
683
|
+
# an in-flight job already debited tokens against its budgets, and
|
|
684
|
+
# jobs in a terminal state are immutable to all further changes.
|
|
685
|
+
# Either raises a `Zizq::ClientError` (422).
|
|
686
|
+
#
|
|
687
|
+
# Requires a Pro license on the server, else a `Zizq::ClientError`
|
|
688
|
+
# (403) is raised.
|
|
689
|
+
#
|
|
690
|
+
# @rbs id: String
|
|
691
|
+
# @rbs budgets: Array[Zizq::budget_binding_params]
|
|
692
|
+
# @rbs return: Resources::Job
|
|
693
|
+
def replace_job_budgets(id, budgets:)
|
|
694
|
+
body = { budgets: budgets.map { |b| wire_binding(b) } }
|
|
695
|
+
response = put("/jobs/#{enc(id)}/budgets", body)
|
|
696
|
+
data = handle_response!(response, expected: 200)
|
|
697
|
+
Resources::Job.new(self, data)
|
|
698
|
+
end
|
|
699
|
+
|
|
700
|
+
# Unbind every budget from a job, leaving it unthrottled.
|
|
701
|
+
#
|
|
702
|
+
# @rbs id: String
|
|
703
|
+
# @rbs return: Resources::Job
|
|
704
|
+
def clear_job_budgets(id)
|
|
705
|
+
response = delete("/jobs/#{enc(id)}/budgets")
|
|
706
|
+
data = handle_response!(response, expected: 200)
|
|
707
|
+
Resources::Job.new(self, data)
|
|
708
|
+
end
|
|
709
|
+
|
|
710
|
+
# Bind a job to a budget it is not already bound to.
|
|
711
|
+
#
|
|
712
|
+
# Raises a `Zizq::ConflictError` (409) if the job is already bound
|
|
713
|
+
# to this budget, leaving the existing binding untouched — use
|
|
714
|
+
# `put_job_budget` to replace it, or `update_job_budget` to change
|
|
715
|
+
# only its cost.
|
|
716
|
+
#
|
|
717
|
+
# `create_with` declares the budget's policy should it not exist
|
|
718
|
+
# yet, binding and creating in one atomic call. It is ignored when
|
|
719
|
+
# the budget already exists.
|
|
720
|
+
#
|
|
721
|
+
# @rbs id: String
|
|
722
|
+
# @rbs key: String
|
|
723
|
+
# @rbs cost: Integer?
|
|
724
|
+
# @rbs create_with: Zizq::budget_policy?
|
|
725
|
+
# @rbs return: Resources::Job
|
|
726
|
+
def add_job_budget(id, key, cost: nil, create_with: nil)
|
|
727
|
+
body = binding_body(cost:, create_with:)
|
|
728
|
+
response = post("/jobs/#{enc(id)}/budgets/#{enc(key)}", body)
|
|
729
|
+
data = handle_response!(response, expected: 200)
|
|
730
|
+
Resources::Job.new(self, data)
|
|
731
|
+
end
|
|
732
|
+
|
|
733
|
+
# Bind a job to a budget, replacing any existing binding to it.
|
|
734
|
+
#
|
|
735
|
+
# Unlike `add_job_budget` this never conflicts. The binding is
|
|
736
|
+
# replaced whole, so a `cost` left unset returns to the default of 1
|
|
737
|
+
# rather than keeping what was there.
|
|
738
|
+
#
|
|
739
|
+
# @rbs id: String
|
|
740
|
+
# @rbs key: String
|
|
741
|
+
# @rbs cost: Integer?
|
|
742
|
+
# @rbs create_with: Zizq::budget_policy?
|
|
743
|
+
# @rbs return: Resources::Job
|
|
744
|
+
def put_job_budget(id, key, cost: nil, create_with: nil)
|
|
745
|
+
body = binding_body(cost:, create_with:)
|
|
746
|
+
response = put("/jobs/#{enc(id)}/budgets/#{enc(key)}", body)
|
|
747
|
+
data = handle_response!(response, expected: 200)
|
|
748
|
+
Resources::Job.new(self, data)
|
|
749
|
+
end
|
|
750
|
+
|
|
751
|
+
# Change what an existing binding costs, leaving the rest alone.
|
|
752
|
+
#
|
|
753
|
+
# Raises `Zizq::NotFoundError` if the job is not bound to this
|
|
754
|
+
# budget. Unlike `put_job_budget`, a change of cost has nothing to
|
|
755
|
+
# apply to and does not create the binding if missing.
|
|
756
|
+
#
|
|
757
|
+
# Raises a `Zizq::ClientError` (422) if the new cost exceeds the
|
|
758
|
+
# budget's capacity.
|
|
759
|
+
#
|
|
760
|
+
# @rbs id: String
|
|
761
|
+
# @rbs key: String
|
|
762
|
+
# @rbs cost: Integer
|
|
763
|
+
# @rbs return: Resources::Job
|
|
764
|
+
def update_job_budget(id, key, cost:)
|
|
765
|
+
response = patch("/jobs/#{enc(id)}/budgets/#{enc(key)}", { cost: })
|
|
766
|
+
data = handle_response!(response, expected: 200)
|
|
767
|
+
Resources::Job.new(self, data)
|
|
768
|
+
end
|
|
769
|
+
|
|
770
|
+
# Unbind one budget from a job, leaving its other budgets alone.
|
|
771
|
+
#
|
|
772
|
+
# Raises `Zizq::NotFoundError` if the job is not bound to it.
|
|
773
|
+
#
|
|
774
|
+
# @rbs id: String
|
|
775
|
+
# @rbs key: String
|
|
776
|
+
# @rbs return: Resources::Job
|
|
777
|
+
def delete_job_budget(id, key)
|
|
778
|
+
response = delete("/jobs/#{enc(id)}/budgets/#{enc(key)}")
|
|
779
|
+
data = handle_response!(response, expected: 200)
|
|
780
|
+
Resources::Job.new(self, data)
|
|
781
|
+
end
|
|
782
|
+
|
|
783
|
+
# Bind every job matching the filter to a budget, skipping over the
|
|
784
|
+
# ones that are already bound to it.
|
|
785
|
+
#
|
|
786
|
+
# The bulk form of `add_job_budget`; each of these takes the same
|
|
787
|
+
# name with `job` widened to `all_jobs`.
|
|
788
|
+
#
|
|
789
|
+
# Filters in the `where:` argument use the same keys as `list_jobs`.
|
|
790
|
+
# An unfiltered call binds every queued job on the server.
|
|
791
|
+
#
|
|
792
|
+
# Unlike the single-job form this does not conflict but simply
|
|
793
|
+
# skips.
|
|
794
|
+
#
|
|
795
|
+
# @rbs key: String
|
|
796
|
+
# @rbs where: Zizq::where_params
|
|
797
|
+
# @rbs cost: Integer?
|
|
798
|
+
# @rbs create_with: Zizq::budget_policy?
|
|
799
|
+
# @rbs return: Zizq::bulk_budget_change
|
|
800
|
+
def add_all_jobs_budget(key, where: {}, cost: nil, create_with: nil)
|
|
801
|
+
bulk_budget_request(
|
|
802
|
+
:post,
|
|
803
|
+
"/jobs/budgets/#{enc(key)}",
|
|
804
|
+
where,
|
|
805
|
+
binding_body(cost:, create_with:)
|
|
806
|
+
)
|
|
807
|
+
end
|
|
808
|
+
|
|
809
|
+
# Bind every job matching the filter to a budget, replacing any
|
|
810
|
+
# existing binding to it.
|
|
811
|
+
#
|
|
812
|
+
# See `add_all_jobs_budget` for the filter semantics, and
|
|
813
|
+
# `put_job_budget` for how a binding is replaced.
|
|
814
|
+
#
|
|
815
|
+
# @rbs key: String
|
|
816
|
+
# @rbs where: Zizq::where_params
|
|
817
|
+
# @rbs cost: Integer?
|
|
818
|
+
# @rbs create_with: Zizq::budget_policy?
|
|
819
|
+
# @rbs return: Zizq::bulk_budget_change
|
|
820
|
+
def put_all_jobs_budget(key, where: {}, cost: nil, create_with: nil)
|
|
821
|
+
bulk_budget_request(
|
|
822
|
+
:put,
|
|
823
|
+
"/jobs/budgets/#{enc(key)}",
|
|
824
|
+
where,
|
|
825
|
+
binding_body(cost:, create_with:)
|
|
826
|
+
)
|
|
827
|
+
end
|
|
828
|
+
|
|
829
|
+
# Change what an existing binding costs, across every job matching
|
|
830
|
+
# the filter. Jobs not bound to this budget are skipped.
|
|
831
|
+
#
|
|
832
|
+
# The bulk form of `update_job_budget`.
|
|
833
|
+
#
|
|
834
|
+
# Raises a `Zizq::ClientError` (422) if the new cost exceeds the
|
|
835
|
+
# budget's capacity.
|
|
836
|
+
#
|
|
837
|
+
# @rbs key: String
|
|
838
|
+
# @rbs cost: Integer
|
|
839
|
+
# @rbs where: Zizq::where_params
|
|
840
|
+
# @rbs return: Zizq::bulk_budget_change
|
|
841
|
+
def update_all_jobs_budget(key, cost:, where: {})
|
|
842
|
+
bulk_budget_request(:patch, "/jobs/budgets/#{enc(key)}", where, { cost: })
|
|
843
|
+
end
|
|
844
|
+
|
|
845
|
+
# Unbind one budget from every job matching the filter, leaving
|
|
846
|
+
# their other budgets alone. The bulk form of `delete_job_budget`.
|
|
847
|
+
#
|
|
848
|
+
# This is the way to drain a budget before deleting it, since
|
|
849
|
+
# `delete_budget` refuses while anything is still bound to it.
|
|
850
|
+
#
|
|
851
|
+
# @rbs key: String
|
|
852
|
+
# @rbs where: Zizq::where_params
|
|
853
|
+
# @rbs return: Zizq::bulk_budget_change
|
|
854
|
+
def delete_all_jobs_budget(key, where: {})
|
|
855
|
+
bulk_budget_request(:delete, "/jobs/budgets/#{enc(key)}", where, nil)
|
|
856
|
+
end
|
|
857
|
+
|
|
858
|
+
# Unbind *every* budget from every job matching the filter. The
|
|
859
|
+
# bulk form of `clear_job_budgets`.
|
|
860
|
+
#
|
|
861
|
+
# Beware: An unfiltered call leaves every queued job on the server
|
|
862
|
+
# unthrottled — include a filter unless that is the intent.
|
|
863
|
+
#
|
|
864
|
+
# @rbs where: Zizq::where_params
|
|
865
|
+
# @rbs return: Zizq::bulk_budget_change
|
|
866
|
+
def clear_all_jobs_budgets(where: {})
|
|
867
|
+
bulk_budget_request(:delete, "/jobs/budgets", where, nil)
|
|
868
|
+
end
|
|
869
|
+
|
|
506
870
|
# List all cron group names.
|
|
507
871
|
#
|
|
508
872
|
# @rbs return: Array[String]
|
|
@@ -527,13 +891,19 @@ module Zizq
|
|
|
527
891
|
# Entries not present in the request are removed. Entries with unchanged
|
|
528
892
|
# expressions preserve their scheduling state.
|
|
529
893
|
#
|
|
894
|
+
# The timezone applies to every entry that does not specify one of its
|
|
895
|
+
# own. Since this replaces the group in full, omitting it clears whatever
|
|
896
|
+
# the group had.
|
|
897
|
+
#
|
|
530
898
|
# @rbs name: String
|
|
531
899
|
# @rbs paused: bool?
|
|
900
|
+
# @rbs timezone: String?
|
|
532
901
|
# @rbs entries: Array[Zizq::cron_entry_params]
|
|
533
902
|
# @rbs return: Resources::CronGroup
|
|
534
|
-
def replace_cron_group(name, paused: nil, entries: [])
|
|
903
|
+
def replace_cron_group(name, paused: nil, timezone: nil, entries: [])
|
|
535
904
|
body = {
|
|
536
905
|
paused:,
|
|
906
|
+
timezone:,
|
|
537
907
|
entries: entries.map { |entry| build_cron_entry(**entry) }
|
|
538
908
|
}.compact
|
|
539
909
|
response = put("/crons/#{enc(name)}", body)
|
|
@@ -592,7 +962,7 @@ module Zizq
|
|
|
592
962
|
|
|
593
963
|
# Add a single entry to a cron group (creates the group if needed).
|
|
594
964
|
#
|
|
595
|
-
# Raises a
|
|
965
|
+
# Raises a `Zizq::ConflictError` (409) if an entry with the same name
|
|
596
966
|
# already exists.
|
|
597
967
|
#
|
|
598
968
|
# @rbs group: String
|
|
@@ -602,7 +972,14 @@ module Zizq
|
|
|
602
972
|
# @rbs timezone: String?
|
|
603
973
|
# @rbs paused: bool?
|
|
604
974
|
# @rbs return: Resources::CronEntry
|
|
605
|
-
def add_cron_group_entry(
|
|
975
|
+
def add_cron_group_entry(
|
|
976
|
+
group,
|
|
977
|
+
name:,
|
|
978
|
+
expression:,
|
|
979
|
+
job:,
|
|
980
|
+
timezone: nil,
|
|
981
|
+
paused: nil
|
|
982
|
+
)
|
|
606
983
|
body = build_cron_entry(name:, expression:, job:, timezone:, paused:)
|
|
607
984
|
response = post("/crons/#{enc(group)}/entries", body)
|
|
608
985
|
data = handle_response!(response, expected: 201)
|
|
@@ -620,8 +997,16 @@ module Zizq
|
|
|
620
997
|
# @rbs timezone: String?
|
|
621
998
|
# @rbs paused: bool?
|
|
622
999
|
# @rbs return: Resources::CronEntry
|
|
623
|
-
def replace_cron_group_entry(
|
|
624
|
-
|
|
1000
|
+
def replace_cron_group_entry(
|
|
1001
|
+
group,
|
|
1002
|
+
entry,
|
|
1003
|
+
expression:,
|
|
1004
|
+
job:,
|
|
1005
|
+
timezone: nil,
|
|
1006
|
+
paused: nil
|
|
1007
|
+
)
|
|
1008
|
+
body =
|
|
1009
|
+
build_cron_entry(name: entry, expression:, job:, timezone:, paused:)
|
|
625
1010
|
response = put("/crons/#{enc(group)}/entries/#{enc(entry)}", body)
|
|
626
1011
|
data = handle_response!(response, expected: 200)
|
|
627
1012
|
Resources::CronEntry.new(self, data)
|
|
@@ -634,7 +1019,8 @@ module Zizq
|
|
|
634
1019
|
# @rbs paused: bool
|
|
635
1020
|
# @rbs return: Resources::CronEntry
|
|
636
1021
|
def update_cron_group_entry(group, entry, paused:)
|
|
637
|
-
response =
|
|
1022
|
+
response =
|
|
1023
|
+
patch("/crons/#{enc(group)}/entries/#{enc(entry)}", { paused: })
|
|
638
1024
|
data = handle_response!(response, expected: 200)
|
|
639
1025
|
Resources::CronEntry.new(self, data)
|
|
640
1026
|
end
|
|
@@ -723,7 +1109,14 @@ module Zizq
|
|
|
723
1109
|
# @rbs retry_at: Float?
|
|
724
1110
|
# @rbs kill: bool
|
|
725
1111
|
# @rbs return: Resources::Job
|
|
726
|
-
def report_failure(
|
|
1112
|
+
def report_failure(
|
|
1113
|
+
id,
|
|
1114
|
+
message:,
|
|
1115
|
+
error_type: nil,
|
|
1116
|
+
backtrace: nil,
|
|
1117
|
+
retry_at: nil,
|
|
1118
|
+
kill: false
|
|
1119
|
+
)
|
|
727
1120
|
body = { message: } #: Hash[Symbol, untyped]
|
|
728
1121
|
body[:error_type] = error_type if error_type
|
|
729
1122
|
body[:backtrace] = backtrace if backtrace
|
|
@@ -772,7 +1165,14 @@ module Zizq
|
|
|
772
1165
|
# @rbs worker_id: String?
|
|
773
1166
|
# @rbs &block: (Resources::Job) -> void
|
|
774
1167
|
# @rbs return: void
|
|
775
|
-
def take_jobs(
|
|
1168
|
+
def take_jobs(
|
|
1169
|
+
prefetch: 1,
|
|
1170
|
+
queues: [],
|
|
1171
|
+
worker_id: nil,
|
|
1172
|
+
on_connect: nil,
|
|
1173
|
+
on_response: nil,
|
|
1174
|
+
&block
|
|
1175
|
+
)
|
|
776
1176
|
raise ArgumentError, "take_jobs requires a block" unless block
|
|
777
1177
|
|
|
778
1178
|
params = { prefetch: } #: Hash[Symbol, untyped]
|
|
@@ -786,7 +1186,10 @@ module Zizq
|
|
|
786
1186
|
response = stream_http.get(path, headers)
|
|
787
1187
|
|
|
788
1188
|
begin
|
|
789
|
-
|
|
1189
|
+
unless response.status == 200
|
|
1190
|
+
raise StreamError,
|
|
1191
|
+
"take jobs stream returned HTTP #{response.status}"
|
|
1192
|
+
end
|
|
790
1193
|
on_connect&.call
|
|
791
1194
|
on_response&.call(response)
|
|
792
1195
|
|
|
@@ -800,11 +1203,17 @@ module Zizq
|
|
|
800
1203
|
body = response.body || []
|
|
801
1204
|
|
|
802
1205
|
case @format
|
|
803
|
-
when :json
|
|
804
|
-
|
|
1206
|
+
when :json
|
|
1207
|
+
self.class.parse_ndjson(body, &wrapper)
|
|
1208
|
+
when :msgpack
|
|
1209
|
+
self.class.parse_msgpack_stream(body, &wrapper)
|
|
805
1210
|
end
|
|
806
1211
|
ensure
|
|
807
|
-
|
|
1212
|
+
begin
|
|
1213
|
+
response.close
|
|
1214
|
+
rescue StandardError
|
|
1215
|
+
nil
|
|
1216
|
+
end
|
|
808
1217
|
end
|
|
809
1218
|
end
|
|
810
1219
|
rescue SocketError,
|
|
@@ -887,7 +1296,10 @@ module Zizq
|
|
|
887
1296
|
# so that resource objects like Page can follow links without resorting
|
|
888
1297
|
# to `.send`.
|
|
889
1298
|
def get_path(path) #: (String) -> Hash[String, untyped]
|
|
890
|
-
response =
|
|
1299
|
+
response =
|
|
1300
|
+
request do |http|
|
|
1301
|
+
consume_response(http.get(path, { "accept" => @content_type }))
|
|
1302
|
+
end
|
|
891
1303
|
handle_response!(response, expected: 200)
|
|
892
1304
|
end
|
|
893
1305
|
|
|
@@ -900,9 +1312,7 @@ module Zizq
|
|
|
900
1312
|
|
|
901
1313
|
# Build a relative path with optional query parameters.
|
|
902
1314
|
def build_path(path, params: {}) #: (String, ?params: Hash[Symbol, untyped]) -> String
|
|
903
|
-
unless params.empty?
|
|
904
|
-
path = "#{path}?#{URI.encode_www_form(params)}"
|
|
905
|
-
end
|
|
1315
|
+
path = "#{path}?#{URI.encode_www_form(params)}" unless params.empty?
|
|
906
1316
|
path
|
|
907
1317
|
end
|
|
908
1318
|
|
|
@@ -914,13 +1324,19 @@ module Zizq
|
|
|
914
1324
|
# @rbs timezone: String?
|
|
915
1325
|
# @rbs paused: bool?
|
|
916
1326
|
# @rbs return: Hash[Symbol, untyped]
|
|
917
|
-
def build_cron_entry(
|
|
1327
|
+
def build_cron_entry(
|
|
1328
|
+
name: nil,
|
|
1329
|
+
expression: nil,
|
|
1330
|
+
job: nil,
|
|
1331
|
+
timezone: nil,
|
|
1332
|
+
paused: nil
|
|
1333
|
+
)
|
|
918
1334
|
{
|
|
919
1335
|
name:,
|
|
920
1336
|
expression:,
|
|
921
1337
|
timezone:,
|
|
922
1338
|
paused:,
|
|
923
|
-
job: build_cron_job(**job)
|
|
1339
|
+
job: build_cron_job(**job)
|
|
924
1340
|
}.compact
|
|
925
1341
|
end
|
|
926
1342
|
|
|
@@ -937,23 +1353,31 @@ module Zizq
|
|
|
937
1353
|
# @rbs retention: Zizq::retention?
|
|
938
1354
|
# @rbs unique_key: String?
|
|
939
1355
|
# @rbs unique_while: Zizq::unique_scope?
|
|
1356
|
+
# @rbs batch: Zizq::batch?
|
|
1357
|
+
# @rbs budgets: Array[Zizq::budget_binding_params]?
|
|
940
1358
|
# @rbs return: Hash[Symbol, untyped]
|
|
941
|
-
def build_cron_job(
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1359
|
+
def build_cron_job(
|
|
1360
|
+
type: nil,
|
|
1361
|
+
queue: nil,
|
|
1362
|
+
payload: nil,
|
|
1363
|
+
priority: nil,
|
|
1364
|
+
retry_limit: nil,
|
|
1365
|
+
backoff: nil,
|
|
1366
|
+
retention: nil,
|
|
1367
|
+
unique_key: nil,
|
|
1368
|
+
unique_while: nil,
|
|
1369
|
+
batch: nil,
|
|
1370
|
+
budgets: nil
|
|
1371
|
+
)
|
|
950
1372
|
job = { type:, queue:, payload: } #: Hash[Symbol, untyped]
|
|
951
1373
|
job[:priority] = priority if priority
|
|
952
1374
|
job[:retry_limit] = retry_limit if retry_limit
|
|
953
|
-
job[:backoff] = backoff if backoff
|
|
954
|
-
job[:retention] = retention if retention
|
|
1375
|
+
job[:backoff] = wire_backoff(backoff) if backoff
|
|
1376
|
+
job[:retention] = wire_retention(retention) if retention
|
|
955
1377
|
job[:unique_key] = unique_key if unique_key
|
|
956
1378
|
job[:unique_while] = unique_while.to_s if unique_while
|
|
1379
|
+
job[:batch] = batch if batch
|
|
1380
|
+
job[:budgets] = budgets.map { |b| wire_binding(b) } if budgets&.any?
|
|
957
1381
|
job
|
|
958
1382
|
end
|
|
959
1383
|
|
|
@@ -969,15 +1393,19 @@ module Zizq
|
|
|
969
1393
|
# @rbs priority: (Integer | Range[Integer?])?
|
|
970
1394
|
# @rbs ready_at: (Zizq::to_f | Range[Zizq::to_f?])?
|
|
971
1395
|
# @rbs attempts: (Integer | Range[Integer?])?
|
|
1396
|
+
# @rbs budgets_key: (String | Array[String])?
|
|
972
1397
|
# @rbs return: Hash[Symbol, untyped]
|
|
973
|
-
def validate_where(
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1398
|
+
def validate_where(
|
|
1399
|
+
id: nil,
|
|
1400
|
+
status: nil,
|
|
1401
|
+
queue: nil,
|
|
1402
|
+
type: nil,
|
|
1403
|
+
filter: nil,
|
|
1404
|
+
priority: nil,
|
|
1405
|
+
ready_at: nil,
|
|
1406
|
+
attempts: nil,
|
|
1407
|
+
budgets_key: nil
|
|
1408
|
+
)
|
|
981
1409
|
{
|
|
982
1410
|
id:,
|
|
983
1411
|
status:,
|
|
@@ -987,6 +1415,7 @@ module Zizq
|
|
|
987
1415
|
priority: encode_range(priority),
|
|
988
1416
|
ready_at: encode_range(ready_at) { |v| (v.to_f * 1000).to_i },
|
|
989
1417
|
attempts: encode_range(attempts),
|
|
1418
|
+
budgets_key:
|
|
990
1419
|
}.compact
|
|
991
1420
|
end
|
|
992
1421
|
|
|
@@ -1016,7 +1445,7 @@ module Zizq
|
|
|
1016
1445
|
when Range
|
|
1017
1446
|
if value.exclude_end?
|
|
1018
1447
|
raise ArgumentError,
|
|
1019
|
-
|
|
1448
|
+
"exclusive ranges are not supported by the server; use an inclusive range (a..b) instead"
|
|
1020
1449
|
end
|
|
1021
1450
|
"#{value.begin&.then(&block)}..#{value.end&.then(&block)}"
|
|
1022
1451
|
else
|
|
@@ -1034,13 +1463,22 @@ module Zizq
|
|
|
1034
1463
|
# @rbs backoff: (Zizq::backoff | singleton(Zizq::RESET) | singleton(Zizq::UNCHANGED))?
|
|
1035
1464
|
# @rbs retention: (Zizq::retention | singleton(Zizq::RESET) | singleton(Zizq::UNCHANGED))?
|
|
1036
1465
|
# @rbs return: Hash[Symbol, untyped]
|
|
1037
|
-
def validate_and_build_set(
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1466
|
+
def validate_and_build_set(
|
|
1467
|
+
queue: UNCHANGED,
|
|
1468
|
+
priority: UNCHANGED,
|
|
1469
|
+
ready_at: UNCHANGED,
|
|
1470
|
+
retry_limit: UNCHANGED,
|
|
1471
|
+
backoff: UNCHANGED,
|
|
1472
|
+
retention: UNCHANGED
|
|
1473
|
+
)
|
|
1474
|
+
build_set_body(
|
|
1475
|
+
queue:,
|
|
1476
|
+
priority:,
|
|
1477
|
+
ready_at:,
|
|
1478
|
+
retry_limit:,
|
|
1479
|
+
backoff:,
|
|
1480
|
+
retention:
|
|
1481
|
+
)
|
|
1044
1482
|
end
|
|
1045
1483
|
|
|
1046
1484
|
# Build the JSON body hash for a PATCH request from set parameters.
|
|
@@ -1051,26 +1489,40 @@ module Zizq
|
|
|
1051
1489
|
# - Other values are converted to their wire format.
|
|
1052
1490
|
#
|
|
1053
1491
|
# @rbs return: Hash[Symbol, untyped]
|
|
1054
|
-
def build_set_body(
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1492
|
+
def build_set_body(
|
|
1493
|
+
queue: UNCHANGED,
|
|
1494
|
+
priority: UNCHANGED,
|
|
1495
|
+
ready_at: UNCHANGED,
|
|
1496
|
+
retry_limit: UNCHANGED,
|
|
1497
|
+
backoff: UNCHANGED,
|
|
1498
|
+
retention: UNCHANGED
|
|
1499
|
+
)
|
|
1060
1500
|
body = {} #: Hash[Symbol, untyped]
|
|
1061
1501
|
|
|
1062
1502
|
unless queue.equal?(UNCHANGED)
|
|
1063
|
-
|
|
1503
|
+
if queue.nil?
|
|
1504
|
+
raise ArgumentError,
|
|
1505
|
+
"queue cannot be nil; use Zizq::RESET to clear or Zizq::UNCHANGED to leave as-is"
|
|
1506
|
+
end
|
|
1064
1507
|
body[:queue] = queue
|
|
1065
1508
|
end
|
|
1066
1509
|
|
|
1067
1510
|
unless priority.equal?(UNCHANGED)
|
|
1068
|
-
|
|
1511
|
+
if priority.nil?
|
|
1512
|
+
raise ArgumentError,
|
|
1513
|
+
"priority cannot be nil; use Zizq::RESET to clear or Zizq::UNCHANGED to leave as-is"
|
|
1514
|
+
end
|
|
1069
1515
|
body[:priority] = priority
|
|
1070
1516
|
end
|
|
1071
1517
|
|
|
1072
1518
|
unless ready_at.equal?(UNCHANGED)
|
|
1073
|
-
body[:ready_at] =
|
|
1519
|
+
body[:ready_at] = (
|
|
1520
|
+
if ready_at.equal?(RESET)
|
|
1521
|
+
nil
|
|
1522
|
+
else
|
|
1523
|
+
(ready_at.to_f * 1000).to_i
|
|
1524
|
+
end
|
|
1525
|
+
)
|
|
1074
1526
|
end
|
|
1075
1527
|
|
|
1076
1528
|
unless retry_limit.equal?(UNCHANGED)
|
|
@@ -1078,39 +1530,179 @@ module Zizq
|
|
|
1078
1530
|
end
|
|
1079
1531
|
|
|
1080
1532
|
unless backoff.equal?(UNCHANGED)
|
|
1081
|
-
body[:backoff] =
|
|
1082
|
-
nil
|
|
1083
|
-
else
|
|
1084
|
-
{
|
|
1085
|
-
exponent: backoff[:exponent].to_f,
|
|
1086
|
-
base_ms: (backoff[:base].to_f * 1000).to_i,
|
|
1087
|
-
jitter_ms: (backoff[:jitter].to_f * 1000).to_i
|
|
1088
|
-
}
|
|
1089
|
-
end
|
|
1533
|
+
body[:backoff] = backoff.equal?(RESET) ? nil : wire_backoff(backoff)
|
|
1090
1534
|
end
|
|
1091
1535
|
|
|
1092
1536
|
unless retention.equal?(UNCHANGED)
|
|
1093
1537
|
body[:retention] = if retention.equal?(RESET)
|
|
1094
1538
|
nil
|
|
1095
1539
|
else
|
|
1096
|
-
|
|
1097
|
-
ret[:completed_ms] = (retention[:completed].to_f * 1000).to_i if retention[:completed]
|
|
1098
|
-
ret[:dead_ms] = (retention[:dead].to_f * 1000).to_i if retention[:dead]
|
|
1099
|
-
ret
|
|
1540
|
+
wire_retention(retention)
|
|
1100
1541
|
end
|
|
1101
1542
|
end
|
|
1102
1543
|
|
|
1103
1544
|
body
|
|
1104
1545
|
end
|
|
1105
1546
|
|
|
1547
|
+
# Convert a `Zizq::backoff` (fractional seconds) into the server's form
|
|
1548
|
+
# (integer milliseconds under keys suffixed `_ms`).
|
|
1549
|
+
#
|
|
1550
|
+
# @rbs backoff: Zizq::backoff
|
|
1551
|
+
# @rbs return: Hash[Symbol, untyped]
|
|
1552
|
+
def wire_backoff(backoff)
|
|
1553
|
+
{
|
|
1554
|
+
exponent: backoff[:exponent].to_f,
|
|
1555
|
+
base_ms: (backoff[:base].to_f * 1000).to_i,
|
|
1556
|
+
jitter_ms: (backoff[:jitter].to_f * 1000).to_i
|
|
1557
|
+
}
|
|
1558
|
+
end
|
|
1559
|
+
|
|
1560
|
+
# Convert a `Zizq::retention` (fractional seconds) into the server's form
|
|
1561
|
+
# (integer milliseconds under keys suffixed `_ms`). Both fields are
|
|
1562
|
+
# optional.
|
|
1563
|
+
#
|
|
1564
|
+
# @rbs retention: Zizq::retention
|
|
1565
|
+
# @rbs return: Hash[Symbol, Integer]
|
|
1566
|
+
def wire_retention(retention)
|
|
1567
|
+
wire = {} #: Hash[Symbol, Integer]
|
|
1568
|
+
wire[:completed_ms] = (
|
|
1569
|
+
retention[:completed].to_f * 1000
|
|
1570
|
+
).to_i if retention[:completed]
|
|
1571
|
+
wire[:dead_ms] = (retention[:dead].to_f * 1000).to_i if retention[:dead]
|
|
1572
|
+
wire
|
|
1573
|
+
end
|
|
1574
|
+
|
|
1575
|
+
# Build the body for a single binding, where the budget key travels
|
|
1576
|
+
# in the path rather than the body.
|
|
1577
|
+
#
|
|
1578
|
+
# @rbs cost: Integer?
|
|
1579
|
+
# @rbs create_with: Zizq::budget_policy?
|
|
1580
|
+
# @rbs return: Hash[Symbol, untyped]
|
|
1581
|
+
def binding_body(cost:, create_with:)
|
|
1582
|
+
body = {} #: Hash[Symbol, untyped]
|
|
1583
|
+
body[:cost] = cost if cost
|
|
1584
|
+
body[:create_with] = wire_policy(create_with) if create_with
|
|
1585
|
+
body
|
|
1586
|
+
end
|
|
1587
|
+
|
|
1588
|
+
# Convert a `Zizq::budget_binding_params` into the wire form, key included.
|
|
1589
|
+
#
|
|
1590
|
+
# @rbs binding: Zizq::budget_binding_params
|
|
1591
|
+
# @rbs return: Hash[Symbol, untyped]
|
|
1592
|
+
def wire_binding(binding)
|
|
1593
|
+
{ key: binding[:key] }.merge(
|
|
1594
|
+
binding_body(cost: binding[:cost], create_with: binding[:create_with])
|
|
1595
|
+
)
|
|
1596
|
+
end
|
|
1597
|
+
|
|
1598
|
+
# Convert a `Zizq::budget_policy` into the wire form.
|
|
1599
|
+
#
|
|
1600
|
+
# @rbs policy: Zizq::budget_policy
|
|
1601
|
+
# @rbs return: Hash[Symbol, untyped]
|
|
1602
|
+
def wire_policy(policy)
|
|
1603
|
+
{
|
|
1604
|
+
allocation: policy[:allocation],
|
|
1605
|
+
strategy: wire_strategy(policy[:strategy])
|
|
1606
|
+
}
|
|
1607
|
+
end
|
|
1608
|
+
|
|
1609
|
+
# Send one of the bulk job-budget requests and read its result.
|
|
1610
|
+
#
|
|
1611
|
+
# `body` is `nil` for the two that carry none, which are sent as
|
|
1612
|
+
# `DELETE` with the filter in the query string.
|
|
1613
|
+
#
|
|
1614
|
+
# @rbs method: Symbol
|
|
1615
|
+
# @rbs path: String
|
|
1616
|
+
# @rbs where: Zizq::where_params
|
|
1617
|
+
# @rbs body: Hash[Symbol, untyped]?
|
|
1618
|
+
# @rbs return: Zizq::bulk_budget_change
|
|
1619
|
+
def bulk_budget_request(method, path, where, body)
|
|
1620
|
+
params = filter_params(where)
|
|
1621
|
+
|
|
1622
|
+
# An empty multi-value filter matches nothing — short-circuit
|
|
1623
|
+
# rather than send a request that would match everything.
|
|
1624
|
+
return { changed: 0, blocked: [] } if params.nil?
|
|
1625
|
+
|
|
1626
|
+
response =
|
|
1627
|
+
case method
|
|
1628
|
+
when :post
|
|
1629
|
+
post(build_path(path, params:), body || {})
|
|
1630
|
+
when :put
|
|
1631
|
+
put(build_path(path, params:), body || {})
|
|
1632
|
+
when :patch
|
|
1633
|
+
patch(path, body || {}, params:)
|
|
1634
|
+
else
|
|
1635
|
+
delete(path, params:)
|
|
1636
|
+
end
|
|
1637
|
+
data = handle_response!(response, expected: 200)
|
|
1638
|
+
{ changed: data.fetch("changed"), blocked: data.fetch("blocked") }
|
|
1639
|
+
end
|
|
1640
|
+
|
|
1641
|
+
# Validate a `where:` filter and build its query params.
|
|
1642
|
+
#
|
|
1643
|
+
# Returns `nil` when a multi-value filter is present but empty,
|
|
1644
|
+
# which matches nothing — callers short-circuit rather than send a
|
|
1645
|
+
# request that would match everything instead.
|
|
1646
|
+
#
|
|
1647
|
+
# @rbs where: Zizq::where_params
|
|
1648
|
+
# @rbs return: Hash[Symbol, untyped]?
|
|
1649
|
+
def filter_params(where)
|
|
1650
|
+
params =
|
|
1651
|
+
build_where_params(
|
|
1652
|
+
validate_where(**where),
|
|
1653
|
+
multi_keys: MULTI_FILTER_KEYS
|
|
1654
|
+
)
|
|
1655
|
+
|
|
1656
|
+
# Both `[]` and `""` mean "match nothing"; an empty array has
|
|
1657
|
+
# already been joined into an empty string by this point.
|
|
1658
|
+
if MULTI_FILTER_KEYS.any? { |key| params[wire_name(key)] == "" }
|
|
1659
|
+
return nil
|
|
1660
|
+
end
|
|
1661
|
+
|
|
1662
|
+
params
|
|
1663
|
+
end
|
|
1664
|
+
|
|
1665
|
+
# Convert a budget strategy into the wire form — the period as
|
|
1666
|
+
# integer milliseconds under `duration_ms`, and the kind as a
|
|
1667
|
+
# string. A `while_in_flight` strategy carries neither field.
|
|
1668
|
+
#
|
|
1669
|
+
# Takes the merge-patch shape, which a whole `Zizq::budget_strategy`
|
|
1670
|
+
# also satisfies, so `create_budget` and `update_budget` share this.
|
|
1671
|
+
# Key presence is what the server reads either way: a field absent
|
|
1672
|
+
# from a patch is absent from the body and left alone, while an
|
|
1673
|
+
# explicit `burst: nil` is sent as JSON null and clears the burst.
|
|
1674
|
+
# That is why `burst` is tested with `key?` rather than for truth.
|
|
1675
|
+
#
|
|
1676
|
+
# @rbs strategy: Zizq::budget_strategy_patch
|
|
1677
|
+
# @rbs return: Hash[Symbol, untyped]
|
|
1678
|
+
def wire_strategy(strategy)
|
|
1679
|
+
wire = {} #: Hash[Symbol, untyped]
|
|
1680
|
+
wire[:type] = strategy[:type].to_s if strategy[:type]
|
|
1681
|
+
if (duration = strategy[:duration])
|
|
1682
|
+
wire[:duration_ms] = (duration.to_f * 1000).to_i
|
|
1683
|
+
end
|
|
1684
|
+
wire[:burst] = strategy[:burst] if strategy.key?(:burst)
|
|
1685
|
+
wire
|
|
1686
|
+
end
|
|
1687
|
+
|
|
1106
1688
|
# Build query params for list endpoints, joining multi-value keys with ",".
|
|
1689
|
+
# The query parameter name for a filter argument, which differs from
|
|
1690
|
+
# the Ruby name only where `API_FILTER_NAMES` says so.
|
|
1691
|
+
#
|
|
1692
|
+
# @rbs key: Symbol
|
|
1693
|
+
# @rbs return: Symbol
|
|
1694
|
+
def wire_name(key)
|
|
1695
|
+
API_FILTER_NAMES.fetch(key, key)
|
|
1696
|
+
end
|
|
1697
|
+
|
|
1107
1698
|
def build_where_params(options, multi_keys: []) #: (Hash[Symbol, untyped], ?multi_keys: Array[Symbol]) -> Hash[Symbol, untyped]
|
|
1108
1699
|
params = {} #: Hash[Symbol, untyped]
|
|
1109
1700
|
options.each do |key, value|
|
|
1110
|
-
if multi_keys.include?(key) &&
|
|
1111
|
-
|
|
1701
|
+
params[wire_name(key)] = if multi_keys.include?(key) &&
|
|
1702
|
+
value.is_a?(Array)
|
|
1703
|
+
value.join(",")
|
|
1112
1704
|
else
|
|
1113
|
-
|
|
1705
|
+
value
|
|
1114
1706
|
end
|
|
1115
1707
|
end
|
|
1116
1708
|
params
|
|
@@ -1118,22 +1710,32 @@ module Zizq
|
|
|
1118
1710
|
|
|
1119
1711
|
def encode_body(body) #: (Hash[Symbol, untyped]) -> String
|
|
1120
1712
|
case @format
|
|
1121
|
-
when :msgpack
|
|
1122
|
-
|
|
1123
|
-
|
|
1713
|
+
when :msgpack
|
|
1714
|
+
MessagePack.pack(body)
|
|
1715
|
+
when :json
|
|
1716
|
+
JSON.generate(body)
|
|
1717
|
+
else
|
|
1718
|
+
raise ArgumentError, "Unknown format: #{@format}"
|
|
1124
1719
|
end
|
|
1125
1720
|
end
|
|
1126
1721
|
|
|
1127
1722
|
def decode_body(data, content_type: nil) #: (String, ?content_type: String?) -> Hash[String, untyped]
|
|
1128
|
-
format =
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1723
|
+
format =
|
|
1724
|
+
case content_type
|
|
1725
|
+
when /msgpack/
|
|
1726
|
+
:msgpack
|
|
1727
|
+
when /json/
|
|
1728
|
+
:json
|
|
1729
|
+
else
|
|
1730
|
+
@format
|
|
1731
|
+
end
|
|
1133
1732
|
case format
|
|
1134
|
-
when :msgpack
|
|
1135
|
-
|
|
1136
|
-
|
|
1733
|
+
when :msgpack
|
|
1734
|
+
MessagePack.unpack(data)
|
|
1735
|
+
when :json
|
|
1736
|
+
JSON.parse(data)
|
|
1737
|
+
else
|
|
1738
|
+
raise ArgumentError, "Unknown format: #{format}"
|
|
1137
1739
|
end
|
|
1138
1740
|
end
|
|
1139
1741
|
|
|
@@ -1153,7 +1755,11 @@ module Zizq
|
|
|
1153
1755
|
# Read the response body and close it, returning a RawResponse that is
|
|
1154
1756
|
# safe to use outside the reactor.
|
|
1155
1757
|
def consume_response(response) #: (untyped) -> RawResponse
|
|
1156
|
-
RawResponse.new(
|
|
1758
|
+
RawResponse.new(
|
|
1759
|
+
status: response.status,
|
|
1760
|
+
body: response.read,
|
|
1761
|
+
content_type: response.headers["content-type"]
|
|
1762
|
+
)
|
|
1157
1763
|
ensure
|
|
1158
1764
|
response.close
|
|
1159
1765
|
end
|
|
@@ -1239,24 +1845,22 @@ module Zizq
|
|
|
1239
1845
|
end
|
|
1240
1846
|
|
|
1241
1847
|
def thread_local_http(key, endpoint) #: (Symbol, Async::HTTP::Endpoint) -> Async::HTTP::Client
|
|
1242
|
-
Thread.current.thread_variable_get(key) ||
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
@
|
|
1246
|
-
|
|
1848
|
+
Thread.current.thread_variable_get(key) ||
|
|
1849
|
+
begin
|
|
1850
|
+
client = Async::HTTP::Client.new(endpoint)
|
|
1851
|
+
@mutex.synchronize do
|
|
1852
|
+
@http_clients.reject! { |ref| !ref.weakref_alive? }
|
|
1853
|
+
@http_clients << WeakRef.new(client)
|
|
1854
|
+
end
|
|
1855
|
+
Thread.current.thread_variable_set(key, client)
|
|
1856
|
+
client
|
|
1247
1857
|
end
|
|
1248
|
-
Thread.current.thread_variable_set(key, client)
|
|
1249
|
-
client
|
|
1250
|
-
end
|
|
1251
1858
|
end
|
|
1252
1859
|
|
|
1253
1860
|
def get(path, params: {}) #: (String, ?params: Hash[Symbol, untyped]) -> RawResponse
|
|
1254
1861
|
request do |http|
|
|
1255
1862
|
consume_response(
|
|
1256
|
-
http.get(
|
|
1257
|
-
build_path(path, params:),
|
|
1258
|
-
{"accept" => @content_type}
|
|
1259
|
-
)
|
|
1863
|
+
http.get(build_path(path, params:), { "accept" => @content_type })
|
|
1260
1864
|
)
|
|
1261
1865
|
end
|
|
1262
1866
|
end
|
|
@@ -1266,7 +1870,7 @@ module Zizq
|
|
|
1266
1870
|
consume_response(
|
|
1267
1871
|
http.post(
|
|
1268
1872
|
build_path(path),
|
|
1269
|
-
{"content-type" => @content_type, "accept" => @content_type},
|
|
1873
|
+
{ "content-type" => @content_type, "accept" => @content_type },
|
|
1270
1874
|
Protocol::HTTP::Body::Buffered.wrap(encode_body(body))
|
|
1271
1875
|
)
|
|
1272
1876
|
)
|
|
@@ -1276,10 +1880,7 @@ module Zizq
|
|
|
1276
1880
|
def raw_post(path) #: (String) -> RawResponse
|
|
1277
1881
|
request do |http|
|
|
1278
1882
|
consume_response(
|
|
1279
|
-
http.post(
|
|
1280
|
-
build_path(path),
|
|
1281
|
-
{"accept" => @content_type}
|
|
1282
|
-
)
|
|
1883
|
+
http.post(build_path(path), { "accept" => @content_type })
|
|
1283
1884
|
)
|
|
1284
1885
|
end
|
|
1285
1886
|
end
|
|
@@ -1289,7 +1890,7 @@ module Zizq
|
|
|
1289
1890
|
consume_response(
|
|
1290
1891
|
http.put(
|
|
1291
1892
|
build_path(path),
|
|
1292
|
-
{"content-type" => @content_type, "accept" => @content_type},
|
|
1893
|
+
{ "content-type" => @content_type, "accept" => @content_type },
|
|
1293
1894
|
Protocol::HTTP::Body::Buffered.wrap(encode_body(body))
|
|
1294
1895
|
)
|
|
1295
1896
|
)
|
|
@@ -1299,10 +1900,7 @@ module Zizq
|
|
|
1299
1900
|
def delete(path, params: {}) #: (String, ?params: Hash[Symbol, untyped]) -> RawResponse
|
|
1300
1901
|
request do |http|
|
|
1301
1902
|
consume_response(
|
|
1302
|
-
http.delete(
|
|
1303
|
-
build_path(path, params:),
|
|
1304
|
-
{"accept" => @content_type}
|
|
1305
|
-
)
|
|
1903
|
+
http.delete(build_path(path, params:), { "accept" => @content_type })
|
|
1306
1904
|
)
|
|
1307
1905
|
end
|
|
1308
1906
|
end
|
|
@@ -1312,7 +1910,7 @@ module Zizq
|
|
|
1312
1910
|
consume_response(
|
|
1313
1911
|
http.patch(
|
|
1314
1912
|
build_path(path, params:),
|
|
1315
|
-
{"content-type" => @content_type, "accept" => @content_type},
|
|
1913
|
+
{ "content-type" => @content_type, "accept" => @content_type },
|
|
1316
1914
|
Protocol::HTTP::Body::Buffered.wrap(encode_body(body))
|
|
1317
1915
|
)
|
|
1318
1916
|
)
|
|
@@ -1330,18 +1928,26 @@ module Zizq
|
|
|
1330
1928
|
return nil if status == 204
|
|
1331
1929
|
decode_body(response.body, content_type: ct)
|
|
1332
1930
|
else
|
|
1333
|
-
body =
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1931
|
+
body =
|
|
1932
|
+
begin
|
|
1933
|
+
decode_body(response.body, content_type: ct)
|
|
1934
|
+
rescue StandardError
|
|
1935
|
+
nil
|
|
1936
|
+
end
|
|
1338
1937
|
message = body&.fetch("error", nil) || "HTTP #{status}"
|
|
1339
|
-
error_class =
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1938
|
+
error_class =
|
|
1939
|
+
case status
|
|
1940
|
+
when 404
|
|
1941
|
+
NotFoundError
|
|
1942
|
+
when 409
|
|
1943
|
+
ConflictError
|
|
1944
|
+
when 400..499
|
|
1945
|
+
ClientError
|
|
1946
|
+
when 500..599
|
|
1947
|
+
ServerError
|
|
1948
|
+
else
|
|
1949
|
+
ResponseError
|
|
1950
|
+
end
|
|
1345
1951
|
raise error_class.new(message, status: status, body: body)
|
|
1346
1952
|
end
|
|
1347
1953
|
end
|