Skip to content

Tier 3: TransactGetItems action-level missing key returns 500 InternalServerError instead of TransactionCanceledException #19

@hicksy

Description

@hicksy

Summary

TransactGetItems with a missing partition key in an action's Key returns HTTP 500 InternalServerError("Missing partition key") instead of TransactionCanceledException carrying a ValidationError cancellation reason. Two layered bugs:

  1. The action-level validation surfaces as InternalServerError (server fault) rather than a 4xx validation error. The duplicate-target dedup loop calls extract_key_strings (which throws InternalServerError on missing PK) before validate_key_only (which would throw the proper ValidationException) gets a chance to run.
  2. Even with the right exception class, real DynamoDB wraps per-action validation failures inside TransactionCanceledException with a positional CancellationReasons array. The reason Code is ValidationError, not ConditionalCheckFailed. Dynoxide's transact-get path doesn't have this wrapping at all.

Version: dynoxide 0.9.9

Test: tests/tier3/error-messages/transactGetItems.test.ts "missing key attribute: action-level ValidationError surfaces as TransactionCanceledException"

Expected:

  • Exception class: TransactionCanceledException
  • Message: Transaction cancelled, please refer cancellation reasons for specific reasons [ValidationError]
  • CancellationReasons[].Code: ['ValidationError']

Actual:

  • HTTP 500
  • Exception class: InternalServerError
  • Message: Missing partition key

Reproduction

npx dynoxide@0.9.9 --port 8001 &
node -e '
import("@aws-sdk/client-dynamodb").then(async ({ DynamoDBClient, CreateTableCommand, TransactGetItemsCommand }) => {
  const c = new DynamoDBClient({ endpoint: "http://localhost:8001", region: "us-east-1", credentials: { accessKeyId: "f", secretAccessKey: "f" } });
  const t = "t" + Date.now();
  await c.send(new CreateTableCommand({ TableName: t, AttributeDefinitions: [{ AttributeName: "pk", AttributeType: "S" }], KeySchema: [{ AttributeName: "pk", KeyType: "HASH" }], BillingMode: "PAY_PER_REQUEST" }));
  try { await c.send(new TransactGetItemsCommand({ TransactItems: [{ Get: { TableName: t, Key: {} } }] })); }
  catch (e) { console.log(e.name, e.$response?.statusCode, e.message); }
});
'

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingv0.9.10

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions