Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/#80 use zod object for headers #85

Merged
merged 2 commits into from
Jan 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ The library specific properties for `registerPath` are `method`, `path`, `reques
- `body` - an object with a `description` and a `content` record where:
- the key is a `mediaType` string like `application/json`
- and the value is an object with a `schema` of any `zod` type
- `headers` - an array of `zod` instances
- `headers` - instances of `ZodObject` or an array of any `zod` instances
- `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:
- the key is a `mediaType` string like `application/json`
- and the value is an object with a `schema` of any `zod` type
Expand Down
35 changes: 33 additions & 2 deletions spec/lib/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { OpenAPIGenerator, OpenApiVersion } from '../../src/openapi-generator';
import {
OpenAPIGenerator,
OpenAPIObjectConfig,
OpenApiVersion,
} from '../../src/openapi-generator';
import type { SchemasObject } from 'openapi3-ts';
import type { ZodSchema } from 'zod';
import { OpenAPIRegistry } from '../../src/openapi-registry';
import { OpenAPIRegistry, RouteConfig } from '../../src/openapi-registry';

export function createSchemas(
zodSchemas: ZodSchema<any>[],
Expand Down Expand Up @@ -38,3 +42,30 @@ export function registerSchema<T extends ZodSchema<any>>(

return registry.register(refId, zodSchema);
}

export function createTestRoute(props: Partial<RouteConfig> = {}): RouteConfig {
return {
method: 'get',
path: '/',
responses: {
200: {
description: 'OK Response',
},
},
...props,
};
}

export const testDocConfig: OpenAPIObjectConfig = {
info: {
version: '1.0.0',
title: 'Swagger Petstore',
description: 'A sample API',
termsOfService: 'http://swagger.io/terms/',
license: {
name: 'Apache 2.0',
url: 'https://www.apache.org/licenses/LICENSE-2.0.html',
},
},
servers: [{ url: 'v1' }],
};
274 changes: 3 additions & 271 deletions spec/routes.spec.ts → spec/routes/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,38 +1,8 @@
import { z, ZodSchema } from 'zod';
import { OperationObject, PathItemObject } from 'openapi3-ts';
import {
OpenAPIGenerator,
OpenAPIObjectConfig,
} from '../src/openapi-generator';
import { OpenAPIRegistry, RouteConfig } from '../src/openapi-registry';
import { registerSchema } from './lib/helpers';

function createTestRoute(props: Partial<RouteConfig> = {}): RouteConfig {
return {
method: 'get',
path: '/',
responses: {
200: {
description: 'OK Response',
},
},
...props,
};
}

const testDocConfig: OpenAPIObjectConfig = {
info: {
version: '1.0.0',
title: 'Swagger Petstore',
description: 'A sample API',
termsOfService: 'http://swagger.io/terms/',
license: {
name: 'Apache 2.0',
url: 'https://www.apache.org/licenses/LICENSE-2.0.html',
},
},
servers: [{ url: 'v1' }],
};
import { OpenAPIGenerator } from '../../src/openapi-generator';
import { OpenAPIRegistry, RouteConfig } from '../../src/openapi-registry';
import { createTestRoute, registerSchema, testDocConfig } from '../lib/helpers';

const routeTests = ({
registerFunction,
Expand Down Expand Up @@ -190,244 +160,6 @@ const routeTests = ({
});
});

describe('parameters', () => {
it('generates a query parameter for route', () => {
const routeParameters = generateParamsForRoute({
request: { query: z.object({ test: z.string() }) },
});

expect(routeParameters).toEqual([
{
in: 'query',
name: 'test',
required: true,
schema: {
type: 'string',
},
},
]);
});

it('generates a path parameter for route', () => {
const routeParameters = generateParamsForRoute({
request: { params: z.object({ test: z.string() }) },
});

expect(routeParameters).toEqual([
{
in: 'path',
name: 'test',
required: true,
schema: {
type: 'string',
},
},
]);
});

it('generates a header parameter for route', () => {
const routeParameters = generateParamsForRoute({
request: {
headers: [z.string().openapi({ param: { name: 'test' } })],
},
});

expect(routeParameters).toEqual([
{
in: 'header',
name: 'test',
required: true,
schema: {
type: 'string',
},
},
]);
});

it('generates a reference header parameter for route', () => {
const TestHeader = registerSchema('TestHeader', z.string()).openapi({
param: { name: 'test', in: 'header' },
});

const routeParameters = generateParamsForRoute(
{
request: { headers: [TestHeader] },
},
[TestHeader]
);

expect(routeParameters).toEqual([
{
$ref: '#/components/parameters/TestHeader',
},
]);
});

it('generates a reference query parameter for route', () => {
const TestQuery = registerSchema('TestQuery', z.string()).openapi({
param: { name: 'test', in: 'query' },
});

const routeParameters = generateParamsForRoute(
{
request: { query: z.object({ test: TestQuery }) },
},
[TestQuery]
);

expect(routeParameters).toEqual([
{
$ref: '#/components/parameters/TestQuery',
},
]);
});

it('generates required based on inner schema', () => {
const routeParameters = generateParamsForRoute({
request: {
query: z.object({ test: z.string().optional().default('test') }),
},
});

expect(routeParameters).toEqual([
{
in: 'query',
name: 'test',
required: false,
schema: {
type: 'string',
default: 'test',
},
},
]);
});

it('supports strict zod objects', () => {
const routeParameters = generateParamsForRoute({
request: {
query: z.strictObject({
test: z.string().optional().default('test'),
}),
},
});

expect(routeParameters).toEqual([
{
in: 'query',
name: 'test',
required: false,
schema: {
type: 'string',
default: 'test',
},
},
]);
});

describe('errors', () => {
it('throws an error in case of names mismatch', () => {
expect(() =>
generateParamsForRoute({
request: {
query: z.object({
test: z.string().openapi({ param: { name: 'another' } }),
}),
},
})
).toThrowError(/^Conflicting name/);
});

it('throws an error in case of location mismatch', () => {
expect(() =>
generateParamsForRoute({
request: {
query: z.object({
test: z.string().openapi({ param: { in: 'header' } }),
}),
},
})
).toThrowError(/^Conflicting location/);
});

it('throws an error in case of location mismatch with reference', () => {
const TestHeader = registerSchema('TestHeader', z.string()).openapi({
param: { name: 'test', in: 'header' },
});

expect(() =>
generateParamsForRoute(
{
request: { query: z.object({ test: TestHeader }) },
},
[TestHeader]
)
).toThrowError(/^Conflicting location/);
});

it('throws an error in case of name mismatch with reference', () => {
const TestQuery = registerSchema('TestQuery', z.string()).openapi({
param: { name: 'test', in: 'query' },
});

expect(() =>
generateParamsForRoute(
{
request: { query: z.object({ randomName: TestQuery }) },
},
[TestQuery]
)
).toThrowError(/^Conflicting name/);
});

it('throws an error in case of missing name', () => {
expect(() =>
generateParamsForRoute({
request: { headers: [z.string()] },
})
).toThrowError(/^Missing parameter data, please specify `name`/);
});

it('throws an error in case of missing location when registering a parameter', () => {
const TestQuery = registerSchema('TestQuery', z.string()).openapi({
param: { name: 'test' },
});

expect(() => generateParamsForRoute({}, [TestQuery])).toThrowError(
/^Missing parameter data, please specify `in`/
);
});
});

function generateParamsForRoute(
props: Partial<RouteConfig> = {},
paramsToRegister?: ZodSchema<any>[]
): OperationObject['parameters'] {
const route = createTestRoute(props);

const paramDefinitions =
paramsToRegister?.map(schema => ({
type: 'parameter' as const,
schema,
})) ?? [];

const routeDefinition = {
type: 'route' as const,
route,
};

const { paths } = new OpenAPIGenerator(
[...paramDefinitions, routeDefinition],
'3.0.0'
).generateDocument(testDocConfig);

const routes = paths[route.path] as PathItemObject;

const routeDoc = routes[route.method];

return routeDoc?.parameters;
}
});

describe('request body', () => {
it('can specify request body metadata - description/required', () => {
const registry = new OpenAPIRegistry();
Expand Down
Loading