chanfana
Version:
OpenAPI 3 and 3.1 schema generator and validator for Hono, itty-router and more!
148 lines (126 loc) • 5.61 kB
text/typescript
import type { RouteConfig, ZodMediaTypeObject } from "@asteasolutions/zod-to-openapi";
import type { HeadersObject as HeadersObject30, LinksObject as LinksObject30, OpenAPIObject } from "openapi3-ts/oas30";
import type { HeadersObject as HeadersObject31, LinksObject as LinksObject31 } from "openapi3-ts/oas31";
import type { ZodObject, ZodPipe, ZodType, z } from "zod";
// Type alias for compatibility with Zod v4
export type AnyZodObject = ZodObject<any, any>;
export type OrderByDirection = "asc" | "desc";
export type Simplify<T> = { [KeyType in keyof T]: T[KeyType] } & {};
export type IsEqual<A, B> = (<G>() => G extends A ? 1 : 2) extends <G>() => G extends B ? 1 : 2 ? true : false;
type Filter<KeyType, ExcludeType> =
IsEqual<KeyType, ExcludeType> extends true ? never : KeyType extends ExcludeType ? never : KeyType;
type ExceptOptions = {
requireExactProps?: boolean;
};
export type Except<
ObjectType,
KeysType extends keyof ObjectType,
Options extends ExceptOptions = { requireExactProps: false },
> = {
[KeyType in keyof ObjectType as Filter<KeyType, KeysType>]: ObjectType[KeyType];
} & (Options["requireExactProps"] extends true ? Partial<Record<KeysType, never>> : {});
export type SetOptional<BaseType, Keys extends keyof BaseType> = Simplify<
// Pick just the keys that are readonly from the base type.
Except<BaseType, Keys> &
// Pick the keys that should be mutable from the base type and make them mutable.
Partial<Pick<BaseType, Keys>>
>;
export type SetRequired<BaseType, Keys extends keyof BaseType> = BaseType extends unknown
? Simplify<
// Pick just the keys that are optional from the base type.
Except<BaseType, Keys> &
// Pick the keys that should be required from the base type and make them required.
Required<Pick<BaseType, Keys>>
>
: never;
// The following types are copied from @asteasolutions/zod-to-openapi as they are not exported
export type OpenAPIObjectConfig = Omit<OpenAPIObject, "paths" | "components" | "webhooks">;
export type OpenAPIObjectConfigV31 = Omit<OpenAPIObject, "paths" | "components" | "webhooks">;
type HeadersObject = HeadersObject30 | HeadersObject31;
type LinksObject = LinksObject30 | LinksObject31;
export type ZodMediaType = "application/json" | "text/html" | "text/plain" | "application/xml" | (string & {});
export type ZodContentObject = Partial<Record<ZodMediaType, ZodMediaTypeObject>>;
export interface ZodRequestBody {
description?: string;
content: ZodContentObject;
required?: boolean;
}
export interface ResponseConfig {
description: string;
headers?: AnyZodObject | HeadersObject;
links?: LinksObject;
content?: ZodContentObject;
}
export type RouteParameter = AnyZodObject | ZodPipe<AnyZodObject, any> | undefined;
export interface RouterOptions {
base?: string;
schema?: Partial<OpenAPIObjectConfigV31 | OpenAPIObjectConfig>;
docs_url?: string | null;
redoc_url?: string | null;
openapi_url?: string | null;
raiseUnknownParameters?: boolean;
generateOperationIds?: boolean;
openapiVersion?: "3" | "3.1";
raiseOnError?: boolean;
passthroughErrors?: boolean;
/**
* When enabled, response bodies are parsed through their Zod schema,
* stripping unknown fields and validating required fields/types.
* Validation failures result in a 500 error response and a console.error log.
* Responses without a Zod schema are passed through unchanged.
*/
validateResponse?: boolean;
}
export interface RouteOptions {
router: any;
raiseUnknownParameters: boolean;
raiseOnError?: boolean;
passthroughErrors?: boolean;
validateResponse?: boolean;
route: string;
urlParams: Array<string>;
}
export type RequestTypes = {
body?: ZodRequestBody;
params?: AnyZodObject;
query?: AnyZodObject;
cookies?: AnyZodObject;
headers?: AnyZodObject | ZodType<unknown>[];
};
// Changes over the original RouteConfig:
// - Make responses optional (a default one is generated)
// - Removes method and path (its inject on boot)
export type OpenAPIRouteSchema = Simplify<
Omit<RouteConfig, "responses" | "method" | "path" | "request"> & {
request?: RequestTypes;
responses?: {
[statusCode: string]: ResponseConfig;
};
"x-ignore"?: boolean;
}
>;
export type ValidatedData<S> = S extends OpenAPIRouteSchema
? {
query: GetRequest<S> extends NonNullable<GetRequest<S>> ? GetOutput<GetRequest<S>, "query"> : undefined;
params: GetRequest<S> extends NonNullable<GetRequest<S>> ? GetOutput<GetRequest<S>, "params"> : undefined;
headers: GetRequest<S> extends NonNullable<GetRequest<S>> ? GetOutput<GetRequest<S>, "headers"> : undefined;
body: GetRequest<S> extends NonNullable<GetRequest<S>> ? GetBody<GetPartBody<GetRequest<S>, "body">> : undefined;
}
: {
query: undefined;
params: undefined;
headers: undefined;
body: undefined;
};
type GetRequest<T extends OpenAPIRouteSchema> = T["request"];
type GetOutput<T extends object | undefined, P extends keyof T> =
T extends NonNullable<T> ? (T[P] extends AnyZodObject ? z.output<T[P]> : undefined) : undefined;
type GetPartBody<T extends RequestTypes, P extends keyof T> = T[P] extends ZodRequestBody ? T[P] : undefined;
type GetBody<T extends ZodRequestBody | undefined> =
T extends NonNullable<T>
? T["content"]["application/json"] extends NonNullable<T["content"]["application/json"]>
? T["content"]["application/json"]["schema"] extends z.ZodType
? z.output<T["content"]["application/json"]["schema"]>
: undefined
: undefined
: undefined;