Skip to content

Commit ae9d1db

Browse files
authored
Merge pull request #861 from BitGo/DX-644
feat: consolidate unknown unions with well defined codecs
2 parents dacc359 + 288dff5 commit ae9d1db

File tree

2 files changed

+125
-0
lines changed

2 files changed

+125
-0
lines changed

packages/openapi-generator/src/openapi.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ export function schemaToOpenAPI(
102102
const nonUndefinedSchema = schema.schemas.find((s) => s.type !== 'undefined');
103103
// If nullSchema exists, that means that the union is also nullable
104104
const nullSchema = schema.schemas.find((s) => s.type === 'null');
105+
106+
// If any schema exists and it is in union with another schema - we can remove the any schema as an optimization
107+
const unknownSchema = schema.schemas.find((s) => s.type === 'any');
108+
105109
// and we can just return the other schema (while attaching the comment to that schema)
106110
const isOptional =
107111
schema.schemas.length >= 2 && undefinedSchema && nonUndefinedSchema;
@@ -113,6 +117,29 @@ export function schemaToOpenAPI(
113117
});
114118
}
115119

120+
// This is an edge case for something like this -> t.union([WellDefinedCodec, t.unknown])
121+
// It doesn't make sense to display the unknown codec in the OpenAPI spec so this essentially strips it out of the generation
122+
// so that we don't present useless information to the user
123+
const isUnionWithUnknown = schema.schemas.length >= 2 && unknownSchema;
124+
if (isUnionWithUnknown) {
125+
const nonUnknownSchemas = schema.schemas.filter((s) => s.type !== 'any');
126+
127+
if (nonUnknownSchemas.length === 1 && nonUnknownSchemas[0] !== undefined) {
128+
return schemaToOpenAPI({
129+
...nonUnknownSchemas[0],
130+
comment: schema.comment,
131+
...(nullSchema ? { nullable: true } : {}),
132+
});
133+
} else if (nonUnknownSchemas.length > 1) {
134+
return schemaToOpenAPI({
135+
type: 'union',
136+
schemas: nonUnknownSchemas,
137+
comment: schema.comment,
138+
...(nullSchema ? { nullable: true } : {}),
139+
});
140+
}
141+
}
142+
116143
for (const s of schema.schemas) {
117144
if (s.type === 'null') {
118145
nullable = true;

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

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4602,3 +4602,101 @@ testCase("route with record types", ROUTE_WITH_RECORD_TYPES, {
46024602
}
46034603
});
46044604

4605+
const ROUTE_WITH_UNKNOWN_UNIONS = `
4606+
import * as t from 'io-ts';
4607+
import * as h from '@api-ts/io-ts-http';
4608+
4609+
const UnknownUnion = t.union([t.string, t.number, t.boolean, t.unknown]);
4610+
const SingleUnknownUnion = t.union([t.unknown, t.string]);
4611+
4612+
const NestedUnknownUnion = t.union([t.union([t.string, t.unknown]), t.union([t.boolean, t.unknown])]);
4613+
4614+
export const route = h.httpRoute({
4615+
path: '/foo',
4616+
method: 'GET',
4617+
request: h.httpRequest({}),
4618+
response: {
4619+
200: {
4620+
single: SingleUnknownUnion,
4621+
unknown: UnknownUnion,
4622+
nested: NestedUnknownUnion,
4623+
}
4624+
},
4625+
});
4626+
`;
4627+
4628+
testCase("route with unknown unions", ROUTE_WITH_UNKNOWN_UNIONS, {
4629+
info: {
4630+
title: 'Test',
4631+
version: '1.0.0'
4632+
},
4633+
openapi: '3.0.3',
4634+
paths: {
4635+
'/foo': {
4636+
get: {
4637+
parameters: [],
4638+
responses: {
4639+
'200': {
4640+
content: {
4641+
'application/json': {
4642+
schema: {
4643+
properties: {
4644+
nested: {
4645+
'$ref': '#/components/schemas/NestedUnknownUnion'
4646+
},
4647+
single: {
4648+
'$ref': '#/components/schemas/SingleUnknownUnion'
4649+
},
4650+
unknown: {
4651+
'$ref': '#/components/schemas/UnknownUnion'
4652+
}
4653+
},
4654+
required: [
4655+
'single',
4656+
'unknown',
4657+
'nested'
4658+
],
4659+
type: 'object'
4660+
}
4661+
}
4662+
},
4663+
description: 'OK'
4664+
}
4665+
}
4666+
}
4667+
}
4668+
},
4669+
components: {
4670+
schemas: {
4671+
NestedUnknownUnion: {
4672+
oneOf: [
4673+
{
4674+
type: 'string'
4675+
},
4676+
{
4677+
type: 'boolean'
4678+
}
4679+
],
4680+
title: 'NestedUnknownUnion'
4681+
},
4682+
SingleUnknownUnion: {
4683+
title: 'SingleUnknownUnion',
4684+
type: 'string'
4685+
},
4686+
UnknownUnion: {
4687+
oneOf: [
4688+
{
4689+
type: 'string'
4690+
},
4691+
{
4692+
type: 'number'
4693+
},
4694+
{
4695+
type: 'boolean'
4696+
}
4697+
],
4698+
title: 'UnknownUnion'
4699+
}
4700+
}
4701+
},
4702+
});

0 commit comments

Comments
 (0)