Hone logo
Hone
Problems

Compile-Time Shape Validation with Conditional Types

Compile-time validation is a powerful feature of TypeScript that allows you to catch errors before runtime. This challenge focuses on creating a reusable utility type that validates the shape of an object at compile time, ensuring it conforms to a specified structure. This is incredibly useful for ensuring data integrity and preventing unexpected errors when working with complex data structures.

Problem Description

You need to create a TypeScript type called ValidateShape that takes two type parameters: T (the type to validate) and Shape (the expected shape). ValidateShape<T, Shape> should return T if T conforms to the shape Shape, and never otherwise. Conforming to the shape means that T must have all the properties defined in Shape, and the types of those properties must match.

Key Requirements:

  • Complete Coverage: The Shape must be a complete description of the expected properties. Missing properties in T should result in never.
  • Type Matching: The types of the properties in T must match the corresponding types in Shape.
  • Reusability: The solution should be a generic type that can be used with any object type and shape.
  • Compile-Time Error: The validation should fail at compile time, providing a clear error message if the shape doesn't match.

Expected Behavior:

If T matches Shape, ValidateShape<T, Shape> should resolve to T. If T does not match Shape, the TypeScript compiler should produce an error indicating the mismatch.

Edge Cases to Consider:

  • Optional Properties: The Shape type should not need to account for optional properties. The validation should be strict.
  • Index Signatures: The solution should not need to handle index signatures. Focus on named properties.
  • Nested Objects: The validation should work correctly with nested object properties.

Examples

Example 1:

type Shape = {
  name: string;
  age: number;
};

type User = {
  name: string;
  age: number;
};

type ValidatedUser = ValidateShape<User, Shape>; // ValidatedUser is User

type InvalidUser = {
  name: string;
  // age is missing
};

// type ValidatedInvalidUser = ValidateShape<InvalidUser, Shape>; // Compile-time error: Property 'age' is missing in type '{ name: string; }' but required in type 'Shape'.

Example 2:

type Shape = {
  address: {
    street: string;
    city: string;
  };
  isActive: boolean;
};

type Person = {
  address: {
    street: string;
    city: string;
  };
  isActive: boolean;
};

type ValidatedPerson = ValidateShape<Person, Shape>; // ValidatedPerson is Person

type IncorrectPerson = {
  address: {
    street: string;
    city: number; // Incorrect type
  };
  isActive: boolean;
};

// type ValidatedIncorrectPerson = ValidateShape<IncorrectPerson, Shape>; // Compile-time error: Type 'number' is not assignable to type 'string'.

Example 3:

type Shape = {
  id: number;
  details: {
    description: string;
    createdAt: Date;
  }
};

type Product = {
  id: number;
  details: {
    description: string;
    createdAt: Date;
  }
};

type ValidatedProduct = ValidateShape<Product, Shape>; // ValidatedProduct is Product

Constraints

  • The solution must be a TypeScript type definition.
  • The solution must use only standard TypeScript features (no external libraries).
  • The solution must be generic and reusable.
  • The validation must occur at compile time.
  • The solution should be reasonably efficient (avoid unnecessary complexity).

Notes

Consider using conditional types and mapped types to achieve the desired validation. Think about how to iterate over the properties of the Shape and check if they exist in T with the correct types. The key is to leverage TypeScript's type system to enforce the shape at compile time.

Loading editor...
typescript