Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions src/app/api/v1/resources/providers/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
publicActionErrorDetail,
} from "@/lib/api/v1/_shared/error-envelope";
import { redactHeaderRecord, redactUrlCredentials } from "@/lib/api/v1/_shared/redaction";
import { parseHonoJsonBody } from "@/lib/api/v1/_shared/request-body";
import { parseHonoJsonBody, type SchemaOutput } from "@/lib/api/v1/_shared/request-body";
import {
createdResponse,
jsonResponse,
Expand Down Expand Up @@ -773,11 +773,16 @@ function providerNotFound(c: Context): Response {
});
}

type JsonBodySchema<T> = {
safeParse: (value: unknown) => { success: true; data: T } | { success: false; error: ZodError };
type JsonBodySchema = {
safeParse: (
value: unknown
) => { success: true; data: unknown } | { success: false; error: ZodError };
};

async function parseJson<T>(c: Context, schema: JsonBodySchema<T>): Promise<T | Response> {
async function parseJson<S extends JsonBodySchema>(
c: Context,
schema: S
): Promise<SchemaOutput<S> | Response> {
const body = await parseHonoJsonBody(c, schema);
if (!body.ok) return body.response;
return body.data;
Expand Down
27 changes: 17 additions & 10 deletions src/lib/api/v1/_shared/request-body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,17 @@ import { createProblemResponse, normalizeZodPath } from "./error-envelope";

export type ParsedBodyResult<T> = { ok: true; data: T } | { ok: false; response: Response };

type JsonBodySchema<T> = {
safeParse: (value: unknown) => { success: true; data: T } | { success: false; error: z.ZodError };
type JsonBodySchema = {
safeParse: (
value: unknown
) => { success: true; data: unknown } | { success: false; error: z.ZodError };
};

// zod 4 marks the schema output type parameter as `out` (covariant), so the
// compiler cannot infer it from a value parameter. Extract it from the
// `_output` phantom property instead.
export type SchemaOutput<S> = S extends { _output: infer O } ? O : unknown;

type ParseJsonBodyOptions = {
validationErrorCode?: (error: z.ZodError) => string | undefined;
};
Expand All @@ -20,10 +27,10 @@ type HonoJsonRequest = {
};
};

export async function parseJsonBody<T>(
export async function parseJsonBody<S extends JsonBodySchema>(
request: Request,
schema: JsonBodySchema<T>
): Promise<ParsedBodyResult<T>> {
schema: S
): Promise<ParsedBodyResult<SchemaOutput<S>>> {
const contentType = request.headers.get("content-type") ?? "";
if (!contentType.toLowerCase().includes("application/json")) {
return {
Expand Down Expand Up @@ -70,14 +77,14 @@ export async function parseJsonBody<T>(
};
}

return { ok: true, data: parsed.data };
return { ok: true, data: parsed.data as SchemaOutput<S> };
}

export async function parseHonoJsonBody<T>(
export async function parseHonoJsonBody<S extends JsonBodySchema>(
c: HonoJsonRequest,
schema: JsonBodySchema<T>,
schema: S,
options?: ParseJsonBodyOptions
): Promise<ParsedBodyResult<T>> {
): Promise<ParsedBodyResult<SchemaOutput<S>>> {
const contentType =
c.req.header("content-type") ??
c.req.header("Content-Type") ??
Expand Down Expand Up @@ -128,5 +135,5 @@ export async function parseHonoJsonBody<T>(
};
}

return { ok: true, data: parsed.data };
return { ok: true, data: parsed.data as SchemaOutput<S> };
}
4 changes: 3 additions & 1 deletion src/lib/api/v1/schemas/audit-logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ export const AuditLogListQuerySchema = z.object({
success: z
.enum(["true", "false"])
.optional()
.transform((val) => (val === undefined ? undefined : val === "true"))
.transform((val: "true" | "false" | undefined) =>
val === undefined ? undefined : val === "true"
)
.describe("Optional success filter."),
from: IsoDateTimeStringSchema.optional().describe("Optional inclusive start time."),
to: IsoDateTimeStringSchema.optional().describe("Optional inclusive end time."),
Expand Down
2 changes: 1 addition & 1 deletion src/lib/api/v1/schemas/me.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { z } from "@hono/zod-openapi";
const NumberQuerySchema = z.coerce.number().optional();
const BooleanQuerySchema = z
.union([z.literal("true"), z.literal("false"), z.boolean()])
.transform((value) => value === true || value === "true")
.transform((value: boolean | "true" | "false") => value === true || value === "true")
.optional();

export const MeUsageLogsQuerySchema = z.object({
Expand Down
2 changes: 1 addition & 1 deletion src/lib/api/v1/schemas/system-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const CodexPriorityBillingSourceSchema = z
const TimeZoneSchema = z
.string()
.refine(
(value) => {
(value: string) => {
try {
new Intl.DateTimeFormat("en-US", { timeZone: value });
return true;
Expand Down
2 changes: 1 addition & 1 deletion src/lib/api/v1/schemas/usage-logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { z } from "@hono/zod-openapi";
const NumberQuerySchema = z.coerce.number().optional();
const BooleanQuerySchema = z
.union([z.literal("true"), z.literal("false"), z.boolean()])
.transform((value) => value === true || value === "true")
.transform((value: boolean | "true" | "false") => value === true || value === "true")
.optional();

export const UsageLogsQuerySchema = z.object({
Expand Down