Skip to content

Commit da45b21

Browse files
authored
Merge pull request #85 from asteasolutions/feature/#80-use-zod-object-for-headers
Feature/#80 use zod object for headers
2 parents 43aabc5 + c972d9b commit da45b21

File tree

6 files changed

+308
-279
lines changed

6 files changed

+308
-279
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,7 @@ The library specific properties for `registerPath` are `method`, `path`, `reques
286286
- `body` - an object with a `description` and a `content` record where:
287287
- the key is a `mediaType` string like `application/json`
288288
- and the value is an object with a `schema` of any `zod` type
289-
- `headers` - an array of `zod` instances
289+
- `headers` - instances of `ZodObject` or an array of any `zod` instances
290290
- `responses` - an object where the key is the status code or `default` and the value is an object with a `description` and a `content` record where:
291291
- the key is a `mediaType` string like `application/json`
292292
- and the value is an object with a `schema` of any `zod` type

spec/lib/helpers.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1-
import { OpenAPIGenerator, OpenApiVersion } from '../../src/openapi-generator';
1+
import {
2+
OpenAPIGenerator,
3+
OpenAPIObjectConfig,
4+
OpenApiVersion,
5+
} from '../../src/openapi-generator';
26
import type { SchemasObject } from 'openapi3-ts';
37
import type { ZodSchema } from 'zod';
4-
import { OpenAPIRegistry } from '../../src/openapi-registry';
8+
import { OpenAPIRegistry, RouteConfig } from '../../src/openapi-registry';
59

610
export function createSchemas(
711
zodSchemas: ZodSchema<any>[],
@@ -38,3 +42,30 @@ export function registerSchema<T extends ZodSchema<any>>(
3842

3943
return registry.register(refId, zodSchema);
4044
}
45+
46+
export function createTestRoute(props: Partial<RouteConfig> = {}): RouteConfig {
47+
return {
48+
method: 'get',
49+
path: '/',
50+
responses: {
51+
200: {
52+
description: 'OK Response',
53+
},
54+
},
55+
...props,
56+
};
57+
}
58+
59+
export const testDocConfig: OpenAPIObjectConfig = {
60+
info: {
61+
version: '1.0.0',
62+
title: 'Swagger Petstore',
63+
description: 'A sample API',
64+
termsOfService: 'http://swagger.io/terms/',
65+
license: {
66+
name: 'Apache 2.0',
67+
url: 'https://www.apache.org/licenses/LICENSE-2.0.html',
68+
},
69+
},
70+
servers: [{ url: 'v1' }],
71+
};
Lines changed: 3 additions & 271 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,8 @@
11
import { z, ZodSchema } from 'zod';
22
import { OperationObject, PathItemObject } from 'openapi3-ts';
3-
import {
4-
OpenAPIGenerator,
5-
OpenAPIObjectConfig,
6-
} from '../src/openapi-generator';
7-
import { OpenAPIRegistry, RouteConfig } from '../src/openapi-registry';
8-
import { registerSchema } from './lib/helpers';
9-
10-
function createTestRoute(props: Partial<RouteConfig> = {}): RouteConfig {
11-
return {
12-
method: 'get',
13-
path: '/',
14-
responses: {
15-
200: {
16-
description: 'OK Response',
17-
},
18-
},
19-
...props,
20-
};
21-
}
22-
23-
const testDocConfig: OpenAPIObjectConfig = {
24-
info: {
25-
version: '1.0.0',
26-
title: 'Swagger Petstore',
27-
description: 'A sample API',
28-
termsOfService: 'http://swagger.io/terms/',
29-
license: {
30-
name: 'Apache 2.0',
31-
url: 'https://www.apache.org/licenses/LICENSE-2.0.html',
32-
},
33-
},
34-
servers: [{ url: 'v1' }],
35-
};
3+
import { OpenAPIGenerator } from '../../src/openapi-generator';
4+
import { OpenAPIRegistry, RouteConfig } from '../../src/openapi-registry';
5+
import { createTestRoute, registerSchema, testDocConfig } from '../lib/helpers';
366

377
const routeTests = ({
388
registerFunction,
@@ -190,244 +160,6 @@ const routeTests = ({
190160
});
191161
});
192162

193-
describe('parameters', () => {
194-
it('generates a query parameter for route', () => {
195-
const routeParameters = generateParamsForRoute({
196-
request: { query: z.object({ test: z.string() }) },
197-
});
198-
199-
expect(routeParameters).toEqual([
200-
{
201-
in: 'query',
202-
name: 'test',
203-
required: true,
204-
schema: {
205-
type: 'string',
206-
},
207-
},
208-
]);
209-
});
210-
211-
it('generates a path parameter for route', () => {
212-
const routeParameters = generateParamsForRoute({
213-
request: { params: z.object({ test: z.string() }) },
214-
});
215-
216-
expect(routeParameters).toEqual([
217-
{
218-
in: 'path',
219-
name: 'test',
220-
required: true,
221-
schema: {
222-
type: 'string',
223-
},
224-
},
225-
]);
226-
});
227-
228-
it('generates a header parameter for route', () => {
229-
const routeParameters = generateParamsForRoute({
230-
request: {
231-
headers: [z.string().openapi({ param: { name: 'test' } })],
232-
},
233-
});
234-
235-
expect(routeParameters).toEqual([
236-
{
237-
in: 'header',
238-
name: 'test',
239-
required: true,
240-
schema: {
241-
type: 'string',
242-
},
243-
},
244-
]);
245-
});
246-
247-
it('generates a reference header parameter for route', () => {
248-
const TestHeader = registerSchema('TestHeader', z.string()).openapi({
249-
param: { name: 'test', in: 'header' },
250-
});
251-
252-
const routeParameters = generateParamsForRoute(
253-
{
254-
request: { headers: [TestHeader] },
255-
},
256-
[TestHeader]
257-
);
258-
259-
expect(routeParameters).toEqual([
260-
{
261-
$ref: '#/components/parameters/TestHeader',
262-
},
263-
]);
264-
});
265-
266-
it('generates a reference query parameter for route', () => {
267-
const TestQuery = registerSchema('TestQuery', z.string()).openapi({
268-
param: { name: 'test', in: 'query' },
269-
});
270-
271-
const routeParameters = generateParamsForRoute(
272-
{
273-
request: { query: z.object({ test: TestQuery }) },
274-
},
275-
[TestQuery]
276-
);
277-
278-
expect(routeParameters).toEqual([
279-
{
280-
$ref: '#/components/parameters/TestQuery',
281-
},
282-
]);
283-
});
284-
285-
it('generates required based on inner schema', () => {
286-
const routeParameters = generateParamsForRoute({
287-
request: {
288-
query: z.object({ test: z.string().optional().default('test') }),
289-
},
290-
});
291-
292-
expect(routeParameters).toEqual([
293-
{
294-
in: 'query',
295-
name: 'test',
296-
required: false,
297-
schema: {
298-
type: 'string',
299-
default: 'test',
300-
},
301-
},
302-
]);
303-
});
304-
305-
it('supports strict zod objects', () => {
306-
const routeParameters = generateParamsForRoute({
307-
request: {
308-
query: z.strictObject({
309-
test: z.string().optional().default('test'),
310-
}),
311-
},
312-
});
313-
314-
expect(routeParameters).toEqual([
315-
{
316-
in: 'query',
317-
name: 'test',
318-
required: false,
319-
schema: {
320-
type: 'string',
321-
default: 'test',
322-
},
323-
},
324-
]);
325-
});
326-
327-
describe('errors', () => {
328-
it('throws an error in case of names mismatch', () => {
329-
expect(() =>
330-
generateParamsForRoute({
331-
request: {
332-
query: z.object({
333-
test: z.string().openapi({ param: { name: 'another' } }),
334-
}),
335-
},
336-
})
337-
).toThrowError(/^Conflicting name/);
338-
});
339-
340-
it('throws an error in case of location mismatch', () => {
341-
expect(() =>
342-
generateParamsForRoute({
343-
request: {
344-
query: z.object({
345-
test: z.string().openapi({ param: { in: 'header' } }),
346-
}),
347-
},
348-
})
349-
).toThrowError(/^Conflicting location/);
350-
});
351-
352-
it('throws an error in case of location mismatch with reference', () => {
353-
const TestHeader = registerSchema('TestHeader', z.string()).openapi({
354-
param: { name: 'test', in: 'header' },
355-
});
356-
357-
expect(() =>
358-
generateParamsForRoute(
359-
{
360-
request: { query: z.object({ test: TestHeader }) },
361-
},
362-
[TestHeader]
363-
)
364-
).toThrowError(/^Conflicting location/);
365-
});
366-
367-
it('throws an error in case of name mismatch with reference', () => {
368-
const TestQuery = registerSchema('TestQuery', z.string()).openapi({
369-
param: { name: 'test', in: 'query' },
370-
});
371-
372-
expect(() =>
373-
generateParamsForRoute(
374-
{
375-
request: { query: z.object({ randomName: TestQuery }) },
376-
},
377-
[TestQuery]
378-
)
379-
).toThrowError(/^Conflicting name/);
380-
});
381-
382-
it('throws an error in case of missing name', () => {
383-
expect(() =>
384-
generateParamsForRoute({
385-
request: { headers: [z.string()] },
386-
})
387-
).toThrowError(/^Missing parameter data, please specify `name`/);
388-
});
389-
390-
it('throws an error in case of missing location when registering a parameter', () => {
391-
const TestQuery = registerSchema('TestQuery', z.string()).openapi({
392-
param: { name: 'test' },
393-
});
394-
395-
expect(() => generateParamsForRoute({}, [TestQuery])).toThrowError(
396-
/^Missing parameter data, please specify `in`/
397-
);
398-
});
399-
});
400-
401-
function generateParamsForRoute(
402-
props: Partial<RouteConfig> = {},
403-
paramsToRegister?: ZodSchema<any>[]
404-
): OperationObject['parameters'] {
405-
const route = createTestRoute(props);
406-
407-
const paramDefinitions =
408-
paramsToRegister?.map(schema => ({
409-
type: 'parameter' as const,
410-
schema,
411-
})) ?? [];
412-
413-
const routeDefinition = {
414-
type: 'route' as const,
415-
route,
416-
};
417-
418-
const { paths } = new OpenAPIGenerator(
419-
[...paramDefinitions, routeDefinition],
420-
'3.0.0'
421-
).generateDocument(testDocConfig);
422-
423-
const routes = paths[route.path] as PathItemObject;
424-
425-
const routeDoc = routes[route.method];
426-
427-
return routeDoc?.parameters;
428-
}
429-
});
430-
431163
describe('request body', () => {
432164
it('can specify request body metadata - description/required', () => {
433165
const registry = new OpenAPIRegistry();

0 commit comments

Comments
 (0)