Skip to content

Commit

Permalink
Support ZodEffects (#211)
Browse files Browse the repository at this point in the history
  • Loading branch information
samchungy authored Dec 16, 2024
1 parent b0f7c94 commit 2dcdeb4
Show file tree
Hide file tree
Showing 4 changed files with 175 additions and 21 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,14 +66,14 @@
"fastify": "5.2.0",
"skuba": "9.1.0",
"zod": "3.24.1",
"zod-openapi": "4.1.0"
"zod-openapi": "4.2.0"
},
"peerDependencies": {
"@fastify/swagger": "^9.0.0",
"@fastify/swagger-ui": "^5.0.1",
"fastify": "5",
"zod": "^3.21.4",
"zod-openapi": ">=3.2.0 <5.0.0"
"zod-openapi": "^4.2.0"
},
"peerDependenciesMeta": {
"@fastify/swagger": {
Expand Down
10 changes: 5 additions & 5 deletions pnpm-lock.yaml

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

144 changes: 144 additions & 0 deletions src/transformer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,150 @@ describe('fastifyZodOpenApiTransform', () => {
`);
});

it('should support creating parameters using Zod Effects', async () => {
const app = fastify();

app.setValidatorCompiler(validatorCompiler);

await app.register(fastifyZodOpenApiPlugin);
await app.register(fastifySwagger, {
openapi: {
info: {
title: 'hello world',
version: '1.0.0',
},
openapi: '3.1.0',
},
transform: fastifyZodOpenApiTransform,
});
await app.register(fastifySwaggerUI, {
routePrefix: '/documentation',
});

app.withTypeProvider<FastifyZodOpenApiTypeProvider>().post(
'/',
{
schema: {
body: z
.object({
jobId: z.string().openapi({
description: 'Job ID',
example: '60002023',
}),
})
.refine(() => true),
querystring: z
.object({
jobId: z.string().openapi({
description: 'Job ID',
example: '60002023',
}),
})
.refine(() => true),
params: z
.object({
jobId: z.string().openapi({
description: 'Job ID',
example: '60002023',
}),
})
.refine(() => true),
headers: z
.object({
jobId: z.string().openapi({
description: 'Job ID',
example: '60002023',
}),
})
.refine(() => true),
} satisfies FastifyZodOpenApiSchema,
},
async (_req, res) =>
res.send({
jobId: '60002023',
}),
);
await app.ready();

const result = await app.inject().get('/documentation/json');

expect(result.json()).toMatchInlineSnapshot(`
{
"components": {
"schemas": {},
},
"info": {
"title": "hello world",
"version": "1.0.0",
},
"openapi": "3.1.0",
"paths": {
"/": {
"post": {
"parameters": [
{
"description": "Job ID",
"in": "query",
"name": "jobId",
"required": true,
"schema": {
"example": "60002023",
"type": "string",
},
},
{
"description": "Job ID",
"in": "path",
"name": "jobId",
"required": true,
"schema": {
"example": "60002023",
"type": "string",
},
},
{
"description": "Job ID",
"in": "header",
"name": "jobId",
"required": true,
"schema": {
"example": "60002023",
"type": "string",
},
},
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"properties": {
"jobId": {
"description": "Job ID",
"example": "60002023",
"type": "string",
},
},
"required": [
"jobId",
],
"type": "object",
},
},
},
"required": true,
},
"responses": {
"200": {
"description": "Default Response",
},
},
},
},
},
}
`);
});

it('should support creating an openapi header parameter', async () => {
const app = fastify();

Expand Down
38 changes: 24 additions & 14 deletions src/transformer.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { FastifyDynamicSwaggerOptions } from '@fastify/swagger';
import type { FastifySchema } from 'fastify';
import type { OpenAPIV3 } from 'openapi-types';
import type { AnyZodObject, ZodObject, ZodRawShape, ZodType } from 'zod';
import type { ZodObject, ZodRawShape, ZodType } from 'zod';
import type {
CreateDocumentOptions,
ZodObjectInputType,
ZodOpenApiComponentsObject,
ZodOpenApiParameters,
ZodOpenApiResponsesObject,
Expand All @@ -15,6 +16,7 @@ import {
createComponents,
createMediaTypeSchema,
createParamOrRef,
getZodObject,
} from 'zod-openapi/api';

import {
Expand All @@ -37,10 +39,10 @@ export type FastifyZodOpenApiSchema = Omit<
'response' | 'headers' | 'querystring' | 'body' | 'params'
> & {
response?: ZodOpenApiResponsesObject;
headers?: AnyZodObject;
querystring?: AnyZodObject;
body?: AnyZodObject;
params?: AnyZodObject;
headers?: ZodObjectInputType;
querystring?: ZodObjectInputType;
body?: ZodObjectInputType;
params?: ZodObjectInputType;
};

export const isZodType = (object: unknown): object is ZodType =>
Expand Down Expand Up @@ -244,28 +246,36 @@ export const fastifyZodOpenApiTransform: Transform = ({
transformedSchema.response = maybeResponse;
}

if (isZodObject(querystring)) {
if (isZodType(querystring)) {
const queryStringSchema = getZodObject(
querystring as ZodObjectInputType,
'input',
);
transformedSchema.querystring = createParams(
querystring,
queryStringSchema,
'query',
components,
[url, 'querystring'],
documentOpts,
);
}

if (isZodObject(params)) {
transformedSchema.params = createParams(params, 'path', components, [
if (isZodType(params)) {
const paramsSchema = getZodObject(params as ZodObjectInputType, 'input');
transformedSchema.params = createParams(paramsSchema, 'path', components, [
url,
'params',
]);
}

if (isZodObject(headers)) {
transformedSchema.headers = createParams(headers, 'header', components, [
url,
'headers',
]);
if (isZodType(headers)) {
const headersSchema = getZodObject(headers as ZodObjectInputType, 'input');
transformedSchema.headers = createParams(
headersSchema,
'header',
components,
[url, 'headers'],
);
}

return {
Expand Down

0 comments on commit 2dcdeb4

Please sign in to comment.