feat(project): init
This commit is contained in:
155
lib/request-schema.ts
Normal file
155
lib/request-schema.ts
Normal file
@@ -0,0 +1,155 @@
|
||||
import type {
|
||||
GenerateOutputFormat,
|
||||
GeneratePayload,
|
||||
HttpError,
|
||||
} from "@/lib/types";
|
||||
|
||||
const SIZE_OPTIONS = ["auto", "1024x1024", "1536x1024", "1024x1536"] as const;
|
||||
const QUALITY_OPTIONS = ["auto", "low", "medium", "high"] as const;
|
||||
const OUTPUT_FORMAT_OPTIONS = ["png", "jpeg", "webp"] as const;
|
||||
const BACKGROUND_OPTIONS = ["auto", "transparent", "opaque"] as const;
|
||||
const MODERATION_OPTIONS = ["auto", "low"] as const;
|
||||
|
||||
export function parseGenerateRequest(
|
||||
body: unknown,
|
||||
model: string,
|
||||
): GeneratePayload {
|
||||
if (!isRecord(body)) {
|
||||
throw createRequestError(400, "请求体必须是 JSON 对象。");
|
||||
}
|
||||
|
||||
const prompt = normalizePrompt(body.prompt);
|
||||
const size = normalizeEnum(body.size, SIZE_OPTIONS, "size");
|
||||
const quality = normalizeEnum(body.quality, QUALITY_OPTIONS, "quality");
|
||||
const outputFormat = normalizeEnum(
|
||||
body.outputFormat,
|
||||
OUTPUT_FORMAT_OPTIONS,
|
||||
"outputFormat",
|
||||
);
|
||||
const background = normalizeEnum(
|
||||
body.background,
|
||||
BACKGROUND_OPTIONS,
|
||||
"background",
|
||||
);
|
||||
const moderation = normalizeEnum(
|
||||
body.moderation,
|
||||
MODERATION_OPTIONS,
|
||||
"moderation",
|
||||
);
|
||||
const count = normalizeCount(body.count);
|
||||
const outputCompression = normalizeCompression(
|
||||
body.outputCompression,
|
||||
outputFormat,
|
||||
);
|
||||
const user = normalizeUser(body.user);
|
||||
|
||||
if (outputFormat === "jpeg" && background === "transparent") {
|
||||
throw createRequestError(
|
||||
400,
|
||||
"JPEG 不支持透明背景,请改为 PNG / WEBP 或将背景改成不透明。",
|
||||
);
|
||||
}
|
||||
|
||||
const payload: GeneratePayload = {
|
||||
model,
|
||||
prompt,
|
||||
size,
|
||||
quality,
|
||||
n: count,
|
||||
moderation,
|
||||
output_format: outputFormat,
|
||||
background,
|
||||
};
|
||||
|
||||
if (typeof outputCompression === "number") {
|
||||
payload.output_compression = outputCompression;
|
||||
}
|
||||
|
||||
if (user) {
|
||||
payload.user = user;
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
export function createRequestError(
|
||||
statusCode: number,
|
||||
message: string,
|
||||
): HttpError {
|
||||
const error = new Error(message) as HttpError;
|
||||
error.statusCode = statusCode;
|
||||
return error;
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function normalizePrompt(value: unknown): string {
|
||||
const prompt = String(value ?? "").trim();
|
||||
|
||||
if (!prompt) {
|
||||
throw createRequestError(400, "Prompt 不能为空。");
|
||||
}
|
||||
|
||||
if (prompt.length > 32000) {
|
||||
throw createRequestError(400, "Prompt 不能超过 32000 个字符。");
|
||||
}
|
||||
|
||||
return prompt;
|
||||
}
|
||||
|
||||
function normalizeEnum<T extends string>(
|
||||
value: unknown,
|
||||
options: readonly T[],
|
||||
fieldName: string,
|
||||
): T {
|
||||
const normalizedValue = (String(value ?? "").trim() || options[0]) as T;
|
||||
|
||||
if (!options.includes(normalizedValue)) {
|
||||
throw createRequestError(400, `字段 ${fieldName} 的取值不合法。`);
|
||||
}
|
||||
|
||||
return normalizedValue;
|
||||
}
|
||||
|
||||
function normalizeCount(value: unknown): number {
|
||||
const numericValue = Number.parseInt(String(value ?? "1"), 10);
|
||||
|
||||
if (!Number.isFinite(numericValue) || numericValue < 1 || numericValue > 4) {
|
||||
throw createRequestError(400, "单次最多生成 4 张图片。");
|
||||
}
|
||||
|
||||
return numericValue;
|
||||
}
|
||||
|
||||
function normalizeCompression(
|
||||
value: unknown,
|
||||
outputFormat: GenerateOutputFormat,
|
||||
): number | undefined {
|
||||
if (outputFormat === "png") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const numericValue = Number.parseInt(String(value ?? "90"), 10);
|
||||
|
||||
if (
|
||||
!Number.isFinite(numericValue) ||
|
||||
numericValue < 0 ||
|
||||
numericValue > 100
|
||||
) {
|
||||
throw createRequestError(400, "压缩质量必须在 0 到 100 之间。");
|
||||
}
|
||||
|
||||
return numericValue;
|
||||
}
|
||||
|
||||
function normalizeUser(value: unknown): string | undefined {
|
||||
const user = String(value ?? "").trim();
|
||||
|
||||
if (!user) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return user.slice(0, 64);
|
||||
}
|
||||
Reference in New Issue
Block a user