Type-Driven Configuration and Validation Framework
This challenge asks you to build a foundational framework in TypeScript that leverages TypeScript's type system to define, validate, and apply configurations. The goal is to create a system where the structure of your configuration is dictated by a TypeScript type, ensuring type safety and reducing runtime errors related to misconfigured settings. This is a core concept in type-driven development, promoting robust and maintainable applications.
Problem Description
You are tasked with creating a framework that allows developers to define configurations using TypeScript types, validate those configurations against the defined types, and then access the validated configuration data. The framework should consist of the following components:
ConfigSchemaType: A generic type that represents the schema of your configuration. This type will be a TypeScript type (e.g., an interface or type alias).ConfigValidatorClass: A class responsible for validating a configuration object against theConfigSchema. It should throw an error if the configuration doesn't conform to the schema.ConfigClass: A class that encapsulates the validated configuration data. It should accept a validated configuration object in its constructor and provide methods for accessing the configuration values in a type-safe manner. TheConfigclass should prevent modification of the configuration after instantiation.
Key Requirements:
- Type Safety: The framework must leverage TypeScript's type system to ensure that the configuration data conforms to the defined schema.
- Validation: The
ConfigValidatormust thoroughly validate the configuration object against the schema, checking for missing properties, incorrect types, and potentially other constraints (though this challenge focuses primarily on type validation). - Type-Safe Access: The
Configclass must provide type-safe access to the configuration values. - Immutability: The
Configclass should ensure that the configuration data is immutable after instantiation.
Expected Behavior:
- When a configuration object is validated against a schema, the
ConfigValidatorshould throw an error if the object doesn't match the schema. - When a validated configuration object is passed to the
Configconstructor, aConfiginstance should be created with type-safe access to the configuration values. - Attempting to modify the configuration data within a
Configinstance should result in an error or no effect (depending on your implementation of immutability).
Edge Cases to Consider:
- What happens if the configuration object is
nullorundefined? - How should the framework handle optional properties in the schema?
- How should the framework handle nested objects and arrays within the schema? (Focus on basic object validation for this challenge; array validation is optional).
Examples
Example 1:
interface MyConfig {
apiKey: string;
port: number;
debug: boolean;
}
const configValidator = new ConfigValidator<MyConfig>();
const validConfig = {
apiKey: "your-api-key",
port: 8080,
debug: true,
};
const invalidConfig = {
apiKey: 123, // Incorrect type
port: "8080", // Incorrect type
debug: "yes", // Incorrect type
};
const config = configValidator.validate(validConfig, MyConfig);
console.log(config.getApiKey()); // Output: "your-api-key"
console.log(config.getPort()); // Output: 8080
console.log(config.getDebug()); // Output: true
try {
configValidator.validate(invalidConfig, MyConfig);
} catch (error) {
console.error(error.message); // Output: Configuration validation failed.
}
Example 2:
interface DatabaseConfig {
host: string;
username: string;
password?: string; // Optional property
}
const dbConfigValidator = new ConfigValidator<DatabaseConfig>();
const dbConfig1 = {
host: "localhost",
username: "user",
};
const dbConfig2 = {
host: "localhost",
username: "user",
password: "secret",
};
const dbConfig = dbConfigValidator.validate(dbConfig1, DatabaseConfig);
console.log(dbConfig.getHost()); // Output: "localhost"
console.log(dbConfig.getUsername()); // Output: "user"
console.log(dbConfig.getPassword()); // Output: undefined (because it's optional and not provided)
const dbConfigWithPassword = dbConfigValidator.validate(dbConfig2, DatabaseConfig);
console.log(dbConfigWithPassword.getPassword()); // Output: "secret"
Constraints
- TypeScript Version: Use TypeScript 4.0 or higher.
- No External Libraries: Do not use external validation libraries (e.g., Joi, Zod). The focus is on leveraging TypeScript's type system.
- Basic Object Validation: Focus on validating the types of properties within a simple object. Array validation is optional.
- Performance: While performance is not the primary concern, avoid unnecessarily complex or inefficient code. Reasonable performance is expected.
- Error Handling: Provide clear and informative error messages when validation fails.
Notes
- Consider using generics to make the framework reusable with different configuration schemas.
- Think about how to handle optional properties in the schema.
- The
ConfigValidatorcan useinoperator and type assertions to validate the configuration object. - Immutability can be achieved by creating a new object with the desired changes instead of modifying the original object. Alternatively, you can use
Object.freeze()to make the configuration object immutable. - This is a foundational framework. You can extend it with more advanced features like schema composition, custom validation rules, and array validation in future iterations.