Hone logo
Hone
Problems

Compile-Time Configuration Object Validation

This challenge focuses on leveraging TypeScript's type system to enforce the structure and validity of a configuration object at compile time. The goal is to create a system where any deviations from the expected configuration schema result in immediate compilation errors, preventing runtime issues and improving developer confidence.

Problem Description

You are tasked with creating a robust compile-time validation mechanism for a configuration object in TypeScript. This validation should ensure that:

  1. Required Properties: All mandatory properties are present.
  2. Optional Properties: Optional properties are either present with the correct type or absent.
  3. Type Correctness: Each property adheres to its specified type (e.g., string, number, boolean, specific union types, or nested objects).
  4. Enum Constraints: Properties that are meant to be one of a predefined set of values (enum-like) are strictly enforced.
  5. Conditional Validation (Advanced): If a property has a specific value, other related properties might become mandatory or have specific type requirements.

The validation should be entirely handled by TypeScript's type inference and checking, meaning that any invalid configuration should cause a TypeScript compilation error. You will need to define a type or a set of types that represent your configuration schema and then create a function that accepts a configuration object and "validates" it against this schema using these types.

Examples

Example 1: Basic Validation

Let's imagine a simple configuration for a web server.

// Target configuration schema
interface ServerConfig {
  port: number;
  host: string;
  enableHttps: boolean;
}

// A valid configuration object
const validServerConfig: ServerConfig = {
  port: 8080,
  host: "localhost",
  enableHttps: false,
};

// An invalid configuration object (missing 'host')
/*
const invalidServerConfigMissingHost: ServerConfig = {
  port: 3000,
  enableHttps: true,
};
// This should produce a TypeScript error:
// Property 'host' is missing in type '{ port: number; enableHttps: boolean; }' but required in type 'ServerConfig'.
*/

// An invalid configuration object (wrong type for 'port')
/*
const invalidServerConfigWrongType: ServerConfig = {
  port: "8000", // Should be a number
  host: "example.com",
  enableHttps: false,
};
// This should produce a TypeScript error:
// Type 'string' is not assignable to type 'number'.
*/

Example 2: Nested Objects and Enums

Consider a configuration for a database connection.

// Target configuration schema
type DatabaseType = "postgres" | "mysql" | "mongodb";

interface DbConnectionConfig {
  type: DatabaseType;
  host: string;
  port: number;
  credentials?: { // Optional nested object
    username: string;
    password?: string; // Optional property within a nested object
  };
}

// A valid configuration object
const validDbConfig: DbConnectionConfig = {
  type: "postgres",
  host: "db.example.com",
  port: 5432,
  credentials: {
    username: "admin",
  },
};

// An invalid configuration object (invalid enum value)
/*
const invalidDbConfigEnum: DbConnectionConfig = {
  type: "sqlite", // Not a valid DatabaseType
  host: "localhost",
  port: 3306,
};
// This should produce a TypeScript error:
// Type '"sqlite"' is not assignable to type 'DatabaseType'.
*/

// An invalid configuration object (missing mandatory property in nested object)
/*
const invalidDbConfigNestedMissing: DbConnectionConfig = {
  type: "mysql",
  host: "mysql.example.com",
  port: 3306,
  credentials: {
    // Missing 'username' which is required when 'credentials' is present
  },
};
// This should produce a TypeScript error (depending on how you structure it,
// but conceptually the requirement is that if 'credentials' exists, 'username' must).
*/

Example 3: Conditional Validation (Advanced Scenario)

Imagine a feature flag configuration where enabling a specific feature requires additional parameters.

// Target configuration schema
interface FeatureFlags {
  enableNewDashboard: boolean;
  // If enableNewDashboard is true, analyticsConfig becomes mandatory and must have a specific structure
  analyticsConfig?: {
    provider: "google" | "mixpanel";
    trackingId: string;
  };
}

// A valid configuration object
const validFeatureFlags: FeatureFlags = {
  enableNewDashboard: false,
};

// Another valid configuration object
const validFeatureFlagsWithAnalytics: FeatureFlags = {
  enableNewDashboard: true,
  analyticsConfig: {
    provider: "google",
    trackingId: "UA-12345-6",
  },
};

// An invalid configuration object (enableNewDashboard is true, but analyticsConfig is missing)
/*
const invalidFeatureFlagsMissingAnalytics: FeatureFlags = {
  enableNewDashboard: true,
};
// This should produce a TypeScript error:
// An object literal whose properties do not entirely satisfy the inferred type.
// Property 'analyticsConfig' is missing in type '{ enableNewDashboard: true; }' but required in type '{ enableNewDashboard: true; analyticsConfig: { provider: "google" | "mixpanel"; trackingId: string; }; }'.
*/

// An invalid configuration object (analyticsConfig is present but missing trackingId)
/*
const invalidFeatureFlagsAnalyticsMissingId: FeatureFlags = {
  enableNewDashboard: true,
  analyticsConfig: {
    provider: "mixpanel",
    // trackingId is missing
  },
};
// This should produce a TypeScript error:
// Property 'trackingId' is missing in type '{ provider: "mixpanel"; }' but required in type '{ provider: "google" | "mixpanel"; trackingId: string; }'.
*/

Constraints

  • Language: TypeScript
  • Validation Method: Entirely through TypeScript's static type checking (no runtime checks).
  • Output: The solution should be presented as a set of TypeScript types and potentially a generic helper function or utility type that enforces these rules.
  • No Runtime Logic: Avoid any JavaScript code that performs validation at runtime (e.g., if statements checking property existence or types). The goal is to shift all validation to compile time.
  • Reusability: The defined schema and validation mechanism should be reusable for different configuration objects.

Notes

  • This challenge is about understanding and applying advanced TypeScript features like mapped types, conditional types, and template literal types if necessary.
  • Think about how to model the "required if" or "optional but structured" relationships using TypeScript's type system.
  • Consider how to create a flexible system that can be extended to more complex validation scenarios.
  • The goal is to make the act of writing the configuration object trigger the errors, not to have a function that returns an error at runtime. You might use a generic utility type that infers the configuration and subtly enforces constraints.
Loading editor...
typescript