Skip to content

Commit

Permalink
Merge pull request #773 from BitGo/DX-427-inline-descriptions
Browse files Browse the repository at this point in the history
feat(openapi-generator): add support for inline type descriptions
  • Loading branch information
bitgopatmcl authored May 28, 2024
2 parents 5336a24 + bdb071a commit 35ee662
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 5 deletions.
40 changes: 35 additions & 5 deletions packages/openapi-generator/src/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,27 @@ function schemaToOpenAPI(
const createOpenAPIObject = (
schema: Schema,
): OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined => {
const description = schema.comment?.description;

const defaultObject = {
...(description ? { description } : {}),
};

switch (schema.type) {
case 'boolean':
case 'string':
case 'number':
return { type: schema.type, ...(schema.enum ? { enum: schema.enum } : {}) };
return {
type: schema.type,
...(schema.enum ? { enum: schema.enum } : {}),
...defaultObject,
};
case 'integer':
return { type: 'number', ...(schema.enum ? { enum: schema.enum } : {}) };
return {
type: 'number',
...(schema.enum ? { enum: schema.enum } : {}),
...defaultObject,
};
case 'null':
// TODO: OpenAPI v3 does not have an explicit null type, is there a better way to represent this?
// Or should we just conflate explicit null and undefined properties?
Expand All @@ -32,7 +46,7 @@ function schemaToOpenAPI(
if (innerSchema === undefined) {
return undefined;
}
return { type: 'array', items: innerSchema };
return { type: 'array', items: innerSchema, ...defaultObject };
case 'object':
return {
type: 'object',
Expand Down Expand Up @@ -146,13 +160,25 @@ function routeToOpenAPI(route: Route): [string, string, OpenAPIV3.OperationObjec
{},
);

const stripTopLevelComment = (schema: Schema) => {
const copy = { ...schema };

if (copy.comment?.description !== undefined && copy.comment?.description !== '') {
copy.comment.description = '';
}

return copy;
};

const topLevelStripped = stripTopLevelComment(route.body!);

const requestBody =
route.body === undefined
? {}
: {
requestBody: {
content: {
'application/json': { schema: schemaToOpenAPI(route.body) },
'application/json': { schema: schemaToOpenAPI(topLevelStripped) },
},
},
};
Expand All @@ -174,6 +200,10 @@ function routeToOpenAPI(route: Route): [string, string, OpenAPIV3.OperationObjec
// Array types not allowed here
const schema = schemaToOpenAPI(p.schema);

if (schema && 'description' in schema) {
delete schema.description;
}

return {
name: p.name,
...(p.schema?.comment?.description !== undefined
Expand All @@ -195,7 +225,7 @@ function routeToOpenAPI(route: Route): [string, string, OpenAPIV3.OperationObjec
description,
content: {
'application/json': {
schema: schemaToOpenAPI(response),
schema: schemaToOpenAPI(stripTopLevelComment(response)),
...(example !== undefined ? { example } : undefined),
},
},
Expand Down
123 changes: 123 additions & 0 deletions packages/openapi-generator/test/openapi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1331,3 +1331,126 @@ testCase('route with multiple unknown tags', ROUTE_WITH_MULTIPLE_UNKNOWN_TAGS, {
schemas: {},
},
});


const ROUTE_WITH_TYPE_DESCRIPTIONS = `
import * as t from 'io-ts';
import * as h from '@api-ts/io-ts-http';
/**
* A simple route with type descriptions
*
* @operationId api.v1.test
* @tag Test Routes
*/
export const route = h.httpRoute({
path: '/foo',
method: 'GET',
request: h.httpRequest({
query: {
/** bar param */
bar: t.string,
},
body: {
/** foo description */
foo: t.string,
/** bar description */
bar: t.number,
child: {
/** child description */
child: t.string,
}
},
}),
response: {
200: {
test: t.string
}
},
});
`;

testCase('route with type descriptions', ROUTE_WITH_TYPE_DESCRIPTIONS, {
openapi: '3.0.3',
info: {
title: 'Test',
version: '1.0.0',
},
paths: {
'/foo': {
get: {
summary: 'A simple route with type descriptions',
operationId: 'api.v1.test',
tags: ['Test Routes'],
parameters: [
{
description: 'bar param',
in: 'query',
name: 'bar',
required: true,
schema: {
type: 'string'
}
}
],
requestBody: {
content: {
'application/json': {
schema: {
properties: {
bar: {
description: 'bar description',
type: 'number'
},
child: {
properties: {
child: {
description: 'child description',
type: 'string'
}
},
required: [
'child'
],
type: 'object'
},
foo: {
description: 'foo description',
type: 'string'
}
},
required: [
'foo',
'bar',
'child'
],
type: 'object'
}
}
}
},
responses: {
200: {
description: 'OK',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
test: {
type: 'string',
},
},
required: ['test'],
},
},
},
},
},
},
},
},
components: {
schemas: {},
},
});

0 comments on commit 35ee662

Please sign in to comment.