call(back)
Algorithms & Data StructuresmediumSign in to star problems

Implement a Robust REST Create Method

Implement the handler behind POST /orders. It must be safe to retry: a client that times out and sends the same request again must not create a second order. Answer in code — OrdersApi(tokens) with createOrder(request) returning { status, body }, where request is { headers, body }.

tokens maps a bearer token to { userId, scopes }. Header names are case-insensitive. Apply the checks in this order; the first failure wins:

  1. Authenticate — Authorization: Bearer <token> with a known token, else 401 { error: "unauthorized" }.
  2. Authorise — the caller needs the orders:write scope, else 403 { error: "forbidden" }.
  3. Idempotency key — an Idempotency-Key header of 1–64 characters after trimming, else 400 { error: "invalid_idempotency_key" }.
  4. Body shape — the body must be a JSON object, else 400 { error: "invalid_body" }.
  5. Validate and sanitise — else 422 { error: "validation_failed", fields: [...] } naming every offending field, sorted:
    • sku: a string that, once trimmed, is 1–40 characters of letters, digits, _ or -;
    • quantity: an integer from 1 to 100 (a numeric string or a boolean is invalid);
    • note: optional; a string of at most 200 characters once control characters (codes below 32, and 127) are removed and it is trimmed; an empty or missing note is stored as null;
    • any other field is rejected by name.
  6. Idempotency — keys are scoped to the authenticated user. A key seen before with the same sanitised request returns the original response again and creates nothing. A key seen before with a different request is 409 { error: "idempotency_key_reused" }. A request that failed validation does not consume its key.
  7. Create — ids are ord_1, ord_2, … in creation order; respond 201 { id, sku, quantity, note, createdBy } with the sanitised values.

count() returns how many orders exist.

createOrder({ headers: { Authorization: "Bearer tok_ann", "Idempotency-Key": "k1" }, body: { sku: " ABC-1 ", quantity: 2 } })
  ->  { status: 201, body: { id: "ord_1", sku: "ABC-1", quantity: 2, note: null, createdBy: "ann" } }
the same call again  ->  the same response; count() is still 1

Hints

Hint 1

Write the pipeline as a sequence of early returns in the stated order. Look headers up case-insensitively once, at the top, so the rest of the code never thinks about casing.

Hint 2

Validate into a sanitised copy and collect the names of bad fields as you go, instead of returning on the first one. Everything after validation — the fingerprint, the stored order — uses the sanitised values.

Hint 3

The idempotency store maps (userId, key) to a fingerprint of the sanitised request plus the response you sent. Same fingerprint: return the stored response. Different fingerprint: 409. Only write to it when an order is actually created.

Asked at