UNPKG

fortify-schema

Version:

TypeScript interface-like schema validation system that's easier to use than Zod

319 lines (285 loc) 8.28 kB
/** * 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;