TypeDrop

2026-06-17 Challenge

2026-06-17 Easy

Typed Shopping Cart Price Calculator

You're building the checkout feature for an e-commerce storefront. Raw cart data arrives as `unknown` from a client-side POST request; your engine must validate it, apply typed discount rules, and return a strongly-typed order summary — with zero `any`.

Goals

  • Implement `validateCart` to narrow `unknown` → `Cart` using type guards, collecting every `ValidationError` before returning.
  • Implement `buildOrderSummary` to compute per-line totals, subtotal, and a correctly capped `discountApplied` amount using discriminated-union narrowing on `DiscountKind`.
  • Implement `processCart` to compose the two helpers and return a `Result<OrderSummary, ValidationError[]>`.
  • Use zero `any` and zero type assertions (`as`) throughout — rely solely on type guards and control flow narrowing.
challenge.ts

export type Currency = "USD" | "EUR" | "GBP";

export type DiscountKind =
  | { kind: "percentage"; percent: number }
  | { kind: "fixed";      amount: number };

export interface Cart {
  currency: Currency;
  items: CartItem[];
  discount?: DiscountKind;
}

export type Result<T, E> =
  | { ok: true;  value: T }
  | { ok: false; error: E };

export type ValidationError =
  | { field: "currency";          message: string }
  | { field: "items";             message: string }
  | { field: `items[${number}]`;  message: string }
  | { field: "discount";          message: string };

/** Validate raw unknown input → typed Cart, collecting ALL errors. */
export function validateCart(raw: unknown): Result<Cart, ValidationError[]>;

/** Compute line totals, subtotal, discount, and final total. */
export function buildOrderSummary(cart: Cart): OrderSummary;

/** Validate + calculate in one call. */
export function processCart(raw: unknown): Result<OrderSummary, ValidationError[]>;
Hints (click to reveal)

Hints

  • A `const CURRENCIES = ['USD','EUR','GBP'] as const` array lets you write a clean type-guard without repeating the union members.
  • Narrow `DiscountKind` in `buildOrderSummary` with a `switch (cart.discount.kind)` — TypeScript will fully narrow each branch for you.
  • For the template-literal error field `items[${number}]`, just interpolate the index: `` `items[${i}]` as const `` satisfies the union member exactly.

Or clone locally

git clone -b challenge/2026-06-17 https://github.com/niltonheck/typedrop.git