Skip to content

Commit 35ee662

Browse files
authored
Merge pull request #773 from BitGo/DX-427-inline-descriptions
feat(openapi-generator): add support for inline type descriptions
2 parents 5336a24 + bdb071a commit 35ee662

File tree

2 files changed

+158
-5
lines changed

2 files changed

+158
-5
lines changed

packages/openapi-generator/src/openapi.ts

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,27 @@ function schemaToOpenAPI(
1414
const createOpenAPIObject = (
1515
schema: Schema,
1616
): OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined => {
17+
const description = schema.comment?.description;
18+
19+
const defaultObject = {
20+
...(description ? { description } : {}),
21+
};
22+
1723
switch (schema.type) {
1824
case 'boolean':
1925
case 'string':
2026
case 'number':
21-
return { type: schema.type, ...(schema.enum ? { enum: schema.enum } : {}) };
27+
return {
28+
type: schema.type,
29+
...(schema.enum ? { enum: schema.enum } : {}),
30+
...defaultObject,
31+
};
2232
case 'integer':
23-
return { type: 'number', ...(schema.enum ? { enum: schema.enum } : {}) };
33+
return {
34+
type: 'number',
35+
...(schema.enum ? { enum: schema.enum } : {}),
36+
...defaultObject,
37+
};
2438
case 'null':
2539
// TODO: OpenAPI v3 does not have an explicit null type, is there a better way to represent this?
2640
// Or should we just conflate explicit null and undefined properties?
@@ -32,7 +46,7 @@ function schemaToOpenAPI(
3246
if (innerSchema === undefined) {
3347
return undefined;
3448
}
35-
return { type: 'array', items: innerSchema };
49+
return { type: 'array', items: innerSchema, ...defaultObject };
3650
case 'object':
3751
return {
3852
type: 'object',
@@ -146,13 +160,25 @@ function routeToOpenAPI(route: Route): [string, string, OpenAPIV3.OperationObjec
146160
{},
147161
);
148162

163+
const stripTopLevelComment = (schema: Schema) => {
164+
const copy = { ...schema };
165+
166+
if (copy.comment?.description !== undefined && copy.comment?.description !== '') {
167+
copy.comment.description = '';
168+
}
169+
170+
return copy;
171+
};
172+
173+
const topLevelStripped = stripTopLevelComment(route.body!);
174+
149175
const requestBody =
150176
route.body === undefined
151177
? {}
152178
: {
153179
requestBody: {
154180
content: {
155-
'application/json': { schema: schemaToOpenAPI(route.body) },
181+
'application/json': { schema: schemaToOpenAPI(topLevelStripped) },
156182
},
157183
},
158184
};
@@ -174,6 +200,10 @@ function routeToOpenAPI(route: Route): [string, string, OpenAPIV3.OperationObjec
174200
// Array types not allowed here
175201
const schema = schemaToOpenAPI(p.schema);
176202

203+
if (schema && 'description' in schema) {
204+
delete schema.description;
205+
}
206+
177207
return {
178208
name: p.name,
179209
...(p.schema?.comment?.description !== undefined
@@ -195,7 +225,7 @@ function routeToOpenAPI(route: Route): [string, string, OpenAPIV3.OperationObjec
195225
description,
196226
content: {
197227
'application/json': {
198-
schema: schemaToOpenAPI(response),
228+
schema: schemaToOpenAPI(stripTopLevelComment(response)),
199229
...(example !== undefined ? { example } : undefined),
200230
},
201231
},

packages/openapi-generator/test/openapi.test.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1331,3 +1331,126 @@ testCase('route with multiple unknown tags', ROUTE_WITH_MULTIPLE_UNKNOWN_TAGS, {
13311331
schemas: {},
13321332
},
13331333
});
1334+
1335+
1336+
const ROUTE_WITH_TYPE_DESCRIPTIONS = `
1337+
import * as t from 'io-ts';
1338+
import * as h from '@api-ts/io-ts-http';
1339+
1340+
/**
1341+
* A simple route with type descriptions
1342+
*
1343+
* @operationId api.v1.test
1344+
* @tag Test Routes
1345+
*/
1346+
export const route = h.httpRoute({
1347+
path: '/foo',
1348+
method: 'GET',
1349+
request: h.httpRequest({
1350+
query: {
1351+
/** bar param */
1352+
bar: t.string,
1353+
},
1354+
body: {
1355+
/** foo description */
1356+
foo: t.string,
1357+
/** bar description */
1358+
bar: t.number,
1359+
child: {
1360+
/** child description */
1361+
child: t.string,
1362+
}
1363+
},
1364+
}),
1365+
response: {
1366+
200: {
1367+
test: t.string
1368+
}
1369+
},
1370+
});
1371+
`;
1372+
1373+
testCase('route with type descriptions', ROUTE_WITH_TYPE_DESCRIPTIONS, {
1374+
openapi: '3.0.3',
1375+
info: {
1376+
title: 'Test',
1377+
version: '1.0.0',
1378+
},
1379+
paths: {
1380+
'/foo': {
1381+
get: {
1382+
summary: 'A simple route with type descriptions',
1383+
operationId: 'api.v1.test',
1384+
tags: ['Test Routes'],
1385+
parameters: [
1386+
{
1387+
description: 'bar param',
1388+
in: 'query',
1389+
name: 'bar',
1390+
required: true,
1391+
schema: {
1392+
type: 'string'
1393+
}
1394+
}
1395+
],
1396+
requestBody: {
1397+
content: {
1398+
'application/json': {
1399+
schema: {
1400+
properties: {
1401+
bar: {
1402+
description: 'bar description',
1403+
type: 'number'
1404+
},
1405+
child: {
1406+
properties: {
1407+
child: {
1408+
description: 'child description',
1409+
type: 'string'
1410+
}
1411+
},
1412+
required: [
1413+
'child'
1414+
],
1415+
type: 'object'
1416+
},
1417+
foo: {
1418+
description: 'foo description',
1419+
type: 'string'
1420+
}
1421+
},
1422+
required: [
1423+
'foo',
1424+
'bar',
1425+
'child'
1426+
],
1427+
type: 'object'
1428+
}
1429+
}
1430+
}
1431+
},
1432+
responses: {
1433+
200: {
1434+
description: 'OK',
1435+
content: {
1436+
'application/json': {
1437+
schema: {
1438+
type: 'object',
1439+
properties: {
1440+
test: {
1441+
type: 'string',
1442+
},
1443+
},
1444+
required: ['test'],
1445+
},
1446+
},
1447+
},
1448+
},
1449+
},
1450+
},
1451+
},
1452+
},
1453+
components: {
1454+
schemas: {},
1455+
},
1456+
});

0 commit comments

Comments
 (0)