Skip to content

Feature: Built-in Validation and Constraints #65

@Goldziher

Description

@Goldziher

Feature: Built-in Validation and Constraints

Add a comprehensive constraint and validation system that goes beyond what Faker.js provides, ensuring generated data meets specific business rules and requirements.

Problem Statement

While Faker provides basic data generation, real-world applications often need:

  • Unique constraints across generated datasets
  • Complex validation rules
  • Custom patterns and formats
  • Retry logic when constraints aren't satisfied
  • Better error messages when generation fails

Proposed Features

1. Constraint System API

import { Factory, constraints } from 'interface-forge';

const UserFactory = new Factory<User>((faker) => ({
  id: constraints.unique(faker.string.uuid()),
  email: constraints.unique(faker.internet.email()),
  age: constraints.between(18, 65, faker.number.int()),
  username: constraints.pattern(/^[a-z][a-z0-9_]{3,15}$/, faker.internet.userName()),
  phone: constraints.matches(phoneValidator, faker.phone.number()),
  salary: constraints.custom((value) => value > 30000, faker.number.int({ min: 30000, max: 200000 }))
}));

2. Built-in Constraints

Uniqueness Constraints

// Global uniqueness
email: constraints.unique(faker.internet.email())

// Scoped uniqueness
email: constraints.unique(faker.internet.email(), { scope: 'tenant' })

// Composite uniqueness
constraints.uniqueTogether(['firstName', 'lastName'], {
  firstName: faker.person.firstName(),
  lastName: faker.person.lastName()
})

Numeric Constraints

age: constraints.between(18, 65, faker.number.int())
price: constraints.min(0, faker.number.float())
discount: constraints.max(100, faker.number.int())
quantity: constraints.positive(faker.number.int())

String Constraints

username: constraints.pattern(/^[a-z][a-z0-9_]{3,15}$/, faker.internet.userName())
email: constraints.format('email', faker.internet.email())
url: constraints.format('url', faker.internet.url())
code: constraints.length(6, faker.string.alphanumeric())

Custom Validators

// Inline validation
ssn: constraints.custom(
  (value) => isValidSSN(value),
  faker.string.numeric({ length: 9 })
)

// With error messages
email: constraints.custom(
  (value) => value.includes('@'),
  faker.internet.email(),
  'Email must contain @'
)

// Async validation
username: constraints.asyncCustom(
  async (value) => await checkUsernameAvailable(value),
  faker.internet.userName()
)

3. Validation Integration

With Popular Libraries

// Joi integration
const schema = Joi.object({
  email: Joi.string().email().required(),
  age: Joi.number().min(18).max(65)
});

const factory = Factory.fromJoi(schema);

// Yup integration
const yupSchema = yup.object({
  email: yup.string().email().required(),
  age: yup.number().min(18).max(65)
});

const factory = Factory.fromYup(yupSchema);

// Zod (enhance existing integration)
const zodSchema = z.object({
  email: z.string().email(),
  age: z.number().min(18).max(65)
});

const factory = new ZodFactory(zodSchema, {
  validateOutput: true // Ensure all generated data passes validation
});

4. Retry Logic and Error Handling

const factory = new Factory<User>(/* ... */, {
  validation: {
    maxRetries: 100,
    retryStrategy: 'exponential', // or 'linear'
    onValidationError: (error, attempts) => {
      console.warn(`Validation failed after ${attempts} attempts:`, error);
    }
  }
});

// Detailed error reporting
try {
  const user = factory.build();
} catch (error) {
  if (error instanceof ValidationError) {
    console.error('Validation failures:', error.failures);
    // [{
    //   field: 'email',
    //   constraint: 'unique',
    //   attempts: 100,
    //   lastValue: 'john@example.com'
    // }]
  }
}

5. Constraint Composition

// Combine multiple constraints
const ageConstraint = constraints.compose(
  constraints.between(18, 65),
  constraints.custom((age) => age \!== 33) // Superstitious constraint
);

// Conditional constraints
const salaryConstraint = constraints.conditional(
  (user) => user.role === 'manager',
  constraints.min(80000),
  constraints.between(40000, 70000)
);

Implementation Details

  1. Uniqueness Tracking

    • In-memory set for development
    • Redis/database adapter for production
    • Automatic cleanup strategies
  2. Performance Optimization

    • Lazy constraint evaluation
    • Batch validation for better performance
    • Smart retry strategies
  3. Type Safety

    • Full TypeScript support
    • Infer constraints from types
    • Compile-time validation where possible

Example Use Cases

// E-commerce product catalog
const ProductFactory = new Factory<Product>((faker) => ({
  sku: constraints.unique(
    constraints.pattern(/^PROD-[0-9]{6}$/, () => `PROD-${faker.string.numeric(6)}`)
  ),
  price: constraints.min(0.01, faker.number.float({ precision: 0.01 })),
  stock: constraints.min(0, faker.number.int()),
  discount: constraints.between(0, 100, faker.number.int())
}));

// User registration system
const UserFactory = new Factory<User>((faker) => ({
  email: constraints.compose(
    constraints.unique(),
    constraints.format('email'),
    constraints.lowercase()
  )(faker.internet.email()),
  
  password: constraints.compose(
    constraints.minLength(8),
    constraints.pattern(/[A-Z]/, 'Must contain uppercase'),
    constraints.pattern(/[0-9]/, 'Must contain number'),
    constraints.pattern(/[^A-Za-z0-9]/, 'Must contain special character')
  )(faker.internet.password({ length: 12 }))
}));

Testing Requirements

  • Unit tests for each constraint type
  • Integration tests with validation libraries
  • Performance tests for retry logic
  • Memory tests for uniqueness tracking
  • Edge case handling (impossible constraints)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions