Validation pipeline — chain of validators for request data.
1class ValidationPipeline {2 constructor() {3 this.steps = [];4 }56 add(validator) {7 this.steps.push(validator);8 return this;9 }1011 async validate(data) {12 const errors = [];13 let sanitized = { ...data };1415 for (const step of this.steps) {16 const result = await step(sanitized);17 if (result.errors) {18 errors.push(...result.errors);19 }20 if (result.sanitized) {21 sanitized = result.sanitized;22 }23 }2425 return {26 valid: errors.length === 0,27 errors,28 data: sanitized,29 };30 }31}3233// Validators34const trimStrings = (data) => ({35 sanitized: Object.fromEntries(36 Object.entries(data).map(([k, v]) =>37 [k, typeof v === "string" ? v.trim() : v]38 )39 ),40});4142const checkRequired = (fields) => (data) => {43 const errors = fields44 .filter(f => !data[f])45 .map(f => ({ field: f, message: `${f} is required` }));46 return { errors: errors.length ? errors : null };47};4849const checkEmail = (data) => ({50 errors: data.email && !/^[^@]+@[^@]+$/.test(data.email)51 ? [{ field: "email", message: "Invalid email" }]52 : null,53});5455// Pipeline56const pipeline = new ValidationPipeline()57 .add(trimStrings)58 .add(checkRequired(["name", "email"]))59 .add(checkEmail);6061const result = await pipeline.validate(req.body);62if (!result.valid) return res.status(400).json({ errors: result.errors });