Skip to content

Commit cdaedf0

Browse files
committed
fix ZodType input/output issue
1 parent bf0d90f commit cdaedf0

File tree

9 files changed

+56
-46
lines changed

9 files changed

+56
-46
lines changed

package-lock.json

Lines changed: 12 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@omer-x/next-openapi-route-handler",
3-
"version": "0.3.1",
3+
"version": "0.4.0",
44
"description": "a Next.js plugin to generate OpenAPI documentation from route handlers",
55
"keywords": [
66
"next.js",
@@ -44,7 +44,7 @@
4444
},
4545
"devDependencies": {
4646
"@omer-x/eslint-config": "^1.0.7",
47-
"@omer-x/openapi-types": "^0.1.1",
47+
"@omer-x/openapi-types": "^0.1.2",
4848
"@types/node": "^20.14.2",
4949
"eslint": "^8.57.0",
5050
"ts-unused-exports": "^10.1.0",

src/core/body.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import type { HttpMethod } from "~/types/http";
22
import type { FixedRequest } from "~/types/request";
33
import { resolveContent } from "./content";
44
import type { RequestBodyObject } from "@omer-x/openapi-types/request-body";
5-
import type { ZodError, ZodType } from "zod";
5+
import type { ZodError, ZodType, ZodTypeDef } from "zod";
66

7-
export function resolveRequestBody(source?: ZodType<unknown> | string, isFormData: boolean = false) {
7+
export function resolveRequestBody<I, O>(source?: ZodType<O, ZodTypeDef, I> | string, isFormData: boolean = false) {
88
if (!source) return undefined;
99
return {
1010
// description: "", // how to fill this?
@@ -13,12 +13,12 @@ export function resolveRequestBody(source?: ZodType<unknown> | string, isFormDat
1313
} as RequestBodyObject;
1414
}
1515

16-
export async function parseRequestBody<B>(
17-
request: FixedRequest<B>,
16+
export async function parseRequestBody<I, O>(
17+
request: FixedRequest<NoInfer<I>>,
1818
method: HttpMethod,
19-
schema?: ZodType<B> | string,
19+
schema?: ZodType<O, ZodTypeDef, I> | string,
2020
isFormData: boolean = false
21-
) {
21+
): Promise<O | null> {
2222
if (!schema || typeof schema === "string") return null;
2323
if (method === "GET") throw new Error("GET routes can't have request body");
2424
if (isFormData) {

src/core/path-params.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { safeParse } from "./zod-error-handler";
2-
import type { ZodError, ZodType } from "zod";
2+
import type { ZodError, ZodType, ZodTypeDef } from "zod";
33

4-
export default function parsePathParams<T>(source?: T, schema?: ZodType<T>) {
4+
export default function parsePathParams<I, O>(source?: I, schema?: ZodType<O, ZodTypeDef, I>) {
55
if (!schema || !source) return null;
66
try {
77
return safeParse(schema, source as Record<string, unknown>);

src/core/search-params.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { safeParse } from "./zod-error-handler";
2-
import type { ZodError, ZodType } from "zod";
2+
import type { ZodError, ZodType, ZodTypeDef } from "zod";
33

44
function serializeArray(value: string[]) {
55
return value.join(",");
@@ -9,7 +9,7 @@ export function deserializeArray(value: string) {
99
return value.split(",");
1010
}
1111

12-
export default function parseSearchParams<T>(source: URLSearchParams, schema?: ZodType<T>) {
12+
export default function parseSearchParams<I, O>(source: URLSearchParams, schema?: ZodType<O, ZodTypeDef, I>) {
1313
if (!schema) return null;
1414
const sourceKeys = Array.from(new Set(source.keys()));
1515
const params = sourceKeys.reduce((collection, key) => {

src/core/zod-error-handler.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { deserializeArray } from "./search-params";
2-
import type { ZodType } from "zod";
2+
import type { ZodType, ZodTypeDef } from "zod";
33

44
function convertStringToNumber(input: Record<string, unknown>, keys: string[]) {
55
return keys.reduce((mutation, key) => {
@@ -25,7 +25,7 @@ function convertStringToArray(input: Record<string, unknown>, keys: string[]) {
2525
}, input);
2626
}
2727

28-
export function safeParse<T>(schema: ZodType<T>, input: Record<string, unknown>) {
28+
export function safeParse<I, O>(schema: ZodType<O, ZodTypeDef, I>, input: Record<string, unknown>) {
2929
const result = schema.safeParse(input);
3030
if (!result.success) {
3131
for (const issue of result.error.issues) {

src/index.ts

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { type ZodType } from "zod";
21
import { parseRequestBody, resolveRequestBody } from "./core/body";
32
import { resolveParams } from "./core/params";
43
import parsePathParams from "./core/path-params";
@@ -7,6 +6,7 @@ import parseSearchParams from "./core/search-params";
76
import type { HttpMethod } from "./types/http";
87
import type { RouteHandler, RouteMethodHandler } from "./types/next";
98
import type { ResponseDefinition } from "./types/response";
9+
import type { ZodType, ZodTypeDef } from "zod";
1010

1111
type ActionSource<PathParams, QueryParams, RequestBody> = {
1212
pathParams: PathParams,
@@ -20,31 +20,39 @@ type RouteWithoutBody = {
2020
hasFormData?: boolean,
2121
};
2222

23-
type RouteWithBody<Body> = {
23+
type RouteWithBody<I, O> = {
2424
method: Exclude<HttpMethod, "GET" | "DELETE" | "HEAD">,
25-
requestBody?: ZodType<Body> | string,
25+
requestBody?: ZodType<O, ZodTypeDef, I> | string,
2626
hasFormData?: boolean,
2727
};
2828

29-
type RouteOptions<Method, PathParams, QueryParams, RequestBody> = {
29+
type RouteOptions<
30+
Method,
31+
PathParamsInput,
32+
PathParamsOutput,
33+
QueryParamsInput,
34+
QueryParamsOutput,
35+
RequestBodyInput,
36+
RequestBodyOutput,
37+
> = {
3038
operationId: string,
3139
method: Method,
3240
summary: string,
3341
description: string,
3442
tags: string[],
35-
pathParams?: ZodType<PathParams>,
36-
queryParams?: ZodType<QueryParams>,
37-
action: (source: ActionSource<PathParams, QueryParams, RequestBody>) => Response | Promise<Response>,
43+
pathParams?: ZodType<PathParamsOutput, ZodTypeDef, PathParamsInput>,
44+
queryParams?: ZodType<QueryParamsOutput, ZodTypeDef, QueryParamsInput>,
45+
action: (source: ActionSource<PathParamsOutput, QueryParamsOutput, RequestBodyOutput>) => Response | Promise<Response>,
3846
responses: Record<string, ResponseDefinition>,
39-
} & (RouteWithBody<RequestBody> | RouteWithoutBody);
47+
} & (RouteWithBody<RequestBodyInput, RequestBodyOutput> | RouteWithoutBody);
4048

41-
export default function createRoute<M extends HttpMethod, PP, QP, RB>(input: RouteOptions<M, PP, QP, RB>) {
42-
const handler: RouteMethodHandler<PP> = async (request, props) => {
49+
function createRoute<M extends HttpMethod, PPI, PPO, QPI, QPO, RBI, RBO>(input: RouteOptions<M, PPI, PPO, QPI, QPO, RBI, RBO>) {
50+
const handler: RouteMethodHandler<PPI> = async (request, props) => {
4351
try {
4452
const { searchParams } = new URL(request.url);
45-
const pathParams = parsePathParams<PP>(props.params, input.pathParams) as PP;
46-
const queryParams = parseSearchParams(searchParams, input.queryParams) as QP;
47-
const body = await parseRequestBody(request, input.method, input.requestBody ?? undefined, input.hasFormData) as RB;
53+
const pathParams = parsePathParams(props.params, input.pathParams) as PPO;
54+
const queryParams = parseSearchParams(searchParams, input.queryParams) as QPO;
55+
const body = await parseRequestBody(request, input.method, input.requestBody ?? undefined, input.hasFormData) as RBO;
4856
return await input.action({ pathParams, queryParams, body });
4957
} catch (error) {
5058
if (error instanceof Error) {
@@ -83,5 +91,7 @@ export default function createRoute<M extends HttpMethod, PP, QP, RB>(input: Rou
8391
responses: responses,
8492
};
8593

86-
return { [input.method]: handler } as RouteHandler<M, PP>;
94+
return { [input.method]: handler } as RouteHandler<M, PPI>;
8795
}
96+
97+
export default createRoute;

src/types/next.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@ type RouteHandlerProps<PathParams> = {
55
params?: PathParams,
66
};
77

8-
export type RouteMethodHandler<PathParams> = ((
8+
export type RouteMethodHandler<PathParamsInput> = ((
99
request: Request,
10-
props: RouteHandlerProps<PathParams>
10+
props: RouteHandlerProps<PathParamsInput>
1111
) => Promise<Response>) & {
1212
apiData?: OperationObject,
1313
};
1414

15-
export type RouteHandler<HM extends HttpMethod, PathParams> = {
16-
[key in HM]: RouteMethodHandler<PathParams>;
15+
export type RouteHandler<HM extends HttpMethod, PathParamsInput> = {
16+
[key in HM]: RouteMethodHandler<PathParamsInput>;
1717
};

src/types/request.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
export interface FixedRequest<RequestBody> {
1+
export interface FixedRequest<RequestBodyInput> {
22
url: string,
3-
json: () => Promise<RequestBody>,
3+
json: () => Promise<RequestBodyInput>,
44
formData: () => Promise<FormData>,
55
}

0 commit comments

Comments
 (0)