Skip to content

Commit

Permalink
Merge pull request #36 from asteasolutions/feature/#31-add-support-fo…
Browse files Browse the repository at this point in the history
…r-multiple-response-media-types

#31 add support for multiple response media types
  • Loading branch information
AGalabov authored Oct 3, 2022
2 parents 3769c19 + 1b73046 commit e29da90
Show file tree
Hide file tree
Showing 13 changed files with 333 additions and 110 deletions.
24 changes: 15 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,12 +205,16 @@ registry.registerPath({
},
responses: {
200: {
mediaType: 'application/json',
schema: UserSchema.openapi({
description: 'Object with user data.',
}),
description: 'Object with user data.',
content: {
'application/json': {
schema: UserSchema,
},
},
},
204: {
description: 'No content - successful operation',
},
204: z.void(),
},
});
```
Expand Down Expand Up @@ -246,11 +250,13 @@ The library specific properties for `registerPath` are `method`, `path`, `reques
- `path` - a string - being the path of the endpoint;
- `request` - an optional object with optional `body`, `params`, `query` and `headers` keys,
- `query`, `params` - being instances of `ZodObject`
- `body` - being any `zod` instance
- `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
- `responses` - an object where the key is the status code or `default` and the value is either:
- an instance of `ZodVoid` - meaning a no content response
- an object with `mediaType` (a string like `application/json`) and a `schema` of any zod type
- `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

#### Defining route parameters

Expand Down
14 changes: 9 additions & 5 deletions example/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,16 @@ registry.registerPath({
},
responses: {
200: {
mediaType: 'application/json',
schema: UserSchema.openapi({
description: 'Object with user data.',
}),
description: 'Object with user data.',
content: {
'application/json': {
schema: UserSchema,
},
},
},
204: {
description: 'No content - successful operation',
},
204: z.void().openapi({ description: 'No content - successful operation' }),
},
});

Expand Down
2 changes: 2 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',

setupFilesAfterEnv: ['<rootDir>/spec/setup-tests.ts'],
};
16 changes: 12 additions & 4 deletions spec/custom-components.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,12 @@ describe('Custom components', () => {
security: [{ [bearerAuth.name]: [] }],
responses: {
200: {
mediaType: 'application/json',
schema: z.string().openapi({ description: 'Sample response' }),
description: 'Sample response',
content: {
'application/json': {
schema: z.string(),
},
},
},
},
});
Expand Down Expand Up @@ -74,9 +78,13 @@ describe('Custom components', () => {
method: 'get',
responses: {
200: {
mediaType: 'application/json',
description: 'Sample response',
headers: { 'x-api-key': apiKeyHeader.ref },
schema: z.string().openapi({ description: 'Sample response' }),
content: {
'application/json': {
schema: z.string(),
},
},
},
},
});
Expand Down
4 changes: 0 additions & 4 deletions spec/polymorphism.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import * as z from 'zod';
import { extendZodWithOpenApi } from '../src/zod-extensions';
import { expectSchema } from './lib/helpers';

// TODO: setupTests.ts
extendZodWithOpenApi(z);

describe('Polymorphism', () => {
it('can use allOf for extended schemas', () => {
const BaseSchema = z.object({ id: z.string() }).openapi({
Expand Down
243 changes: 232 additions & 11 deletions spec/routes.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { z, ZodSchema } from 'zod';
import { OperationObject, PathItemObject } from 'openapi3-ts';
import { OpenAPIGenerator } from '../src/openapi-generator';
import { extendZodWithOpenApi } from '../src/zod-extensions';
import { OpenAPIRegistry, RouteConfig } from '../src/openapi-registry';

function createTestRoute(props: Partial<RouteConfig> = {}): RouteConfig {
Expand All @@ -10,8 +9,7 @@ function createTestRoute(props: Partial<RouteConfig> = {}): RouteConfig {
path: '/',
responses: {
200: {
mediaType: 'application/json',
schema: z.object({}).openapi({ description: 'Response' }),
description: 'OK Response',
},
},
...props,
Expand All @@ -33,27 +31,31 @@ const testDocConfig = {
servers: [{ url: 'v1' }],
};

// TODO: setupTests.ts
extendZodWithOpenApi(z);

describe('Routes', () => {
describe('response definitions', () => {
it('can set description through the response definition or through the schema', () => {
it('can set description', () => {
const registry = new OpenAPIRegistry();

registry.registerPath({
method: 'get',
path: '/',
responses: {
200: {
mediaType: 'application/json',
description: 'Simple response',
schema: z.string(),
content: {
'application/json': {
schema: z.string(),
},
},
},

404: {
mediaType: 'application/json',
schema: z.string().openapi({ description: 'Missing object' }),
description: 'Missing object',
content: {
'application/json': {
schema: z.string(),
},
},
},
},
});
Expand All @@ -66,6 +68,113 @@ describe('Routes', () => {
expect(responses['200'].description).toEqual('Simple response');
expect(responses['404'].description).toEqual('Missing object');
});

it('can specify response with plain OpenApi format', () => {
const registry = new OpenAPIRegistry();

registry.registerPath({
method: 'get',
path: '/',
responses: {
200: {
description: 'Simple response',
content: {
'application/json': {
schema: {
type: 'string',
example: 'test',
},
},
},
},

404: {
description: 'Missing object',
content: {
'application/json': {
schema: {
$ref: '#/components/schemas/SomeRef',
},
},
},
},
},
});

const document = new OpenAPIGenerator(
registry.definitions
).generateDocument(testDocConfig);
const responses = document.paths['/'].get.responses;

expect(responses['200'].content['application/json'].schema).toEqual({
type: 'string',
example: 'test',
});
expect(responses['404'].content['application/json'].schema).toEqual({
$ref: '#/components/schemas/SomeRef',
});
});

it('can set multiple response formats', () => {
const registry = new OpenAPIRegistry();

const UserSchema = registry.register(
'User',
z.object({ name: z.string() })
);

registry.registerPath({
method: 'get',
path: '/',
responses: {
200: {
description: 'Simple response',
content: {
'application/json': {
schema: UserSchema,
},
'application/xml': {
schema: UserSchema,
},
},
},
},
});

const document = new OpenAPIGenerator(
registry.definitions
).generateDocument(testDocConfig);
const responses = document.paths['/'].get.responses;

expect(responses['200'].description).toEqual('Simple response');
expect(responses['200'].content['application/json'].schema).toEqual({
$ref: '#/components/schemas/User',
});
expect(responses['200'].content['application/xml'].schema).toEqual({
$ref: '#/components/schemas/User',
});
});

it('can generate responses without content', () => {
const registry = new OpenAPIRegistry();

registry.registerPath({
method: 'get',
path: '/',
responses: {
204: {
description: 'Success',
},
},
});

const document = new OpenAPIGenerator(
registry.definitions
).generateDocument(testDocConfig);
const responses = document.paths['/'].get.responses;

expect(responses['204']).toEqual({ description: 'Success' });
});
});

describe('parameters', () => {
Expand Down Expand Up @@ -265,4 +374,116 @@ describe('Routes', () => {
return routeDoc?.parameters;
}
});

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

const route = createTestRoute({
request: {
body: {
description: 'Test description',
required: true,
content: {
'application/json': {
schema: z.string(),
},
},
},
},
});

registry.registerPath(route);

const document = new OpenAPIGenerator(
registry.definitions
).generateDocument(testDocConfig);

const { requestBody } = document.paths['/'].get;

expect(requestBody).toEqual({
description: 'Test description',
required: true,
content: { 'application/json': { schema: { type: 'string' } } },
});
});

it('can specify request body using plain OpenApi format', () => {
const registry = new OpenAPIRegistry();

const route = createTestRoute({
request: {
body: {
content: {
'application/json': {
schema: {
type: 'string',
enum: ['test'],
},
},
'application/xml': {
schema: { $ref: '#/components/schemas/SomeRef' },
},
},
},
},
});

registry.registerPath(route);

const document = new OpenAPIGenerator(
registry.definitions
).generateDocument(testDocConfig);

const requestBody = document.paths['/'].get.requestBody.content;

expect(requestBody['application/json']).toEqual({
schema: { type: 'string', enum: ['test'] },
});

expect(requestBody['application/xml']).toEqual({
schema: { $ref: '#/components/schemas/SomeRef' },
});
});

it('can have multiple media format bodies', () => {
const registry = new OpenAPIRegistry();

const UserSchema = registry.register(
'User',
z.object({ name: z.string() })
);

const route = createTestRoute({
request: {
body: {
content: {
'application/json': {
schema: z.string(),
},
'application/xml': {
schema: UserSchema,
},
},
},
},
});

registry.registerPath(route);

const document = new OpenAPIGenerator(
registry.definitions
).generateDocument(testDocConfig);

const requestBody = document.paths['/'].get.requestBody.content;

expect(requestBody['application/json']).toEqual({
schema: { type: 'string' },
});

expect(requestBody['application/xml']).toEqual({
schema: { $ref: '#/components/schemas/User' },
});
});
});
});
Loading

0 comments on commit e29da90

Please sign in to comment.