Compile-Time Configuration Validation with TypeScript
This challenge focuses on leveraging TypeScript's type system to enforce the validity of configuration objects at compile time. You will create a system that ensures a configuration object adheres to a predefined schema, preventing common runtime errors related to missing or incorrectly typed properties. This is crucial for building robust applications where configuration consistency is paramount.
Problem Description
Your task is to implement a compile-time validation mechanism for configuration objects in TypeScript. This system should:
- Define a Schema: Create a type or interface that represents the expected structure and types of a valid configuration object.
- Implement a Validation Function: Develop a function that takes an object and a schema as input. This function should only perform validation at compile time, meaning it should not add any runtime overhead for the validation itself. The TypeScript compiler should be the one to detect errors.
- Enforce Type Safety: Ensure that any object passed to or defined within the scope of this validation is checked against the schema before the code can be compiled.
- Handle Nested Structures: The validation should gracefully handle nested configuration objects.
- Support Optional Properties: Allow for configuration properties that are optional.
The ultimate goal is to have the TypeScript compiler report errors if a configuration object deviates from the defined schema, such as missing required properties, having properties of the wrong type, or including unexpected properties (if configured to do so).
Examples
Example 1: Basic Validation
// Define the configuration schema
type AppConfigSchema = {
appName: string;
port: number;
debugMode: boolean;
};
// Implement a way to validate configuration at compile time
// (The implementation details are the core of the challenge)
// --- Valid Configuration ---
const validConfig: AppConfigSchema = {
appName: "My Awesome App",
port: 3000,
debugMode: true,
};
// --- Invalid Configuration (compile-time error expected) ---
// const invalidConfigMissingPort: AppConfigSchema = {
// appName: "Another App",
// debugMode: false,
// };
// Expected Error: Property 'port' is missing in type '{ appName: string; debugMode: boolean; }' but required in type 'AppConfigSchema'.
// const invalidConfigWrongType: AppConfigSchema = {
// appName: "Yet Another App",
// port: "8080", // Should be a number
// debugMode: true,
// };
// Expected Error: Type 'string' is not assignable to type 'number'.
Example 2: Nested Configuration
// Define a nested schema
type DatabaseConfigSchema = {
host: string;
port: number;
credentials?: {
username: string;
password?: string; // Optional password
};
};
type FullConfigSchema = {
server: {
host: string;
port: number;
};
database: DatabaseConfigSchema;
};
// Assume validation mechanism is in place
// --- Valid Nested Configuration ---
const validNestedConfig: FullConfigSchema = {
server: {
host: "localhost",
port: 8080,
},
database: {
host: "db.example.com",
port: 5432,
credentials: {
username: "admin",
password: "securepassword123",
},
},
};
// --- Invalid Nested Configuration (compile-time error expected) ---
// const invalidNestedConfig: FullConfigSchema = {
// server: {
// host: "localhost",
// // port: 8080, // Missing port
// },
// database: {
// host: "db.example.com",
// port: 5432,
// credentials: {
// username: "user",
// // password: 12345, // Wrong type for password
// },
// },
// };
// Expected Errors: Property 'port' is missing... and Type 'number' is not assignable to type 'string | undefined'.
Constraints
- The validation must be performed at compile time. No runtime checks should be added to the validation logic itself.
- The solution should be implemented entirely in TypeScript.
- The primary mechanism for validation should be TypeScript's type system.
- Avoid using external libraries specifically designed for runtime validation (e.g., Zod, Yup, Joi) for the core validation mechanism. You may use them for inspiration or comparison in your thought process, but the final solution should be a native TypeScript approach.
Notes
- Consider how TypeScript's built-in features like mapped types, conditional types, and template literal types can be used to achieve compile-time validation.
- Think about how you can create a function or a type that accepts a configuration object and returns a value strongly typed to the schema, thereby leveraging the compiler's checking capabilities.
- The goal is not to create a runtime validator that throws errors at runtime, but rather a system where the compiler itself flags incorrect configurations before runtime.
- You might explore creating a helper function that, when used to assign a configuration object, forces the assignment to conform to a specific type. The return type of this helper will be key.