Skip to content
Draft
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
25 changes: 25 additions & 0 deletions packages/openapi-generator/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -472,9 +472,13 @@ These are some tags that you can use in your schema JSDocs are custom to this ge
will have `x-internal: true` for schemas with the `@private` tag.
- `@deprecated` allows to mark any field in any schema as deprecated. The final spec
will include `deprecated: true` in the final specificaiton.
- `@contentType` allows you to override the default `application/json` content type for
requests and responses. Can be applied at route level (affects both request and
response), request body level, or individual response status code level.

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

const Schema = t.type({
/** @private */
Expand All @@ -483,4 +487,25 @@ const Schema = t.type({
deprecatedField: t.string,
publicNonDeprecatedField: t.string,
});

/**
* Route-level content type
* @contentType multipart/form-data
*/
export const uploadRoute = h.httpRoute({
request: h.httpRequest({
/**
* Request-specific content type
* @contentType application/xml
*/
body: t.type({ data: t.string }),
}),
response: {
/**
* Response-specific content type
* @contentType text/plain
*/
200: t.string,
},
});
```
30 changes: 26 additions & 4 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';

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 @@ -344,9 +346,20 @@ function routeToOpenAPI(route: Route): [string, string, OpenAPIV3.OperationObjec
? {}
: {
requestBody: {
content: {
'application/json': { schema: schemaToOpenAPI(route.body) },
},
content: (() => {
const emptyBlock: Block = {
description: '',
tags: [],
source: [],
problems: [],
};
const bodyJsdoc = parseCommentBlock(route.body.comment ?? emptyBlock);
const requestContentType = bodyJsdoc.tags?.contentType ?? contentType;

return {
[requestContentType]: { schema: schemaToOpenAPI(route.body) },
};
})(),
},
};

Expand Down Expand Up @@ -392,12 +405,21 @@ function routeToOpenAPI(route: Route): [string, string, OpenAPIV3.OperationObjec
responses: Object.entries(route.response).reduce((acc, [code, response]) => {
const description = STATUS_CODES[code] ?? '';

const emptyBlock: Block = {
description: '',
tags: [],
source: [],
problems: [],
};
const responseJsdoc = parseCommentBlock(response.comment ?? emptyBlock);
const responseContentType = responseJsdoc.tags?.contentType ?? contentType;

return {
...acc,
[Number(code)]: {
description,
content: {
'application/json': {
[responseContentType]: {
schema: schemaToOpenAPI(response),
...(example !== undefined ? { example } : undefined),
},
Expand Down
Loading
Loading