typestar

validate-config.ts in TypeScript

Parse unknown input into a fully typed config or issues.

#!/usr/bin/env node
// Parse and validate a config object into a fully typed shape.

interface Config {
  host: string;
  port: number;
  debug: boolean;
  tags: string[];
}

type Issue = { field: keyof Config; message: string };
type Parsed = { ok: true; config: Config } | { ok: false; issues: Issue[] };

function parseConfig(raw: Record<string, unknown>): Parsed {
  const issues: Issue[] = [];

  const host = typeof raw.host === "string" ? raw.host : "";
  if (!host) issues.push({ field: "host", message: "must be a string" });

  const port = typeof raw.port === "number" ? raw.port : NaN;
  if (!Number.isInteger(port) || port < 1 || port > 65535) {
    issues.push({ field: "port", message: "must be 1-65535" });
  }

  const debug = raw.debug === true;
  const tags = Array.isArray(raw.tags)
    ? raw.tags.filter((t): t is string => typeof t === "string")
    : [];

  if (issues.length > 0) return { ok: false, issues };
  return { ok: true, config: { host, port, debug, tags } };
}

const result = parseConfig({ host: "localhost", port: 8080, tags: ["dev"] });

if (result.ok) {
  console.log(`serving ${result.config.host}:${result.config.port}`);
  console.log(`tags: ${result.config.tags.join(", ") || "none"}`);
} else {
  for (const issue of result.issues) {
    console.error(`${issue.field}: ${issue.message}`);
  }
  process.exitCode = 1;
}

How it works

  1. A discriminated Parsed union reports success or errors.
  2. Each field is checked and defaulted individually.
  3. A type predicate filters the tags array safely.

Keywords and builtins used here

The run, in numbers

Lines
44
Characters to type
1308
Tokens
370
Three-star pace
105 tpm

At the three-star pace of 105 tokens a minute, this run takes about 211 seconds.

Type this snippet

Step 1 of 1 in Encore, step 23 of 23 in Types & annotations.

← Previous