Update a protobuf message using CAS (compare-and-swap). Fetches the current object, validates the expected version, applies the mutation function, and attempts a single CAS write. Returns Conflict on version mismatch for caller-driven retry. # Arguments `id` - Object ID to update `expected_version` - Required resource version for the update to proceed. Pass 0 to use the current version (internal
(
&self,
id: &str,
expected_version: u64,
mut mutate: F,
)
| 438 | /// * `Err(Conflict)` - Version mismatch; caller should retry |
| 439 | /// * `Err(Database)` - Object not found or other DB error |
| 440 | pub async fn update_message_cas<T, F>( |
| 441 | &self, |
| 442 | id: &str, |
| 443 | expected_version: u64, |
| 444 | mut mutate: F, |
| 445 | ) -> PersistenceResult<T> |
| 446 | where |
| 447 | T: Message |
| 448 | + Default |
| 449 | + ObjectType |
| 450 | + ObjectId |
| 451 | + ObjectName |
| 452 | + ObjectLabels |
| 453 | + SetResourceVersion |
| 454 | + GetResourceVersion |
| 455 | + Clone, |
| 456 | F: FnMut(&mut T), |
| 457 | { |
| 458 | // Fetch current object with authoritative resource_version |
| 459 | let current = self |
| 460 | .get_message::<T>(id) |
| 461 | .await? |
| 462 | .ok_or_else(|| PersistenceError::Database(format!("object {id} not found")))?; |
| 463 | |
| 464 | let current_version = current.get_resource_version(); |
| 465 | |
| 466 | // Determine the version to use for CAS: |
| 467 | // - If expected_version is 0, use current version (internal operations) |
| 468 | // - Otherwise, validate that expected matches current (client-facing operations) |
| 469 | let cas_version = if expected_version == 0 { |
| 470 | current_version |
| 471 | } else { |
| 472 | if expected_version != current_version { |
| 473 | return Err(PersistenceError::Conflict { |
| 474 | current_resource_version: Some(current_version), |
| 475 | }); |
| 476 | } |
| 477 | expected_version |
| 478 | }; |
| 479 | |
| 480 | // Apply mutation |
| 481 | let mut updated = current.clone(); |
| 482 | mutate(&mut updated); |
| 483 | |
| 484 | // Serialize labels |
| 485 | let labels_map = updated.object_labels(); |
| 486 | let labels_json = if labels_map.as_ref().is_none_or(HashMap::is_empty) { |
| 487 | None |
| 488 | } else { |
| 489 | Some(serde_json::to_string(&labels_map).map_err(|e| { |
| 490 | PersistenceError::Encode(format!("failed to serialize labels: {e}")) |
| 491 | })?) |
| 492 | }; |
| 493 | |
| 494 | // Single-attempt CAS write - fails with Conflict on version mismatch |
| 495 | let result = self |
| 496 | .put_if( |
| 497 | T::object_type(), |
nothing calls this directly
no test coverage detected