Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
6 changes: 4 additions & 2 deletions packages/openapi-generator/src/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ function routeToOpenAPI(route: Route): [string, string, OpenAPIV3.OperationObjec
const isInternal = jsdoc.tags?.private !== undefined;
const isUnstable = jsdoc.tags?.unstable !== undefined;
const example = jsdoc.tags?.example;
const contentType = jsdoc.tags?.contentType ?? 'application/json';
Comment thread
koralkulacoglu marked this conversation as resolved.

const knownTags = new Set([
'operationId',
Expand All @@ -328,6 +329,7 @@ function routeToOpenAPI(route: Route): [string, string, OpenAPIV3.OperationObjec
'tag',
'description',
'url',
'contentType',
]);
const unknownTagsObject = Object.entries(jsdoc.tags ?? {}).reduce(
(acc, [key, value]) => {
Expand All @@ -345,7 +347,7 @@ function routeToOpenAPI(route: Route): [string, string, OpenAPIV3.OperationObjec
: {
requestBody: {
content: {
'application/json': { schema: schemaToOpenAPI(route.body) },
[contentType]: { schema: schemaToOpenAPI(route.body) },
},
},
};
Expand Down Expand Up @@ -397,7 +399,7 @@ function routeToOpenAPI(route: Route): [string, string, OpenAPIV3.OperationObjec
[Number(code)]: {
description,
content: {
'application/json': {
[contentType]: {
schema: schemaToOpenAPI(response),
...(example !== undefined ? { example } : undefined),
},
Expand Down
141 changes: 141 additions & 0 deletions packages/openapi-generator/test/openapi/misc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,3 +287,144 @@ testCase('route with record types', ROUTE_WITH_RECORD_TYPES, {
},
},
});

const CONTENT_TYPE_TEST = `
import * as t from 'io-ts';
import * as h from '@api-ts/io-ts-http';

/**
* Route with multipart/form-data content type
*
* @contentType multipart/form-data
* @operationId api.v1.uploadDocument
* @tag Document Upload
*/
export const uploadRoute = h.httpRoute({
path: '/upload',
method: 'POST',
request: h.httpRequest({
body: t.type({
file: t.unknown,
documentType: t.string,
}),
}),
response: {
201: t.type({
id: t.string,
success: t.boolean,
}),
},
});

/**
* Route with default application/json content type
*
* @operationId api.v1.createUser
* @tag User Management
*/
export const createUserRoute = h.httpRoute({
path: '/users',
method: 'POST',
request: h.httpRequest({
body: t.type({
name: t.string,
email: t.string,
}),
}),
response: {
201: t.type({
id: t.string,
name: t.string,
}),
},
});
`;

testCase('route with contentType tag uses multipart/form-data', CONTENT_TYPE_TEST, {
openapi: '3.0.3',
info: {
title: 'Test',
version: '1.0.0',
},
paths: {
'/upload': {
post: {
summary: 'Route with multipart/form-data content type',
operationId: 'api.v1.uploadDocument',
tags: ['Document Upload'],
parameters: [],
requestBody: {
content: {
'multipart/form-data': {
schema: {
type: 'object',
properties: {
file: {},
documentType: { type: 'string' },
},
required: ['file', 'documentType'],
},
},
},
},
responses: {
201: {
description: 'Created',
content: {
'multipart/form-data': {
schema: {
type: 'object',
properties: {
id: { type: 'string' },
success: { type: 'boolean' },
},
required: ['id', 'success'],
},
},
},
},
},
},
},
'/users': {
post: {
summary: 'Route with default application/json content type',
operationId: 'api.v1.createUser',
tags: ['User Management'],
parameters: [],
requestBody: {
content: {
'application/json': {
schema: {
type: 'object',
properties: {
name: { type: 'string' },
email: { type: 'string' },
},
required: ['name', 'email'],
},
},
},
},
responses: {
201: {
description: 'Created',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
id: { type: 'string' },
name: { type: 'string' },
},
required: ['id', 'name'],
},
},
},
},
},
},
},
},
components: { schemas: {} },
});