mirror of
https://github.com/hex248/sprint.git
synced 2026-02-07 18:23:03 +00:00
zod validation setup
This commit is contained in:
1
bun.lock
1
bun.lock
@@ -20,6 +20,7 @@
|
|||||||
"jsonwebtoken": "^9.0.3",
|
"jsonwebtoken": "^9.0.3",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
"sharp": "^0.34.5",
|
"sharp": "^0.34.5",
|
||||||
|
"zod": "^3.23.8",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bcrypt": "^6.0.0",
|
"@types/bcrypt": "^6.0.0",
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
"drizzle-orm": "^0.45.0",
|
"drizzle-orm": "^0.45.0",
|
||||||
"jsonwebtoken": "^9.0.3",
|
"jsonwebtoken": "^9.0.3",
|
||||||
"pg": "^8.16.3",
|
"pg": "^8.16.3",
|
||||||
"sharp": "^0.34.5"
|
"sharp": "^0.34.5",
|
||||||
|
"zod": "^3.23.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
60
packages/backend/src/validation.ts
Normal file
60
packages/backend/src/validation.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import type { ApiError } from "@issue/shared";
|
||||||
|
import type { z } from "zod";
|
||||||
|
|
||||||
|
type ZodSchema<T> = z.ZodSchema<T>;
|
||||||
|
type ZodError = z.ZodError;
|
||||||
|
|
||||||
|
export function formatZodError(error: ZodError): ApiError {
|
||||||
|
const details: Record<string, string[]> = {};
|
||||||
|
for (const issue of error.issues) {
|
||||||
|
const path = issue.path.join(".") || "_root";
|
||||||
|
if (!details[path]) details[path] = [];
|
||||||
|
details[path].push(issue.message);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
error: "validation failed",
|
||||||
|
code: "VALIDATION_ERROR",
|
||||||
|
details,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function errorResponse(error: string, code?: string, status = 400): Response {
|
||||||
|
const body: ApiError = { error, code };
|
||||||
|
return Response.json(body, { status });
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function parseJsonBody<T>(
|
||||||
|
req: Request,
|
||||||
|
schema: ZodSchema<T>,
|
||||||
|
): Promise<{ data: T } | { error: Response }> {
|
||||||
|
let body: unknown;
|
||||||
|
try {
|
||||||
|
body = await req.json();
|
||||||
|
} catch {
|
||||||
|
return {
|
||||||
|
error: Response.json({ error: "invalid JSON", code: "INVALID_JSON" } satisfies ApiError, {
|
||||||
|
status: 400,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = schema.safeParse(body);
|
||||||
|
if (!result.success) {
|
||||||
|
return {
|
||||||
|
error: Response.json(formatZodError(result.error), { status: 400 }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { data: result.data };
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseQueryParams<T>(url: URL, schema: ZodSchema<T>): { data: T } | { error: Response } {
|
||||||
|
const params = Object.fromEntries(url.searchParams);
|
||||||
|
const result = schema.safeParse(params);
|
||||||
|
if (!result.success) {
|
||||||
|
return {
|
||||||
|
error: Response.json(formatZodError(result.error), { status: 400 }),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { data: result.data };
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user