Hone logo
Hone
Problems

Implementing a Flexible Contract Type System in TypeScript

Imagine building a system where different types of contracts (e.g., employment, service, lease) need to be managed, each with its own unique set of properties and validation rules. This challenge focuses on creating a robust and type-safe system in TypeScript to represent and manage these diverse contract types efficiently.

Problem Description

You are tasked with designing and implementing a TypeScript system to represent various contract types. This system should allow for:

  1. Defining Base Contract Properties: A common set of properties that all contracts share (e.g., id, startDate, endDate, status).
  2. Defining Specific Contract Types: Creating distinct "types" of contracts, each with its own set of additional, specific properties. For example, an EmploymentContract might have employeeName and position, while a LeaseContract might have propertyName and rentAmount.
  3. Type Safety and Validation: Ensuring that when you work with a specific contract type, you can only access its defined properties and that basic validation can be performed on these properties.
  4. Extensibility: The system should be designed to easily add new contract types in the future without significant refactoring.

Key Requirements:

  • Define a BaseContract interface or type that includes common fields.
  • Define at least two specific contract types (e.g., EmploymentContract, LeaseContract) that extend or compose the BaseContract.
  • Implement a mechanism to represent the "type" of a contract, allowing you to differentiate between them.
  • Provide a function or method that can take a contract object and, based on its type, access its specific properties in a type-safe manner.
  • Implement a simple validation function for at least one of the specific contract types.

Expected Behavior:

  • When creating an EmploymentContract, it should have all BaseContract properties plus its own specific properties.
  • When trying to access a property that doesn't exist on a specific contract type (e.g., trying to access propertyName on an EmploymentContract object directly), TypeScript should raise a compile-time error.
  • A function designed to process contracts should be able to correctly infer and use the specific properties of a contract based on its type.
  • The validation function should return true for valid contracts and false for invalid ones, flagging specific issues.

Edge Cases:

  • Handling contracts that might be "null" or "undefined" before they are fully constructed or retrieved.
  • Consider how to handle contracts where the type information might be missing or ambiguous. (For this challenge, assume the type is present and correct).

Examples

Example 1: Defining Contract Types

// Input (Conceptual - this is how you'd define the types)

interface BaseContract {
  id: string;
  startDate: Date;
  endDate: Date;
  status: 'active' | 'expired' | 'cancelled';
}

enum ContractType {
  Employment = 'employment',
  Lease = 'lease',
}

interface EmploymentContract extends BaseContract {
  type: ContractType.Employment;
  employeeName: string;
  position: string;
  salary: number;
}

interface LeaseContract extends BaseContract {
  type: ContractType.Lease;
  propertyName: string;
  tenantName: string;
  rentAmount: number;
  leaseTermMonths: number;
}

// A union type to represent any contract
type Contract = EmploymentContract | LeaseContract;

// --- Implementation Goal ---
// Create functions that can work with these types.

Example 2: Working with Contracts and Type Guards

// Input (A concrete contract object)
const myEmploymentContract: EmploymentContract = {
  id: 'emp-123',
  startDate: new Date('2023-01-01'),
  endDate: new Date('2024-12-31'),
  status: 'active',
  type: ContractType.Employment,
  employeeName: 'Alice Smith',
  position: 'Software Engineer',
  salary: 80000,
};

// Input (Another concrete contract object)
const myLeaseContract: LeaseContract = {
  id: 'lease-456',
  startDate: new Date('2023-05-01'),
  endDate: new Date('2024-04-30'),
  status: 'active',
  type: ContractType.Lease,
  propertyName: '123 Main St',
  tenantName: 'Bob Johnson',
  rentAmount: 1500,
  leaseTermMonths: 12,
};

// --- Implementation Goal ---
// A function that processes contracts and uses type guards.

function getContractDetails(contract: Contract): string {
  switch (contract.type) {
    case ContractType.Employment:
      // TypeScript knows this is an EmploymentContract here
      return `${contract.employeeName} is a ${contract.position} earning $${contract.salary}.`;
    case ContractType.Lease:
      // TypeScript knows this is a LeaseContract here
      return `Property at ${contract.propertyName} is leased by ${contract.tenantName} for $${contract.rentAmount}/month.`;
    default:
      // Handle unexpected contract types (though our union should prevent this)
      return `Unknown contract type: ${contract.type}`;
  }
}

// Expected Output for getContractDetails(myEmploymentContract):
// "Alice Smith is a Software Engineer earning $80000."

// Expected Output for getContractDetails(myLeaseContract):
// "Property at 123 Main St is leased by Bob Johnson for $1500/month."

Example 3: Validation

// Input (A valid Lease Contract)
const validLease: LeaseContract = {
  id: 'lease-789',
  startDate: new Date('2024-01-01'),
  endDate: new Date('2025-01-01'),
  status: 'active',
  type: ContractType.Lease,
  propertyName: '456 Oak Ave',
  tenantName: 'Charlie Brown',
  rentAmount: 2000,
  leaseTermMonths: 12,
};

// Input (An invalid Lease Contract - missing tenant name)
const invalidLease: Partial<LeaseContract> = { // Using Partial for illustration of missing fields
  id: 'lease-999',
  startDate: new Date('2024-01-01'),
  endDate: new Date('2025-01-01'),
  status: 'active',
  type: ContractType.Lease,
  propertyName: '789 Pine Ln',
  rentAmount: 1800,
  leaseTermMonths: 6,
};

// --- Implementation Goal ---
// A validation function for LeaseContracts.

function isValidLeaseContract(contract: LeaseContract): { isValid: boolean; errors: string[] } {
  const errors: string[] = [];
  if (!contract.tenantName || contract.tenantName.trim() === '') {
    errors.push('Tenant name is required.');
  }
  if (contract.rentAmount <= 0) {
    errors.push('Rent amount must be positive.');
  }
  if (contract.leaseTermMonths <= 0) {
    errors.push('Lease term must be positive.');
  }
  // Add more checks as needed...

  return {
    isValid: errors.length === 0,
    errors: errors,
  };
}

// Expected Output for isValidLeaseContract(validLease):
// { isValid: true, errors: [] }

// Expected Output for isValidLeaseContract(invalidLease as LeaseContract): // Type assertion for demonstration
// { isValid: false, errors: ["Tenant name is required."] }

Constraints

  • Your solution must be written entirely in TypeScript.
  • You must define at least two distinct contract types beyond the base contract.
  • You must use TypeScript's type system effectively for compile-time safety.
  • The solution should include at least one type guard function or a similar mechanism to differentiate contract types at runtime.
  • The solution should include at least one validation function for one of the specific contract types.
  • Code readability and maintainability are important.

Notes

  • Consider using discriminated unions (also known as tagged unions) in TypeScript, as they are a powerful pattern for this type of problem.
  • Think about how you might represent optional fields or different validation rules for different statuses of a contract.
  • The Partial utility type in TypeScript can be helpful for demonstrating cases where fields might be missing during development or for error conditions, but your final functions should expect valid types.
  • Focus on demonstrating the core concepts of defining and differentiating contract types with strong typing.
Loading editor...
typescript