// Utility Types and Functions type NestedPath = string[]; class ParseError extends Error { public type: string = "ParseError"; public path: NestedPath; // Changed from 'trace' to 'path' for clarity public input: any; constructor(input: any, path: NestedPath, msg: string) { super(msg); this.input = input; this.path = path; } // Method to prepend a new path segment prependPath(segment: string): ParseError { return new ParseError( this.input, [segment, ...this.path], this.message, ); } // Method to append a new path segment appendPath(segment: string): ParseError { return new ParseError( this.input, [...this.path, segment], this.message, ); } // Format the error message with the path formattedMessage(): string { return `Error at "${this.path.join(".") || "root"}": ${this.message}`; } } function createParseError( input: any, path: NestedPath, msg: string, ): ParseError { return new ParseError(input, path, msg); } // Base Schema Classes export interface Schema { parse(input: unknown, path?: NestedPath): Result; checkIfValid(input: unknown): boolean; nullable(): NullableSchema>; option(): OptionSchema>; or[]>(...schema: S): UnionSchema<[this, ...S]>; } type CheckFunction = (input: T, path: NestedPath) => ParseError | void; export abstract class BaseSchema implements Schema { protected checks: CheckFunction[] = []; public addCheck(check: CheckFunction): this { this.checks.push(check); return this; } protected runChecks(input: T, path: NestedPath): Result { for (const check of this.checks) { const error = check(input, path); if (error) { return err(error); } } return ok(input); } checkIfValid(input: unknown): boolean { return this.parse(input).isOk(); } nullable(): NullableSchema> { return new NullableSchema(this); } or[]>(...schema: S): UnionSchema<[this, ...S]> { return new UnionSchema(this, ...schema); } option(): OptionSchema> { return new OptionSchema(this); } abstract parse(input: unknown, path?: NestedPath): Result; } export abstract class PrimitiveSchema extends BaseSchema { protected abstract initialCheck( input: unknown, path: NestedPath, ): Result; protected checkPrimitive( input: unknown, type: | "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function", path: NestedPath, ): Result { const inputType = typeof input; if (inputType === type) { return ok(input as U); } return err( createParseError( input, path, `Expected type '${type}', received '${inputType}'`, ), ); } public parse(input: unknown, path: NestedPath = []): Result { return this.initialCheck(input, path).andThen((value) => this.runChecks(value, path) ); } } // Example: StringSchema with Improved Error Handling export class StringSchema extends PrimitiveSchema { private static readonly emailRegex = /^(([^<>()[\]\.,;:\s@\"]+(\.[^<>()[\]\.,;:\s@\"]+)*)|(\".+\"))@(([^<>()[\]\.,;:\s@\"]+\.)+[^<>()[\]\.,;:\s@\"]{2,})$/i; private static readonly ipRegex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(?!$)|$){4}$/; protected initialCheck( input: unknown, path: NestedPath, ): Result { return this.checkPrimitive(input, "string", path); } public max(length: number, msg?: string): this { return this.addCheck((input, path) => { if (input.length <= length) return; return createParseError( input, path, msg || `String length must be at most ${length} characters long`, ); }); } public min(length: number, msg?: string): this { return this.addCheck((input, path) => { if (input.length >= length) return; return createParseError( input, path, msg || `String length must be at least ${length} characters long`, ); }); } public regex(pattern: RegExp, msg?: string): this { return this.addCheck((input, path) => { if (pattern.test(input)) return; return createParseError( input, path, msg || `String does not match the pattern ${pattern}`, ); }); } public email(msg?: string): this { return this.regex( StringSchema.emailRegex, msg || "Invalid email address", ); } public ip(msg?: string): this { return this.regex(StringSchema.ipRegex, msg || "Invalid IP address"); } } // Refactored ObjectSchema with Improved Error Handling class ObjectSchema>> extends BaseSchema<{ [K in keyof O]: InferSchema }> { constructor(private readonly schema: O) { super(); } protected initialCheck( input: unknown, path: NestedPath, ): Result<{ [K in keyof O]: InferSchema }, ParseError> { if (typeof input !== "object" || input === null) { return err( createParseError( input, path, `Expected an object, received '${typeof input}'`, ), ); } const obj = input as Record; const parsedObj: Partial<{ [K in keyof O]: InferSchema }> = {}; for (const key in this.schema) { const value = obj[key]; const fieldPath = [...path, key]; const result = this.schema[key].parse(value, fieldPath); if (result.isErr()) { return err(result.error); } parsedObj[key] = result.value; } return ok(parsedObj as { [K in keyof O]: InferSchema }); } } // Refactored UnionSchema with Simplified Error Handling class UnionSchema[]> extends BaseSchema> { constructor(...schemas: S) { super(); this.schemas = schemas; } private schemas: S; protected initialCheck( input: unknown, path: NestedPath, ): Result, ParseError> { const errors: ParseError[] = []; for (const schema of this.schemas) { const result = schema.parse(input, path); if (result.isOk()) { return ok(result.value); } errors.push(result.error); } // Combine error messages for better readability const combinedMessage = errors.map((err) => err.formattedMessage()) .join(" | "); return err( createParseError( input, path, `Union validation failed: ${combinedMessage}`, ), ); } } // Refactored ArraySchema with Improved Error Handling class ArraySchema> extends BaseSchema[]> { constructor(private readonly schema: S) { super(); } protected initialCheck( input: unknown, path: NestedPath, ): Result[], ParseError> { if (!Array.isArray(input)) { return err( createParseError( input, path, `Expected an array, received '${typeof input}'`, ), ); } const parsedArray: InferSchema[] = []; for (let i = 0; i < input.length; i++) { const elementPath = [...path, `${i}`]; const result = this.schema.parse(input[i], elementPath); if (result.isErr()) { return err(result.error); } parsedArray.push(result.value); } return ok(parsedArray); } } // NullableSchema, OptionSchema, and Other Schemas would follow similar patterns, // ensuring that error paths are correctly managed and messages are clear. // Example Validator Usage class Validator { string(): StringSchema { return new StringSchema(); } // ... other schema methods remain unchanged ... union[]>(...schemas: S): UnionSchema { return new UnionSchema(...schemas); } array>(elementSchema: S): ArraySchema { return new ArraySchema(elementSchema); } // ... other schema methods remain unchanged ... } const v = new Validator(); // Example Usage with Improved Error Handling const schema = v.union( v.string().max(4, "String exceeds maximum length of 4"), v.string().min(10, "Number must be at least 10"), ); const res = schema.parse("11234", ["input"]); if (res.isErr()) { console.error(res.error.formattedMessage()); } else { console.log("Parsed Value:", res.value); } // Utility Types type InferSchema = S extends Schema ? T : never; type InferSchemaUnion[]> = S[number] extends Schema ? U : never; type NestedArray = T | NestedArray[];