fortify-schema
Version:
TypeScript interface-like schema validation system that's easier to use than Zod
319 lines (285 loc) • 8.28 kB
text/typescript
/**
* Conditional Validation - Revolutionary dependent field validation
*
* This module provides powerful conditional validation where fields can depend
* on other fields' values, making complex business logic validation simple.
*/
import { SchemaInterface } from "../mode/interfaces/Interface";
/**
* Conditional validation utilities
*/
export const When = {
/**
* Create conditional validation based on another field's value
*
* @example
* ```typescript
* const UserSchema = Interface({
* accountType: Make.union("free", "premium", "enterprise"),
* maxProjects: When.field("accountType").is("free").then("int(1,3)")
* .is("premium").then("int(1,50)")
* .is("enterprise").then("int(1,)")
* .default("int(1,1)"),
*
* paymentMethod: When.field("accountType").isNot("free").then("string").else("string?"),
* billingAddress: When.field("paymentMethod").exists().then({
* street: "string",
* city: "string",
* country: "string(2,2)"
* }).else("any?")
* });
* ```
*/
field(fieldName: string): ConditionalBuilder {
return new ConditionalBuilder(fieldName);
},
/**
* Create validation that depends on multiple fields
*
* @example
* ```typescript
* const OrderSchema = Interface({
* orderType: Make.union("pickup", "delivery"),
* address: "string?",
* deliveryFee: "number?",
*
* // Complex conditional validation
* ...When.fields(["orderType", "address"]).match({
* orderType: "delivery",
* address: (val) => val && val.length > 0
* }).then({
* deliveryFee: "number(0,)" // Required for delivery with address
* }).else({
* deliveryFee: "number?" // Optional otherwise
* })
* });
* ```
*/
fields(fieldNames: string[]): MultiConditionalBuilder {
return new MultiConditionalBuilder(fieldNames);
},
/**
* Create custom validation logic
*
* @example
* ```typescript
* const EventSchema = Interface({
* startDate: "date",
* endDate: "date",
*
* // Custom validation: endDate must be after startDate
* ...When.custom((data) => {
* if (data.endDate <= data.startDate) {
* return { error: "End date must be after start date" };
* }
* return { valid: true };
* })
* });
* ```
*/
custom(validator: (data: any) => { valid: true } | { error: string }): CustomValidator {
return new CustomValidator(validator);
}
};
/**
* Builder for single field conditional validation
*/
class ConditionalBuilder {
private conditions: Array<{
condition: (value: any) => boolean;
schema: any;
}> = [];
private defaultSchema: any = null;
constructor(private fieldName: string) {}
/**
* Check if field equals specific value
*/
is(value: any): ConditionalThen {
return new ConditionalThen(this, (fieldValue) => fieldValue === value);
}
/**
* Check if field does not equal specific value
*/
isNot(value: any): ConditionalThen {
return new ConditionalThen(this, (fieldValue) => fieldValue !== value);
}
/**
* Check if field exists (not null/undefined)
*/
exists(): ConditionalThen {
return new ConditionalThen(this, (fieldValue) => fieldValue != null);
}
/**
* Check if field matches pattern
*/
matches(pattern: RegExp): ConditionalThen {
return new ConditionalThen(this, (fieldValue) =>
typeof fieldValue === "string" && pattern.test(fieldValue)
);
}
/**
* Check if field is in array of values
*/
in(values: any[]): ConditionalThen {
return new ConditionalThen(this, (fieldValue) => values.includes(fieldValue));
}
/**
* Custom condition function
*/
when(condition: (value: any) => boolean): ConditionalThen {
return new ConditionalThen(this, condition);
}
addCondition(condition: (value: any) => boolean, schema: any): this {
this.conditions.push({ condition, schema });
return this;
}
default(schema: any): any {
this.defaultSchema = schema;
return this.build();
}
build(): any {
// Return a special object that the validation engine can recognize
return {
__conditional: true,
fieldName: this.fieldName,
conditions: this.conditions,
default: this.defaultSchema
};
}
}
/**
* Builder for the "then" part of conditional validation
*/
class ConditionalThen {
constructor(
private builder: ConditionalBuilder,
private condition: (value: any) => boolean
) {}
then(schema: any): ConditionalBuilder {
return this.builder.addCondition(this.condition, schema);
}
}
/**
* Builder for multi-field conditional validation
*/
class MultiConditionalBuilder {
private matchConditions: any = {};
constructor(private fieldNames: string[]) {}
match(conditions: Record<string, any>): MultiConditionalThen {
this.matchConditions = conditions;
return new MultiConditionalThen(this, conditions);
}
}
/**
* Builder for multi-field "then" part
*/
class MultiConditionalThen {
constructor(
private builder: MultiConditionalBuilder,
private conditions: Record<string, any>
) {}
then(schema: any): MultiConditionalElse {
return new MultiConditionalElse(schema, null);
}
}
/**
* Builder for multi-field "else" part
*/
class MultiConditionalElse {
constructor(
private thenSchema: any,
private elseSchema: any
) {}
else(schema: any): any {
return {
__multiConditional: true,
then: this.thenSchema,
else: schema
};
}
}
/**
* Custom validator builder
*/
class CustomValidator {
constructor(private validator: (data: any) => { valid: true } | { error: string }) {}
build(): any {
return {
__customValidator: true,
validator: this.validator
};
}
}
/**
* Revolutionary schema composition with inheritance
*/
export const Extend = {
/**
* Create schema inheritance with method-like syntax
*
* @example
* ```typescript
* const BaseEntitySchema = Interface({
* id: "uuid",
* createdAt: "date",
* updatedAt: "date"
* });
*
* const UserSchema = Extend.from(BaseEntitySchema).with({
* email: "email",
* username: "string(3,20)",
* profile: {
* firstName: "string",
* lastName: "string"
* }
* });
*
* const AdminSchema = Extend.from(UserSchema).with({
* permissions: "string[]",
* role: Make.const("admin")
* }).override({
* email: "email(,254)" // Override with more specific validation
* });
* ```
*/
from(baseSchema: SchemaInterface): ExtendBuilder {
return new ExtendBuilder(baseSchema);
}
};
/**
* Builder for schema extension
*/
class ExtendBuilder {
constructor(private baseSchema: SchemaInterface) {}
with(additionalFields: any): ExtendWithBuilder {
return new ExtendWithBuilder(this.baseSchema, additionalFields);
}
}
/**
* Builder for extended schema with additional methods
*/
class ExtendWithBuilder {
constructor(
private baseSchema: SchemaInterface,
private additionalFields: any
) {}
override(overrides: any): SchemaInterface {
// Merge base schema, additional fields, and overrides
return {
...this.baseSchema,
...this.additionalFields,
...overrides
} as SchemaInterface;
}
build(): SchemaInterface {
return {
...this.baseSchema,
...this.additionalFields
} as SchemaInterface;
}
}
/**
* Export all utilities
*/
export { ConditionalBuilder, MultiConditionalBuilder, CustomValidator, ExtendBuilder };
export default When;