zizq 0.6.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 +82 -11
- data/lib/active_job/queue_adapters/zizq_adapter.rb +4 -23
- data/lib/zizq/bulk_enqueue.rb +61 -14
- data/lib/zizq/client.rb +580 -77
- data/lib/zizq/crontab.rb +36 -3
- data/lib/zizq/crontab_builder.rb +10 -1
- data/lib/zizq/crontab_entry_builder.rb +66 -14
- data/lib/zizq/enqueue_request.rb +22 -21
- data/lib/zizq/enqueue_with.rb +65 -8
- data/lib/zizq/error.rb +10 -0
- data/lib/zizq/job_config.rb +95 -12
- data/lib/zizq/query.rb +111 -39
- data/lib/zizq/resources/budget.rb +109 -0
- data/lib/zizq/resources/cron_group.rb +4 -0
- data/lib/zizq/resources/job.rb +108 -2
- data/lib/zizq/resources/job_template.rb +15 -0
- data/lib/zizq/resources.rb +1 -0
- data/lib/zizq/test/client.rb +6 -3
- data/lib/zizq/version.rb +1 -1
- data/lib/zizq.rb +191 -14
- data/sig/generated/zizq/bulk_enqueue.rbs +4 -16
- data/sig/generated/zizq/client.rbs +377 -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 -17
- data/sig/generated/zizq/enqueue_request.rbs +11 -4
- data/sig/generated/zizq/enqueue_with.rbs +4 -9
- data/sig/generated/zizq/error.rbs +10 -0
- data/sig/generated/zizq/job_config.rbs +65 -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 +94 -0
- data/sig/generated/zizq/resources/job_template.rbs +11 -0
- data/sig/generated/zizq.rbs +111 -33
- data/sig/zizq.rbs +100 -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",
|
|
@@ -152,6 +177,9 @@ module Zizq
|
|
|
152
177
|
# using the Zizq API's expected inputs. Callers should generally use
|
|
153
178
|
# [`Zizq::enqueue`] instead.
|
|
154
179
|
#
|
|
180
|
+
# Durations and timestamps are fractional seconds throughout to align with
|
|
181
|
+
# Ruby, as opposed to the server's integer milliseconds representation.
|
|
182
|
+
#
|
|
155
183
|
# Returns a resource instance of the new job wrapping the API response.
|
|
156
184
|
#
|
|
157
185
|
# @rbs queue: String
|
|
@@ -165,6 +193,7 @@ module Zizq
|
|
|
165
193
|
# @rbs unique_key: String?
|
|
166
194
|
# @rbs unique_while: Zizq::unique_scope?
|
|
167
195
|
# @rbs batch: Zizq::batch?
|
|
196
|
+
# @rbs budgets: Array[Zizq::budget_binding_params]?
|
|
168
197
|
# @rbs return: Resources::Job
|
|
169
198
|
def enqueue(
|
|
170
199
|
queue:,
|
|
@@ -177,18 +206,20 @@ module Zizq
|
|
|
177
206
|
retention: nil,
|
|
178
207
|
unique_key: nil,
|
|
179
208
|
unique_while: nil,
|
|
180
|
-
batch: nil
|
|
209
|
+
batch: nil,
|
|
210
|
+
budgets: nil
|
|
181
211
|
)
|
|
182
212
|
body = { queue:, type:, payload: } #: Hash[Symbol, untyped]
|
|
183
213
|
body[:priority] = priority if priority
|
|
184
214
|
# ready_at is fractional seconds in Ruby; the server expects ms.
|
|
185
215
|
body[:ready_at] = (ready_at.to_f * 1000).to_i if ready_at
|
|
186
216
|
body[:retry_limit] = retry_limit if retry_limit
|
|
187
|
-
body[:backoff] = backoff if backoff
|
|
188
|
-
body[:retention] = retention if retention
|
|
217
|
+
body[:backoff] = wire_backoff(backoff) if backoff
|
|
218
|
+
body[:retention] = wire_retention(retention) if retention
|
|
189
219
|
body[:unique_key] = unique_key if unique_key
|
|
190
220
|
body[:unique_while] = unique_while.to_s if unique_while
|
|
191
221
|
body[:batch] = batch if batch
|
|
222
|
+
body[:budgets] = budgets.map { |b| wire_binding(b) } if budgets&.any?
|
|
192
223
|
|
|
193
224
|
response = post("/jobs", body)
|
|
194
225
|
data = handle_response!(response, expected: [200, 201])
|
|
@@ -220,11 +251,16 @@ module Zizq
|
|
|
220
251
|
:ready_at
|
|
221
252
|
]
|
|
222
253
|
wire[:retry_limit] = job[:retry_limit] if job[:retry_limit]
|
|
223
|
-
wire[:backoff] = job[:backoff] if job[:backoff]
|
|
224
|
-
wire[:retention] = job[:retention] if job[
|
|
254
|
+
wire[:backoff] = wire_backoff(job[:backoff]) if job[:backoff]
|
|
255
|
+
wire[:retention] = wire_retention(job[:retention]) if job[
|
|
256
|
+
:retention
|
|
257
|
+
]
|
|
225
258
|
wire[:unique_key] = job[:unique_key] if job[:unique_key]
|
|
226
259
|
wire[:unique_while] = job[:unique_while].to_s if job[:unique_while]
|
|
227
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
|
|
228
264
|
wire
|
|
229
265
|
end
|
|
230
266
|
}
|
|
@@ -273,34 +309,30 @@ module Zizq
|
|
|
273
309
|
priority: nil,
|
|
274
310
|
ready_at: nil,
|
|
275
311
|
attempts: nil,
|
|
312
|
+
budgets_key: nil,
|
|
276
313
|
from: nil,
|
|
277
314
|
order: nil,
|
|
278
315
|
limit: nil
|
|
279
316
|
)
|
|
280
|
-
|
|
317
|
+
where = {
|
|
281
318
|
id:,
|
|
282
319
|
status:,
|
|
283
320
|
queue:,
|
|
284
321
|
type:,
|
|
285
322
|
filter:,
|
|
286
|
-
priority
|
|
287
|
-
ready_at
|
|
288
|
-
attempts
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
params = build_where_params(options, multi_keys:)
|
|
296
|
-
|
|
297
|
-
# An empty filter ([] or "") matches nothing — short-circuit.
|
|
298
|
-
multi_keys.each do |key|
|
|
299
|
-
if params[key] == ""
|
|
300
|
-
return Resources::JobPage.new(self, { "jobs" => [], "pages" => {} })
|
|
301
|
-
end
|
|
323
|
+
priority:,
|
|
324
|
+
ready_at:,
|
|
325
|
+
attempts:,
|
|
326
|
+
budgets_key:
|
|
327
|
+
}.compact #: Zizq::where_params
|
|
328
|
+
params = filter_params(where)
|
|
329
|
+
|
|
330
|
+
if params.nil?
|
|
331
|
+
return Resources::JobPage.new(self, { "jobs" => [], "pages" => {} })
|
|
302
332
|
end
|
|
303
333
|
|
|
334
|
+
params = params.merge({ from:, order:, limit: }.compact)
|
|
335
|
+
|
|
304
336
|
response = get("/jobs", params:)
|
|
305
337
|
data = handle_response!(response, expected: 200)
|
|
306
338
|
Resources::JobPage.new(self, data)
|
|
@@ -328,24 +360,22 @@ module Zizq
|
|
|
328
360
|
filter: nil,
|
|
329
361
|
priority: nil,
|
|
330
362
|
ready_at: nil,
|
|
331
|
-
attempts: nil
|
|
363
|
+
attempts: nil,
|
|
364
|
+
budgets_key: nil
|
|
332
365
|
)
|
|
333
|
-
|
|
366
|
+
where = {
|
|
334
367
|
id:,
|
|
335
368
|
status:,
|
|
336
369
|
queue:,
|
|
337
370
|
type:,
|
|
338
371
|
filter:,
|
|
339
|
-
priority
|
|
340
|
-
ready_at
|
|
341
|
-
attempts
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
# An empty filter ([] or "") matches nothing — short-circuit.
|
|
348
|
-
multi_keys.each { |key| return 0 if params[key] == "" }
|
|
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?
|
|
349
379
|
|
|
350
380
|
response = get("/jobs/count", params:)
|
|
351
381
|
data = handle_response!(response, expected: 200)
|
|
@@ -372,13 +402,8 @@ module Zizq
|
|
|
372
402
|
# @rbs where: Zizq::where_params
|
|
373
403
|
# @rbs return: Integer
|
|
374
404
|
def delete_all_jobs(where: {})
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
multi_keys = %i[id status queue type]
|
|
378
|
-
params = build_where_params(filter_params, multi_keys:)
|
|
379
|
-
|
|
380
|
-
# An empty multi-value filter matches nothing — short-circuit.
|
|
381
|
-
multi_keys.each { |key| return 0 if params[key] == "" }
|
|
405
|
+
params = filter_params(where)
|
|
406
|
+
return 0 if params.nil?
|
|
382
407
|
|
|
383
408
|
response = delete("/jobs", params:)
|
|
384
409
|
data = handle_response!(response, expected: 200)
|
|
@@ -458,13 +483,8 @@ module Zizq
|
|
|
458
483
|
# @rbs apply: Zizq::apply_params
|
|
459
484
|
# @rbs return: Integer
|
|
460
485
|
def update_all_jobs(where: {}, apply: {})
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
multi_keys = %i[id status queue type]
|
|
464
|
-
params = build_where_params(filter_params, multi_keys:)
|
|
465
|
-
|
|
466
|
-
# An empty multi-value filter matches nothing — short-circuit.
|
|
467
|
-
multi_keys.each { |key| return 0 if params[key] == "" }
|
|
486
|
+
params = filter_params(where)
|
|
487
|
+
return 0 if params.nil?
|
|
468
488
|
|
|
469
489
|
body = validate_and_build_set(**apply)
|
|
470
490
|
response = patch("/jobs", body, params:)
|
|
@@ -517,6 +537,336 @@ module Zizq
|
|
|
517
537
|
data["queues"]
|
|
518
538
|
end
|
|
519
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
|
+
|
|
520
870
|
# List all cron group names.
|
|
521
871
|
#
|
|
522
872
|
# @rbs return: Array[String]
|
|
@@ -541,13 +891,19 @@ module Zizq
|
|
|
541
891
|
# Entries not present in the request are removed. Entries with unchanged
|
|
542
892
|
# expressions preserve their scheduling state.
|
|
543
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
|
+
#
|
|
544
898
|
# @rbs name: String
|
|
545
899
|
# @rbs paused: bool?
|
|
900
|
+
# @rbs timezone: String?
|
|
546
901
|
# @rbs entries: Array[Zizq::cron_entry_params]
|
|
547
902
|
# @rbs return: Resources::CronGroup
|
|
548
|
-
def replace_cron_group(name, paused: nil, entries: [])
|
|
903
|
+
def replace_cron_group(name, paused: nil, timezone: nil, entries: [])
|
|
549
904
|
body = {
|
|
550
905
|
paused:,
|
|
906
|
+
timezone:,
|
|
551
907
|
entries: entries.map { |entry| build_cron_entry(**entry) }
|
|
552
908
|
}.compact
|
|
553
909
|
response = put("/crons/#{enc(name)}", body)
|
|
@@ -606,7 +962,7 @@ module Zizq
|
|
|
606
962
|
|
|
607
963
|
# Add a single entry to a cron group (creates the group if needed).
|
|
608
964
|
#
|
|
609
|
-
# Raises a
|
|
965
|
+
# Raises a `Zizq::ConflictError` (409) if an entry with the same name
|
|
610
966
|
# already exists.
|
|
611
967
|
#
|
|
612
968
|
# @rbs group: String
|
|
@@ -997,6 +1353,8 @@ module Zizq
|
|
|
997
1353
|
# @rbs retention: Zizq::retention?
|
|
998
1354
|
# @rbs unique_key: String?
|
|
999
1355
|
# @rbs unique_while: Zizq::unique_scope?
|
|
1356
|
+
# @rbs batch: Zizq::batch?
|
|
1357
|
+
# @rbs budgets: Array[Zizq::budget_binding_params]?
|
|
1000
1358
|
# @rbs return: Hash[Symbol, untyped]
|
|
1001
1359
|
def build_cron_job(
|
|
1002
1360
|
type: nil,
|
|
@@ -1007,15 +1365,19 @@ module Zizq
|
|
|
1007
1365
|
backoff: nil,
|
|
1008
1366
|
retention: nil,
|
|
1009
1367
|
unique_key: nil,
|
|
1010
|
-
unique_while: nil
|
|
1368
|
+
unique_while: nil,
|
|
1369
|
+
batch: nil,
|
|
1370
|
+
budgets: nil
|
|
1011
1371
|
)
|
|
1012
1372
|
job = { type:, queue:, payload: } #: Hash[Symbol, untyped]
|
|
1013
1373
|
job[:priority] = priority if priority
|
|
1014
1374
|
job[:retry_limit] = retry_limit if retry_limit
|
|
1015
|
-
job[:backoff] = backoff if backoff
|
|
1016
|
-
job[:retention] = retention if retention
|
|
1375
|
+
job[:backoff] = wire_backoff(backoff) if backoff
|
|
1376
|
+
job[:retention] = wire_retention(retention) if retention
|
|
1017
1377
|
job[:unique_key] = unique_key if unique_key
|
|
1018
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?
|
|
1019
1381
|
job
|
|
1020
1382
|
end
|
|
1021
1383
|
|
|
@@ -1031,6 +1393,7 @@ module Zizq
|
|
|
1031
1393
|
# @rbs priority: (Integer | Range[Integer?])?
|
|
1032
1394
|
# @rbs ready_at: (Zizq::to_f | Range[Zizq::to_f?])?
|
|
1033
1395
|
# @rbs attempts: (Integer | Range[Integer?])?
|
|
1396
|
+
# @rbs budgets_key: (String | Array[String])?
|
|
1034
1397
|
# @rbs return: Hash[Symbol, untyped]
|
|
1035
1398
|
def validate_where(
|
|
1036
1399
|
id: nil,
|
|
@@ -1040,7 +1403,8 @@ module Zizq
|
|
|
1040
1403
|
filter: nil,
|
|
1041
1404
|
priority: nil,
|
|
1042
1405
|
ready_at: nil,
|
|
1043
|
-
attempts: nil
|
|
1406
|
+
attempts: nil,
|
|
1407
|
+
budgets_key: nil
|
|
1044
1408
|
)
|
|
1045
1409
|
{
|
|
1046
1410
|
id:,
|
|
@@ -1050,7 +1414,8 @@ module Zizq
|
|
|
1050
1414
|
filter:,
|
|
1051
1415
|
priority: encode_range(priority),
|
|
1052
1416
|
ready_at: encode_range(ready_at) { |v| (v.to_f * 1000).to_i },
|
|
1053
|
-
attempts: encode_range(attempts)
|
|
1417
|
+
attempts: encode_range(attempts),
|
|
1418
|
+
budgets_key:
|
|
1054
1419
|
}.compact
|
|
1055
1420
|
end
|
|
1056
1421
|
|
|
@@ -1165,43 +1530,179 @@ module Zizq
|
|
|
1165
1530
|
end
|
|
1166
1531
|
|
|
1167
1532
|
unless backoff.equal?(UNCHANGED)
|
|
1168
|
-
body[:backoff] =
|
|
1169
|
-
nil
|
|
1170
|
-
else
|
|
1171
|
-
{
|
|
1172
|
-
exponent: backoff[:exponent].to_f,
|
|
1173
|
-
base_ms: (backoff[:base].to_f * 1000).to_i,
|
|
1174
|
-
jitter_ms: (backoff[:jitter].to_f * 1000).to_i
|
|
1175
|
-
}
|
|
1176
|
-
end
|
|
1533
|
+
body[:backoff] = backoff.equal?(RESET) ? nil : wire_backoff(backoff)
|
|
1177
1534
|
end
|
|
1178
1535
|
|
|
1179
1536
|
unless retention.equal?(UNCHANGED)
|
|
1180
1537
|
body[:retention] = if retention.equal?(RESET)
|
|
1181
1538
|
nil
|
|
1182
1539
|
else
|
|
1183
|
-
|
|
1184
|
-
ret[:completed_ms] = (
|
|
1185
|
-
retention[:completed].to_f * 1000
|
|
1186
|
-
).to_i if retention[:completed]
|
|
1187
|
-
ret[:dead_ms] = (retention[:dead].to_f * 1000).to_i if retention[
|
|
1188
|
-
:dead
|
|
1189
|
-
]
|
|
1190
|
-
ret
|
|
1540
|
+
wire_retention(retention)
|
|
1191
1541
|
end
|
|
1192
1542
|
end
|
|
1193
1543
|
|
|
1194
1544
|
body
|
|
1195
1545
|
end
|
|
1196
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
|
+
|
|
1197
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
|
+
|
|
1198
1698
|
def build_where_params(options, multi_keys: []) #: (Hash[Symbol, untyped], ?multi_keys: Array[Symbol]) -> Hash[Symbol, untyped]
|
|
1199
1699
|
params = {} #: Hash[Symbol, untyped]
|
|
1200
1700
|
options.each do |key, value|
|
|
1201
|
-
if multi_keys.include?(key) &&
|
|
1202
|
-
|
|
1701
|
+
params[wire_name(key)] = if multi_keys.include?(key) &&
|
|
1702
|
+
value.is_a?(Array)
|
|
1703
|
+
value.join(",")
|
|
1203
1704
|
else
|
|
1204
|
-
|
|
1705
|
+
value
|
|
1205
1706
|
end
|
|
1206
1707
|
end
|
|
1207
1708
|
params
|
|
@@ -1438,6 +1939,8 @@ module Zizq
|
|
|
1438
1939
|
case status
|
|
1439
1940
|
when 404
|
|
1440
1941
|
NotFoundError
|
|
1942
|
+
when 409
|
|
1943
|
+
ConflictError
|
|
1441
1944
|
when 400..499
|
|
1442
1945
|
ClientError
|
|
1443
1946
|
when 500..599
|