diff --git a/packages/openapi-generator/src/openapi.ts b/packages/openapi-generator/src/openapi.ts index 15d39626..bb08a3bc 100644 --- a/packages/openapi-generator/src/openapi.ts +++ b/packages/openapi-generator/src/openapi.ts @@ -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'; const knownTags = new Set([ 'operationId', @@ -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]) => { @@ -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) }, }, }, }; @@ -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), }, diff --git a/packages/openapi-generator/test/openapi/misc.test.ts b/packages/openapi-generator/test/openapi/misc.test.ts index 801e5190..34956591 100644 --- a/packages/openapi-generator/test/openapi/misc.test.ts +++ b/packages/openapi-generator/test/openapi/misc.test.ts @@ -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: {} }, +});