-
Notifications
You must be signed in to change notification settings - Fork 9
Open
Labels
enhancementNew feature or requestNew feature or requestgood first issueGood for newcomersGood for newcomershelp wantedExtra attention is neededExtra attention is needed
Description
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
-
Uniqueness Tracking
- In-memory set for development
- Redis/database adapter for production
- Automatic cleanup strategies
-
Performance Optimization
- Lazy constraint evaluation
- Batch validation for better performance
- Smart retry strategies
-
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
Labels
enhancementNew feature or requestNew feature or requestgood first issueGood for newcomersGood for newcomershelp wantedExtra attention is neededExtra attention is needed