Bridge Training

Errors

Structured core errors, their factories, validation result shapes, the notation error thrown by the card and bid converters, and the PBN error classes.

Import from @workspace/bridge/errors. Core errors are plain objects discriminated by type, decoupled from translation keys. InvalidNotationError is the one class that carries such an object. PBN errors are classes extending DisplayableError from @workspace/shared/errors.

Base shape

ErrorSeverity is "error" | "warning" | "info". Every core error extends BaseError<TContext> and adds a literal type.

Prop

Type

Hands errors

Union HandsValidationError.

Typetype literalContext
IncompleteHandsErrorINCOMPLETE_HANDS{ incompletePlayers: Player[] }
TooManyCardsErrorTOO_MANY_CARDS_HAND{ tooManyPlayers: Player[] }
DuplicateCardErrorDUPLICATE_CARD{ card: string }
InvalidCardRankErrorINVALID_CARD_RANK{ player, suit, card, validRanks, line? }

Auction errors

Union AuctionValidationError.

Typetype literalContext
AuctionNotUsedErrorAUCTION_NOT_USED{ mode: string }
AuctionRequiredErrorAUCTION_REQUIRED{ mode: string }
IncompleteAuctionErrorINCOMPLETE_AUCTIONnone
NoContractErrorNO_CONTRACTnone
ContractCalculationErrorCONTRACT_CALCULATION_ERROR{ error: string }

Play errors

Union PlayValidationError. It includes InvalidCardRankError, shared with the hands union.

Typetype literalContext
CardNotOwnedErrorCARD_NOT_OWNED{ card, player, line }
RevokeErrorREVOKE{ card, player, line }
TooManyCardsInLineErrorTOO_MANY_CARDS_HAND_IN_LINE{ line }
InvalidPlayFormatErrorINVALID_PLAY_FORMAT{ line }
InvalidCardRankErrorINVALID_CARD_RANK{ player, suit, card, validRanks, line? }

General errors

Union GeneralValidationError.

Typetype literalContext
PayloadValidationErrorPAYLOAD_VALIDATION_ERROR{ message }
ZodValidationErrorZOD_VALIDATION_ERROR{ code, message }

CoreError is the union of all four groups. Narrow on error.type.

Notation errors

Union NotationValidationError, kept out of CoreError: these describe a value that is not a card or a bid at all, not a rule broken by a deal.

Typetype literalContext
InvalidCardErrorINVALID_CARD{ input: string }
InvalidBidErrorINVALID_BID{ input: string }

toCard and toBid throw them wrapped in InvalidNotationError, an Error whose detail field holds the plain object. The parse* functions never throw; reach for them when the input is untrusted.

errors.ts
const invalidCard = createNotationError.invalidCard("H1");
// => { type: "INVALID_CARD", severity: "error", context: { input: "H1" } }

function readCard(read: () => Card): Card | null {
  try {
    return read();
  } catch (error) {
    if (error instanceof InvalidNotationError) {
      error.detail.type; // "INVALID_CARD" | "INVALID_BID"
      return null;
    }
    throw error;
  }
}

Factories

Five namespaced objects build errors with the right severity, field default and path. All field parameters are optional and default to the group's field ("hands", "auction.moves", "payload").

FactoryMethodSignature
createHandsErrorincompleteHands(incompletePlayers: Player[], field?) => IncompleteHandsError
tooManyCards(tooManyPlayers: Player[], field?) => TooManyCardsError
duplicateCard(card: string, field?) => DuplicateCardError
invalidCardRank(player, suit, card: Rank, validRanks: Rank[], field?) => InvalidCardRankError
createAuctionErrorauctionNotUsed(mode: string, field?) => AuctionNotUsedError, severity warning
auctionRequired(mode: string, field?) => AuctionRequiredError
incompleteAuction(field?) => IncompleteAuctionError
noContract(field?) => NoContractError
contractCalculationError(error: string, field?) => ContractCalculationError
createPlayErrorcardNotOwned(card: string, player, line: number) => CardNotOwnedError
revoke(card: string, player, line: number) => RevokeError
tooManyCardsInLine(line: number) => TooManyCardsInLineError
invalidPlayFormat(line: number) => InvalidPlayFormatError
invalidCardRank(player, suit, card: Rank, validRanks: Rank[], line: number) => InvalidCardRankError
createGeneralErrorpayloadValidation(message: string, field?) => PayloadValidationError
zodValidation(code: string, message: string, field?, path?: (string | number)[]) => ZodValidationError
createNotationErrorinvalidCard(input: string) => InvalidCardError
invalidBid(input: string) => InvalidBidError
errors.ts
const incomplete = createHandsError.incompleteHands(["N", "S"]);
// => { type: "INCOMPLETE_HANDS", severity: "error", field: "hands", path: ["hands"], context: { incompletePlayers: ["N", "S"] } }
const notUsed = createAuctionError.auctionNotUsed("no-auction");
// severity: "warning"
const revoke = createPlayError.revoke("H2", "E", 3);
const zod = createGeneralError.zodValidation("invalid_type", "Expected string", undefined, ["metadata", "board"]);
// field: "metadata.board"

Validation results

CoreValidationResult

Prop

Type

CorePlayValidationResult

Prop

Type

errors.ts
const result: CoreValidationResult = {
  isValid: false,
  errors: [incomplete, revoke],
  warnings: [notUsed],
  infos: [],
};

function describe(error: CoreError): string {
  switch (error.type) {
    case "INCOMPLETE_HANDS":
      return `Incomplete: ${error.context?.incompletePlayers.join(", ")}`;
    case "REVOKE":
      return `Revoke by ${error.context?.player} at line ${error.context?.line}`;
    default:
      return error.type;
  }
}

PBN errors

Values and types

ExportDefinition
SUPPORTED_ENCODINGS["UTF-8", "ISO-8859-1", "windows-1252", "UTF-16LE", "UTF-16BE"]
SupportedEncodingUnion of the tuple above
PBNErrorCode"MAX_FILE_SIZE_EXCEEDED" | "ENCODING_MISMATCH" | "TOO_MANY_DEALS" | "PARSE_ERROR"
PBNErrorDetails{ type: "encoding-mismatch"; expected; detected } | { type: "too-many-deals"; max; actual } | { type: "parse-error"; message }

PBNError

Callback-friendly shape produced by toPBNError.

Prop

Type

Classes

ClassExtendsConstructorFields
PBNParseErrorDisplayableError(message, options?: { cause? })
PBNEncodingMismatchErrorPBNParseError(expectedEncoding: SupportedEncoding, detectedEncoding: string)expectedEncoding, detectedEncoding
PBNTooManyDealsErrorPBNParseError(maxDeals: number, dealCount: number)maxDeals, dealCount
PNBAggregateErrorDisplayableError(message, details: PBNErrorDetails, options?: { cause? })details
MaxFileSizeErrorDisplayableError(maxSize: number, actualSize: number)type, maxSize, actualSize

The aggregate class is spelled PNBAggregateError, with N before B. That is the exported name.

toPBNError

toPBNError(error: MaxFileSizeError | PNBAggregateError | PBNTooManyDealsError | PBNParseError | Error): PBNError

InstanceCode
MaxFileSizeErrorMAX_FILE_SIZE_EXCEEDED
PNBAggregateErrorMapped from details.type
PBNTooManyDealsErrorTOO_MANY_DEALS
Anything else, including PBNEncodingMismatchErrorPARSE_ERROR
errors.ts
const tooBig = new MaxFileSizeError(10, 12);
const mismatch = new PBNEncodingMismatchError("UTF-8", "windows-1252");
const aggregate = new PNBAggregateError("Encoding mismatch", {
  type: "encoding-mismatch",
  expected: "UTF-8",
  detected: "windows-1252",
});

const callbackErrors: PBNError[] = [tooBig, mismatch, aggregate].map(toPBNError);
// codes: "MAX_FILE_SIZE_EXCEEDED", "PARSE_ERROR", "ENCODING_MISMATCH"
SUPPORTED_ENCODINGS.includes("UTF-16LE"); // true