Hone logo
Hone
Problems

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:

  1. ConfigSchema Type: A generic type that represents the schema of your configuration. This type will be a TypeScript type (e.g., an interface or type alias).
  2. ConfigValidator Class: A class responsible for validating a configuration object against the ConfigSchema. It should throw an error if the configuration doesn't conform to the schema.
  3. Config Class: 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. The Config class 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 ConfigValidator must 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 Config class must provide type-safe access to the configuration values.
  • Immutability: The Config class should ensure that the configuration data is immutable after instantiation.

Expected Behavior:

  • When a configuration object is validated against a schema, the ConfigValidator should throw an error if the object doesn't match the schema.
  • When a validated configuration object is passed to the Config constructor, a Config instance should be created with type-safe access to the configuration values.
  • Attempting to modify the configuration data within a Config instance 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 null or undefined?
  • 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 ConfigValidator can use in operator 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.
Loading editor...
typescript