forked from vega/ts-json-schema-generator
/
UnionTypeFormatter.ts
121 lines (107 loc) · 4.19 KB
/
UnionTypeFormatter.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import { JSONSchema7 } from "json-schema";
import { Definition } from "../Schema/Definition";
import { SubTypeFormatter } from "../SubTypeFormatter";
import { BaseType } from "../Type/BaseType";
import { LiteralType } from "../Type/LiteralType";
import { NeverType } from "../Type/NeverType";
import { UnionType } from "../Type/UnionType";
import { TypeFormatter } from "../TypeFormatter";
import { derefType } from "../Utils/derefType";
import { getTypeByKey } from "../Utils/typeKeys";
import { uniqueArray } from "../Utils/uniqueArray";
export class UnionTypeFormatter implements SubTypeFormatter {
public constructor(protected childTypeFormatter: TypeFormatter) {}
public supportsType(type: UnionType): boolean {
return type instanceof UnionType;
}
public getDefinition(type: UnionType): Definition {
// FIXME: Filtering never types from union types is wrong. However,
// disabling this line will result in regressions of tests using the
// `hidden` tag.
const definitions = type
.getTypes()
.filter((item) => !(derefType(item) instanceof NeverType))
.map((item) => this.childTypeFormatter.getDefinition(item));
const discriminator = type.getDiscriminator();
if (discriminator !== undefined) {
if (definitions.length === 1) {
return definitions[0];
}
const kindTypes = type
.getTypes()
.filter((item) => !(derefType(item) instanceof NeverType))
.map((item) => getTypeByKey(item, new LiteralType(discriminator)));
const undefinedIndex = kindTypes.findIndex((item) => item === undefined);
if (undefinedIndex != -1) {
throw new Error(
`Cannot find discriminator keyword "${discriminator}" in type ${JSON.stringify(
type.getTypes()[undefinedIndex]
)}.`
);
}
const kindDefinitions = kindTypes.map((item) => this.childTypeFormatter.getDefinition(item as BaseType));
const allOf = [];
for (let i = 0; i < definitions.length; i++) {
allOf.push({
if: {
properties: { [discriminator]: kindDefinitions[i] },
},
then: definitions[i],
});
}
return { allOf };
}
// TODO: why is this not covered by LiteralUnionTypeFormatter?
// special case for string literals | string -> string
let stringType = true;
let oneNotEnum = false;
for (const def of definitions) {
if (def.type !== "string") {
stringType = false;
break;
}
if (def.enum === undefined) {
oneNotEnum = true;
}
}
if (stringType && oneNotEnum) {
const values = [];
for (const def of definitions) {
if (def.enum) {
values.push(...def.enum);
} else if (def.const) {
values.push(def.const);
} else {
return {
type: "string",
};
}
}
return {
type: "string",
enum: values,
};
}
const flattenedDefinitions: JSONSchema7[] = [];
// Flatten anyOf inside anyOf unless the anyOf has an annotation
for (const def of definitions) {
if (Object.keys(def) === ["anyOf"]) {
flattenedDefinitions.push(...(def.anyOf as any));
} else {
flattenedDefinitions.push(def);
}
}
return flattenedDefinitions.length > 1
? {
anyOf: flattenedDefinitions,
}
: flattenedDefinitions[0];
}
public getChildren(type: UnionType): BaseType[] {
return uniqueArray(
type
.getTypes()
.reduce((result: BaseType[], item) => [...result, ...this.childTypeFormatter.getChildren(item)], [])
);
}
}