Skip to content

Commit

Permalink
fix ZodType input/output issue
Browse files Browse the repository at this point in the history
  • Loading branch information
omermecitoglu committed Jun 15, 2024
1 parent bf0d90f commit cdaedf0
Show file tree
Hide file tree
Showing 9 changed files with 56 additions and 46 deletions.
24 changes: 12 additions & 12 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@omer-x/next-openapi-route-handler",
"version": "0.3.1",
"version": "0.4.0",
"description": "a Next.js plugin to generate OpenAPI documentation from route handlers",
"keywords": [
"next.js",
Expand Down Expand Up @@ -44,7 +44,7 @@
},
"devDependencies": {
"@omer-x/eslint-config": "^1.0.7",
"@omer-x/openapi-types": "^0.1.1",
"@omer-x/openapi-types": "^0.1.2",
"@types/node": "^20.14.2",
"eslint": "^8.57.0",
"ts-unused-exports": "^10.1.0",
Expand Down
12 changes: 6 additions & 6 deletions src/core/body.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import type { HttpMethod } from "~/types/http";
import type { FixedRequest } from "~/types/request";
import { resolveContent } from "./content";
import type { RequestBodyObject } from "@omer-x/openapi-types/request-body";
import type { ZodError, ZodType } from "zod";
import type { ZodError, ZodType, ZodTypeDef } from "zod";

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

export async function parseRequestBody<B>(
request: FixedRequest<B>,
export async function parseRequestBody<I, O>(
request: FixedRequest<NoInfer<I>>,
method: HttpMethod,
schema?: ZodType<B> | string,
schema?: ZodType<O, ZodTypeDef, I> | string,
isFormData: boolean = false
) {
): Promise<O | null> {
if (!schema || typeof schema === "string") return null;
if (method === "GET") throw new Error("GET routes can't have request body");
if (isFormData) {
Expand Down
4 changes: 2 additions & 2 deletions src/core/path-params.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { safeParse } from "./zod-error-handler";
import type { ZodError, ZodType } from "zod";
import type { ZodError, ZodType, ZodTypeDef } from "zod";

export default function parsePathParams<T>(source?: T, schema?: ZodType<T>) {
export default function parsePathParams<I, O>(source?: I, schema?: ZodType<O, ZodTypeDef, I>) {
if (!schema || !source) return null;
try {
return safeParse(schema, source as Record<string, unknown>);
Expand Down
4 changes: 2 additions & 2 deletions src/core/search-params.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { safeParse } from "./zod-error-handler";
import type { ZodError, ZodType } from "zod";
import type { ZodError, ZodType, ZodTypeDef } from "zod";

function serializeArray(value: string[]) {
return value.join(",");
Expand All @@ -9,7 +9,7 @@ export function deserializeArray(value: string) {
return value.split(",");
}

export default function parseSearchParams<T>(source: URLSearchParams, schema?: ZodType<T>) {
export default function parseSearchParams<I, O>(source: URLSearchParams, schema?: ZodType<O, ZodTypeDef, I>) {
if (!schema) return null;
const sourceKeys = Array.from(new Set(source.keys()));
const params = sourceKeys.reduce((collection, key) => {
Expand Down
4 changes: 2 additions & 2 deletions src/core/zod-error-handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { deserializeArray } from "./search-params";
import type { ZodType } from "zod";
import type { ZodType, ZodTypeDef } from "zod";

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

export function safeParse<T>(schema: ZodType<T>, input: Record<string, unknown>) {
export function safeParse<I, O>(schema: ZodType<O, ZodTypeDef, I>, input: Record<string, unknown>) {
const result = schema.safeParse(input);
if (!result.success) {
for (const issue of result.error.issues) {
Expand Down
38 changes: 24 additions & 14 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { type ZodType } from "zod";
import { parseRequestBody, resolveRequestBody } from "./core/body";
import { resolveParams } from "./core/params";
import parsePathParams from "./core/path-params";
Expand All @@ -7,6 +6,7 @@ import parseSearchParams from "./core/search-params";
import type { HttpMethod } from "./types/http";
import type { RouteHandler, RouteMethodHandler } from "./types/next";
import type { ResponseDefinition } from "./types/response";
import type { ZodType, ZodTypeDef } from "zod";

type ActionSource<PathParams, QueryParams, RequestBody> = {
pathParams: PathParams,
Expand All @@ -20,31 +20,39 @@ type RouteWithoutBody = {
hasFormData?: boolean,
};

type RouteWithBody<Body> = {
type RouteWithBody<I, O> = {
method: Exclude<HttpMethod, "GET" | "DELETE" | "HEAD">,
requestBody?: ZodType<Body> | string,
requestBody?: ZodType<O, ZodTypeDef, I> | string,
hasFormData?: boolean,
};

type RouteOptions<Method, PathParams, QueryParams, RequestBody> = {
type RouteOptions<
Method,
PathParamsInput,
PathParamsOutput,
QueryParamsInput,
QueryParamsOutput,
RequestBodyInput,
RequestBodyOutput,
> = {
operationId: string,
method: Method,
summary: string,
description: string,
tags: string[],
pathParams?: ZodType<PathParams>,
queryParams?: ZodType<QueryParams>,
action: (source: ActionSource<PathParams, QueryParams, RequestBody>) => Response | Promise<Response>,
pathParams?: ZodType<PathParamsOutput, ZodTypeDef, PathParamsInput>,
queryParams?: ZodType<QueryParamsOutput, ZodTypeDef, QueryParamsInput>,
action: (source: ActionSource<PathParamsOutput, QueryParamsOutput, RequestBodyOutput>) => Response | Promise<Response>,
responses: Record<string, ResponseDefinition>,
} & (RouteWithBody<RequestBody> | RouteWithoutBody);
} & (RouteWithBody<RequestBodyInput, RequestBodyOutput> | RouteWithoutBody);

export default function createRoute<M extends HttpMethod, PP, QP, RB>(input: RouteOptions<M, PP, QP, RB>) {
const handler: RouteMethodHandler<PP> = async (request, props) => {
function createRoute<M extends HttpMethod, PPI, PPO, QPI, QPO, RBI, RBO>(input: RouteOptions<M, PPI, PPO, QPI, QPO, RBI, RBO>) {
const handler: RouteMethodHandler<PPI> = async (request, props) => {
try {
const { searchParams } = new URL(request.url);
const pathParams = parsePathParams<PP>(props.params, input.pathParams) as PP;
const queryParams = parseSearchParams(searchParams, input.queryParams) as QP;
const body = await parseRequestBody(request, input.method, input.requestBody ?? undefined, input.hasFormData) as RB;
const pathParams = parsePathParams(props.params, input.pathParams) as PPO;
const queryParams = parseSearchParams(searchParams, input.queryParams) as QPO;
const body = await parseRequestBody(request, input.method, input.requestBody ?? undefined, input.hasFormData) as RBO;
return await input.action({ pathParams, queryParams, body });
} catch (error) {
if (error instanceof Error) {
Expand Down Expand Up @@ -83,5 +91,7 @@ export default function createRoute<M extends HttpMethod, PP, QP, RB>(input: Rou
responses: responses,
};

return { [input.method]: handler } as RouteHandler<M, PP>;
return { [input.method]: handler } as RouteHandler<M, PPI>;
}

export default createRoute;
8 changes: 4 additions & 4 deletions src/types/next.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ type RouteHandlerProps<PathParams> = {
params?: PathParams,
};

export type RouteMethodHandler<PathParams> = ((
export type RouteMethodHandler<PathParamsInput> = ((
request: Request,
props: RouteHandlerProps<PathParams>
props: RouteHandlerProps<PathParamsInput>
) => Promise<Response>) & {
apiData?: OperationObject,
};

export type RouteHandler<HM extends HttpMethod, PathParams> = {
[key in HM]: RouteMethodHandler<PathParams>;
export type RouteHandler<HM extends HttpMethod, PathParamsInput> = {
[key in HM]: RouteMethodHandler<PathParamsInput>;
};
4 changes: 2 additions & 2 deletions src/types/request.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
export interface FixedRequest<RequestBody> {
export interface FixedRequest<RequestBodyInput> {
url: string,
json: () => Promise<RequestBody>,
json: () => Promise<RequestBodyInput>,
formData: () => Promise<FormData>,
}

0 comments on commit cdaedf0

Please sign in to comment.