t8y2/dbx · info

checked above

Error message

checked above

What it means

Panic from `Option::expect` in validate_item_keys after `item.contains_key(&key.name)` returned true; the subsequent item.get(&key.name) is therefore guaranteed Some. The expect documents that get must succeed right after contains_key on the same map. It can only panic if the map is mutated between the two calls or the code is restructured without preserving the guard, or if the borrowed key/name disagree (same &str source, so not realistic).

Source

Thrown at crates/dbx-core/src/db/dynamodb_driver.rs:1198

        .await
        .map_err(|error| dynamodb_sdk_error!("Failed to delete DynamoDB item", error))?;
    Ok(1)
}

fn json_document_to_item(doc_json: &str) -> Result<HashMap<String, AttributeValue>, String> {
    let mut value: Value =
        serde_json::from_str(doc_json).map_err(|error| format!("Invalid DynamoDB item JSON: {error}"))?;
    let object = value.as_object_mut().ok_or_else(|| "DynamoDB item must be a JSON object".to_string())?;
    object.remove("_id");
    object.iter().map(|(key, value)| Ok((key.clone(), json_to_attribute_value(value)?))).collect()
}

fn validate_item_keys(table: &DynamoDbTableDescription, item: &HashMap<String, AttributeValue>) -> Result<(), String> {
    for key in [Some(&table.partition_key), table.sort_key.as_ref()].into_iter().flatten() {
        if !item.contains_key(&key.name) {
            return Err(format!("DynamoDB item requires key attribute: {}", key.name));
        }
        let value = item.get(&key.name).expect("checked above");
        let actual_type = match value {
            AttributeValue::S(_) => "S",
            AttributeValue::N(_) => "N",
            AttributeValue::B(_) => "B",
            _ => "non-scalar",
        };
        if actual_type != key.attribute_type {
            return Err(format!(
                "DynamoDB key attribute {} must use type {} (received {actual_type})",
                key.name, key.attribute_type
            ));
        }
    }
    Ok(())
}

fn item_key(
    table: &DynamoDbTableDescription,

View on GitHub (pinned to c0390bff16)

Solutions

  1. Keep contains_key immediately before get on the same unshared map
  2. Collapse to entry()/get + match to eliminate the expect: match item.get(&key.name) { Some(v) => ..., None => return Err(...) }
  3. Use item.get(&key.name).ok_or_else(|| ...)? to merge the check and retrieval
  4. Document the adjacency invariant with a comment or test

Example fix

// before
if !item.contains_key(&key.name) { return Err(...); }
let value = item.get(&key.name).expect("checked above");
// after
let Some(value) = item.get(&key.name) else {
    return Err(format!("DynamoDB item requires key attribute: {}", key.name));
};
Defensive patterns

Strategy: validation

Validate before calling

for key_name in required_key_names(table) {
    if !item.contains_key(key_name) {
        return Err(format!("item missing key attribute: {key_name}"));
    }
}

Type guard

fn has_all_key_attributes(desc: &DynamoDbTableDescription, item: &HashMap<String, AttributeValue>) -> bool {
    [Some(&desc.partition_key), desc.sort_key.as_ref()].into_iter().flatten()
        .all(|k| item.contains_key(&k.name))
}

Try / catch

match put_item_with_identity(client, table, &item).await {
    Ok(_) => (),
    Err(e) => eprintln!("item rejected: {e}"),
}

Prevention

When it happens

Trigger: Effectively unreachable as written; would fire only under concurrent mutation of the item map between contains_key and get, or if the contains_key guard is removed in a refactor.

Common situations: Code review refactors splitting the check and the get; concurrent writers to the item HashMap; not triggerable by normal API input.

Related errors


AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05). Data as JSON: /api/errors/2f0e39dacb49629a. Report an issue: GitHub.