v0.3.0
Breaking change. Idempotency-key conflicts on enqueue now return a dedicated, typed error variant instead of the stringly-typed Error::InvalidArgument("idempotency_key conflict: …"):
Error::IdempotencyConflict { job_type: String, conflicting_keys: Vec<String>, total: usize }Match the variant (not the message text) to tell a benign duplicate apart from a real failure:
match storage.push_task_with_conn(conn, task) {
Ok(id) => { /* enqueued */ }
Err(Error::IdempotencyConflict { .. }) => { /* duplicate — swallow it */ }
Err(other) => return Err(other),
}For batch enqueues, conflicting_keys lists exactly which idempotency_keys collided, so you can drop them and re-enqueue the rest.
Behavior is unchanged: a single duplicate still rolls back the whole batch via SAVEPOINT (one duplicate undoes every row in the batch, not just the colliding one) while a surrounding transaction stays alive. Every other Error::InvalidArgument case (queue-name / metadata / idempotency-key length caps, unreachable run_at) is unchanged.
Error is #[non_exhaustive], so future variants won't be a breaking change for downstreams that already match with a wildcard arm.