turtle-express
Version:
`turtle-express` is kinda a framework or a library based on `express.js` with an opinionated express router with type safety and schema validation with zod. Also many [more features](https://github.com/mm-ninja-turtles/turtle-express/discussions/7) planni
191 lines (169 loc) • 4.54 kB
text/typescript
import type { ZodType } from 'zod'
import type { OpenAPIV3_1 } from 'openapi-types'
import type { ExtendsZodWithOpenApiReturnType } from './zod-extends-openapi'
import { z } from 'zod'
import { chain } from 'lodash-es'
/**
* Function to convert each ZodType schema to OpenAPI Request, Parameter, Response schemas.
*/
export function zodToOpenApiJson<
Schema extends ExtendsZodWithOpenApiReturnType<ZodType>,
>(
schema: Schema,
):
| OpenAPIV3_1.ResponseObject
| OpenAPIV3_1.ParameterObject[]
| OpenAPIV3_1.RequestBodyObject {
const { type } = schema._openApi
const openApiJsonElement = createOpenApiJsonElement(schema)
if (type === 'Response') {
const mediaType = schema._openApi.mediaType ?? 'application/json'
const response: OpenAPIV3_1.ResponseObject = {
description: openApiJsonElement.description ?? '',
content: {
[mediaType]: {
schema: openApiJsonElement,
},
},
}
return response
}
if (type === 'RequestQuery' || type === 'RequestParam') {
const parameters: OpenAPIV3_1.ParameterObject[] = []
Object.keys(openApiJsonElement?.properties ?? {}).forEach((key) => {
parameters.push({
name: key,
in: type === 'RequestQuery' ? 'query' : 'path',
schema: openApiJsonElement?.properties?.[key] as any,
})
})
return parameters
}
if (type === 'RequestBody') {
const mediaType = schema._openApi.mediaType ?? 'application/json'
const requestBody: OpenAPIV3_1.RequestBodyObject = {
description: openApiJsonElement.description ?? '',
content: {
[mediaType]: {
schema: openApiJsonElement,
},
},
}
return requestBody
}
return openApiJsonElement
}
/**
* @Credit https://github.com/kbkk/abitia/blob/master/packages/zod-dto/src/OpenApi/zodTypeToOpenApi.ts
* The following function code is inspired and reused some from the above package.
*
* Return the OpenAPI schema for a Zod type.
*/
export function createOpenApiJsonElement<
Schema extends ExtendsZodWithOpenApiReturnType<z.ZodTypeAny>,
>(schema: Schema): any {
const schemaDef = schema._def
function createOpenApi(extendSchema: any) {
const title = schema?._openApi?.title ?? ''
const type = schema?._openApi?.type ?? ''
const transformedTitle = chain(`${title} ${type}`)
.camelCase()
.upperFirst()
.value()
return {
required: true,
...extendSchema,
operationId: transformedTitle ?? '',
title: transformedTitle ?? '',
description: schemaDef?.description ?? '',
}
}
switch (schema.constructor.name) {
case z.ZodObject.name: {
const shape = schemaDef.shape()
const shapeKeys = Object.keys(shape)
const properties = {} as { [key: string]: unknown }
shapeKeys.forEach((key) => {
const property = createOpenApiJsonElement(shape[key])
properties[key] = property
})
return createOpenApi({
type: 'object',
properties,
})
}
case z.ZodOptional.name: {
return createOpenApi({
...createOpenApiJsonElement(schemaDef.innerType),
required: false,
})
}
case z.ZodNullable.name: {
return createOpenApi({
...createOpenApiJsonElement(schemaDef.innerType),
nullable: true,
})
}
case z.ZodTransformer.name: {
return createOpenApi({
...createOpenApiJsonElement(schemaDef.schema),
})
}
case z.ZodEnum.name: {
const enumValues = schemaDef.values
return createOpenApi({
type: 'string',
enum: enumValues,
})
}
case z.ZodLiteral.name: {
return createOpenApi({
type: 'string',
enum: [schemaDef.value],
})
}
case z.ZodUnion.name: {
const options = schemaDef.options
return createOpenApi({
oneOf: options.map((item: any) => createOpenApiJsonElement(item)),
})
}
case z.ZodTuple.name: {
const items = schemaDef.items
return createOpenApi({
type: 'array',
items: {
oneOf: items.map((item: any) => createOpenApiJsonElement(item)),
},
minItems: items.length,
maxItems: items.length,
})
}
case z.ZodArray.name: {
const type = schemaDef.type
return createOpenApi({
type: 'array',
items: type ? createOpenApiJsonElement(type) : {},
})
}
case z.ZodBigInt.name: {
return createOpenApi({
type: 'integer',
format: 'int64',
})
}
case z.ZodString.name: {
return createOpenApi({ type: 'string' })
}
case z.ZodNumber.name: {
return createOpenApi({ type: 'number' })
}
case z.ZodBoolean.name: {
return createOpenApi({ type: 'boolean' })
}
case z.ZodDefault.name:
default: {
return createOpenApi({ type: 'string' })
}
}
}