forked from Split-Naira/SplitNaira
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvalidateResponse.ts
More file actions
90 lines (76 loc) · 2.61 KB
/
Copy pathvalidateResponse.ts
File metadata and controls
90 lines (76 loc) · 2.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
import { z, ZodError } from "zod";
import { Request, Response, NextFunction } from "express";
import { logger } from "../services/logger.js";
export type RouteHandler = (
req: Request,
res: Response,
next: NextFunction,
) => Promise<void> | void;
// In-process counter — exposed for tests and metrics endpoints.
let _validationFailureCount = 0;
export function getValidationFailureCount(): number {
return _validationFailureCount;
}
export function resetValidationFailureCount(): void {
_validationFailureCount = 0;
}
/**
* Wraps a route handler and validates its JSON response body against
* a Zod schema before sending it to the client.
*
* Behaviour on schema mismatch:
* - Always increments the in-process failure counter and logs the diff.
* - Returns 500 when strict mode is active (production default, or when
* STRICT_RESPONSE_VALIDATION=true is set explicitly).
* - In non-strict mode, logs-only and forwards the original body so
* development iteration is not blocked by partial coverage.
*
* Enable strict mode: set STRICT_RESPONSE_VALIDATION=true
* Disable in production: set STRICT_RESPONSE_VALIDATION=false (not recommended)
*/
export function withResponseValidation<T>(
schema: z.ZodType<T>,
handler: RouteHandler,
): RouteHandler {
return async (req, res, next) => {
const originalJson = res.json.bind(res);
res.json = (body: unknown) => {
const result = schema.safeParse(body);
if (!result.success) {
_validationFailureCount += 1;
const formatted = formatZodError(result.error);
const requestId = res.locals.requestId as string | undefined;
logger.error("Response schema validation failed", {
requestId,
method: req.method,
path: req.path,
errors: formatted,
validationFailureCount: _validationFailureCount,
});
if (isStrictMode()) {
res.status(500);
return originalJson({
error: "internal_error",
message: "Response schema validation failed.",
requestId,
details: {},
});
}
}
return originalJson(body);
};
return handler(req, res, next);
};
}
function isStrictMode(): boolean {
const explicit = process.env.STRICT_RESPONSE_VALIDATION;
if (explicit === "true") return true;
if (explicit === "false") return false;
// Default: strict in production, lenient elsewhere.
return process.env.NODE_ENV === "production";
}
function formatZodError(error: ZodError): string {
return error.issues
.map((i) => ` [${i.path.join(".")}] ${i.message}`)
.join("\n");
}