mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-21 22:52:19 +03:00
v1SearchSchema.provider was a hard-coded z.enum that rejected any id outside its list before the route's own resolveSearchProvider() check ever ran, so unknown/short-alias provider ids (grok, brave, serper, ...) always surfaced a generic "Invalid request" instead of the informative "Unknown search provider: <id>" message. Relax the schema to a free-form string and let resolveSearchProvider() own runtime validation (as it already did for ids that passed the enum). Also extend SEARCH_PROVIDER_ALIASES with short-form aliases mirroring the existing jina/jina-ai pattern (brave, serper, perplexity, exa, tavily, google-pse, linkup, ollama, searchapi, youcom, searxng, zai, duckduckgo), and surface the first Zod validation issue's field name instead of the generic message for other still-invalid fields (e.g. search_type).
130 lines
3.7 KiB
TypeScript
130 lines
3.7 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { z } from "zod";
|
|
|
|
type ValidationErrorDetail = {
|
|
field: string;
|
|
message: string;
|
|
};
|
|
|
|
type ValidationErrorPayload = {
|
|
message: string;
|
|
details: ValidationErrorDetail[];
|
|
};
|
|
|
|
type ValidationSuccess<TData> = {
|
|
success: true;
|
|
data: TData;
|
|
};
|
|
|
|
type ValidationFailure = {
|
|
success: false;
|
|
error: ValidationErrorPayload;
|
|
};
|
|
|
|
export type ValidationResult<TData> = ValidationSuccess<TData> | ValidationFailure;
|
|
|
|
// ──── Helper ────
|
|
|
|
/**
|
|
* Parse and validate request body with a Zod schema.
|
|
* Returns { success: true, data } or { success: false, error }.
|
|
*/
|
|
export function validateBody<TSchema extends z.ZodTypeAny>(
|
|
schema: TSchema,
|
|
body: unknown
|
|
): ValidationResult<z.infer<TSchema>> {
|
|
const result = schema.safeParse(body);
|
|
if (result.success) {
|
|
return { success: true, data: result.data };
|
|
}
|
|
const issues = Array.isArray(result.error?.issues) ? result.error.issues : [];
|
|
return {
|
|
success: false,
|
|
error: {
|
|
message: "Invalid request",
|
|
details: issues.map((e) => ({
|
|
field: e.path.join("."),
|
|
message: e.message,
|
|
})),
|
|
},
|
|
};
|
|
}
|
|
|
|
export function isValidationFailure<TData>(
|
|
validation: ValidationResult<TData>
|
|
): validation is ValidationFailure {
|
|
return validation.success === false;
|
|
}
|
|
|
|
/**
|
|
* Build a human-readable 400 message from a validation failure, naming the
|
|
* first offending field instead of the generic "Invalid request" (#10849).
|
|
* Intended for routes that reply with a single message string (e.g.
|
|
* `errorResponse()`) rather than the full `{ message, details }` envelope
|
|
* returned by `validatedJsonBody()`.
|
|
*/
|
|
export function formatValidationMessage(error: ValidationErrorPayload): string {
|
|
const [first] = error.details;
|
|
if (!first) return error.message;
|
|
return first.field ? `${first.field}: ${first.message}` : first.message;
|
|
}
|
|
|
|
/**
|
|
* Result of attempting to parse and validate a JSON body against a Zod schema.
|
|
*
|
|
* On failure, `response` is a fully-prepared `NextResponse` (with the standard
|
|
* error envelope) that the caller should return directly, so route handlers can
|
|
* do `if (!r.success) return r.response;` without knowing the envelope shape.
|
|
*/
|
|
export type ValidatedJsonBodyResult<TData> =
|
|
{ success: true; data: TData } | { success: false; response: NextResponse };
|
|
|
|
/**
|
|
* Parse a request body as JSON and validate it against a Zod schema in one
|
|
* step. Returns the parsed (and type-narrowed) data on success, or a ready-to-
|
|
* return 400 `NextResponse` on failure. Both the malformed-JSON and the failed-
|
|
* validation paths emit the same error envelope
|
|
* (`{ error: { message, details: [{ field, message }] } }`), so a single client
|
|
* parser covers both.
|
|
*
|
|
* Usage:
|
|
*
|
|
* ```ts
|
|
* const result = await validatedJsonBody(request, updateComboSchema);
|
|
* if (!result.success) return result.response;
|
|
* const body = result.data; // typed as z.infer<typeof updateComboSchema>
|
|
* ```
|
|
*/
|
|
export async function validatedJsonBody<TSchema extends z.ZodTypeAny>(
|
|
request: Request,
|
|
schema: TSchema
|
|
): Promise<ValidatedJsonBodyResult<z.infer<TSchema>>> {
|
|
let raw: unknown;
|
|
try {
|
|
raw = await request.json();
|
|
} catch {
|
|
return {
|
|
success: false,
|
|
response: NextResponse.json(
|
|
{
|
|
error: {
|
|
message: "Invalid request",
|
|
details: [{ field: "body", message: "Invalid JSON body" }],
|
|
},
|
|
},
|
|
{ status: 400 }
|
|
),
|
|
};
|
|
}
|
|
|
|
const validation = validateBody(schema, raw);
|
|
if (isValidationFailure(validation)) {
|
|
return {
|
|
success: false,
|
|
response: NextResponse.json({ error: validation.error }, { status: 400 }),
|
|
};
|
|
}
|
|
|
|
return { success: true, data: validation.data };
|
|
}
|