Error Handling Mechanisms for Exports in the Instagit Design.md Codebase
The google-labs-code/design.md repository implements a strict, type-safe strategy for export error handling through Zod-based enums, discriminated result objects, and custom I/O error classes that eliminate uncaught exceptions.
The google-labs-code/design.md project—also referred to as Instagit—provides a CLI and programmatic API for linting and parsing design system documentation. Understanding the error handling mechanisms for exports is critical when integrating with its public API surface, as every exported handler guarantees explicit error propagation through structured result types rather than traditional exception throwing.
Typed Error Enums for Parser and Model Validation
The codebase defines closed sets of error identifiers using Zod schemas. In packages/cli/src/linter/parser/spec.ts, the ParserErrorCode enum provides identifiers such as EMPTY_CONTENT and NO_YAML_FOUND. Similarly, packages/cli/src/linter/model/spec.ts exports ModelErrorCode for model-level failures.
Each validation failure returns a structured error object containing { code, message, recoverable }. This design allows consumers to perform exhaustive switch statements on error.code without fragile string-matching. When Zod schemas like ParserInputSchema detect malformed payloads, the resulting ZodError is caught internally and transformed into these typed enum instances.
Result Objects Replace Thrown Exceptions
Every exported handler in the repository follows a strict contract: never throw, always return a result. The concrete implementations—ParserHandler in packages/cli/src/linter/parser/handler.ts and ModelHandler in packages/cli/src/linter/model/handler.ts—return discriminated unions shaped as { success: true, data: ... } or { success: false, error: ... }.
This pattern appears in ParserResult, ModelResult, and LintResult (defined in packages/cli/src/linter/lint.ts). By returning a success boolean flag alongside either data or error details, the API forces callers to handle failure paths explicitly, eliminating unexpected runtime exceptions.
Custom Error Classes for File System Operations
For I/O boundaries, the codebase exports FileReadError from packages/cli/src/utils.ts. When the readInput function encounters file system issues, it instantiates this class with the original filePath and underlying cause, exposing a friendlyMessage property for user-facing diagnostics (e.g., "file not found" or "permission denied").
Unlike the validation errors that populate result objects, FileReadError is thrown immediately to signal unrecoverable infrastructure failures. Consumers should catch this specific class to distinguish between parsing logic errors and external system failures.
Graceful Fallbacks in CLI Utilities
The utility functions in packages/cli/src/utils.ts implement defensive programming to prevent CLI crashes. Functions like formatOutput, serializeDesignSystem, and diffMaps never throw exceptions; instead, they return deterministic default values (empty strings, empty arrays, or null diffs) when processing malformed or incomplete data. This ensures the CLI remains operational even when individual exports encounter unexpected inputs.
Practical Implementation Examples
The following examples demonstrate how to consume the exported API while respecting its error handling contracts.
Consuming a Parser Export Safely
import { ParserSpec, ParserInput } from '@instagit/linter/parser';
import { ParserResult } from '@instagit/linter/parser/spec';
async function runParser(file: string) {
const input: ParserInput = { content: await readFile(file) };
const parser: ParserSpec = new ParserHandler();
const result: ParserResult = parser.execute(input);
if (!result.success) {
switch (result.error.code) {
case 'EMPTY CONTENT':
console.error('The file is empty.');
break;
case 'YAML PARSE ERROR':
console.error('YAML could not be parsed:', result.error.message);
break;
default:
console.error('Unexpected parser error:', result.error);
}
return;
}
console.log('Parsed design system:', result.data);
}
Source: Implementation follows ParserResult definitions in packages/cli/src/linter/parser/spec.ts.
Handling File-Read Errors
import { readInput, FileReadError } from '@instagit/utils';
async function loadDesign(path: string) {
try {
const raw = await readInput(path);
// Process raw DESIGN.md content
} catch (err) {
if (err instanceof FileReadError) {
console.error(err.friendlyMessage);
} else {
console.error('Unexpected error:', err);
}
}
}
Source: FileReadError class and readInput implementation in packages/cli/src/utils.ts.
Using the Linter Result Object
import { lintDesignSystem } from '@instagit/cli';
import type { LintResult } from '@instagit/linter';
async function lintFile(file: string) {
const result: LintResult = await lintDesignSystem(file);
if (!result.success) {
console.error('Linting failed:', result.error);
return;
}
console.log('Lint summary:', result.summary);
result.findings.forEach(f => {
console.log(`[${f.severity}] ${f.path ?? ''}: ${f.message}`);
});
}
Source: LintResult interface defined in packages/cli/src/linter/lint.ts.
Summary
- Typed error enums (
ParserErrorCode,ModelErrorCode) inspec.tsfiles provide exhaustive, type-safe error classification. - Result objects (
ParserResult,ModelResult,LintResult) replace exceptions with explicit{ success, data/error }shapes exported by handler implementations. - Custom I/O errors (
FileReadErrorinutils.ts) capture infrastructure failures with contextual metadata and user-friendly messages. - Graceful fallbacks in utilities like
formatOutputanddiffMapsensure the CLI never crashes on malformed inputs.
Frequently Asked Questions
What happens when Zod validation fails in the exported handlers?
When an input fails Zod schema validation (such as ParserInputSchema), the handler catches the resulting ZodError and transforms it into a structured error object containing a specific ParserErrorCode or ModelErrorCode, then returns it inside a { success: false, error: ... } result object. This prevents validation failures from propagating as uncaught exceptions.
How does FileReadError differ from ParserErrorCode errors?
FileReadError is a custom class exported from packages/cli/src/utils.ts that is thrown to signal unrecoverable file system issues like missing files or permission errors. In contrast, ParserErrorCode errors are returned within result objects to represent logical validation failures inside the parser or model handlers.
Why does the design.md API avoid throwing exceptions?
The API avoids throwing exceptions to ensure predictable control flow and type safety. By returning discriminated result unions from handlers like ParserHandler.execute, the codebase forces consumers to explicitly check result.success before accessing data, eliminating runtime surprises and aligning with TypeScript's type narrowing capabilities.
Which utilities guarantee non-crashing behavior even with malformed inputs?
The CLI utility functions formatOutput, serializeDesignSystem, and diffMaps in packages/cli/src/utils.ts are designed to never throw. They return deterministic fallback values—such as empty strings or empty diffs—when receiving malformed data, ensuring the exported CLI surface remains stable regardless of input quality.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →