Skip to content

Commit 33574c1

Browse files
feat: support enum with null to make nullable (#2034)
1 parent de7e150 commit 33574c1

File tree

15 files changed

+585
-170
lines changed

15 files changed

+585
-170
lines changed

packages/parsers/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@fern-api/docs-parsers",
3-
"version": "0.0.38",
3+
"version": "0.0.39",
44
"repository": {
55
"type": "git",
66
"url": "https://github.com/fern-api/fern-platform.git",

packages/parsers/src/openapi/3.1/schemas/MixedSchemaConverter.node.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
BaseOpenApiV3_1ConverterNodeWithExample,
77
} from "../../BaseOpenApiV3_1Converter.node";
88
import { maybeSingleValueToArray } from "../../utils/maybeSingleValueToArray";
9+
import { wrapNullable } from "../../utils/wrapNullable";
910
import { SchemaConverterNode } from "./SchemaConverter.node";
1011

1112
export declare namespace MixedSchemaConverterNode {
@@ -87,14 +88,7 @@ export class MixedSchemaConverterNode extends BaseOpenApiV3_1ConverterNodeWithEx
8788
}));
8889

8990
return this.nullable
90-
? unions.map((union) => ({
91-
type: "alias" as const,
92-
value: {
93-
type: "nullable" as const,
94-
default: union.variants[0],
95-
shape: union,
96-
},
97-
}))
91+
? unions.map(wrapNullable).filter(isNonNullish)
9892
: unions;
9993
}
10094

packages/parsers/src/openapi/3.1/schemas/OneOfConverter.node.ts

Lines changed: 35 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
} from "../../BaseOpenApiV3_1Converter.node";
88
import { resolveSchemaReference } from "../../utils/3.1/resolveSchemaReference";
99
import { maybeSingleValueToArray } from "../../utils/maybeSingleValueToArray";
10+
import { wrapNullable } from "../../utils/wrapNullable";
1011
import { SchemaConverterNode } from "./SchemaConverter.node";
1112

1213
export class OneOfConverterNode extends BaseOpenApiV3_1ConverterNodeWithExample<
@@ -16,6 +17,7 @@ export class OneOfConverterNode extends BaseOpenApiV3_1ConverterNodeWithExample<
1617
| FernRegistry.api.latest.TypeShape[]
1718
> {
1819
isUnionOfObjects: boolean | undefined;
20+
isNullable: boolean | undefined;
1921
discriminated: boolean | undefined;
2022
discriminant: string | undefined;
2123
discriminatedMapping: Record<string, SchemaConverterNode> | undefined;
@@ -36,18 +38,25 @@ export class OneOfConverterNode extends BaseOpenApiV3_1ConverterNodeWithExample<
3638
resolveSchemaReference(schema, this.context.document)?.type ===
3739
"object"
3840
);
41+
this.isNullable = (this.input.oneOf ?? this.input.anyOf)?.some(
42+
(schema) =>
43+
resolveSchemaReference(schema, this.context.document)?.type === "null"
44+
);
3945
if (this.input.discriminator == null) {
4046
this.discriminated = false;
41-
this.undiscriminatedMapping = (
42-
this.input.oneOf ?? this.input.anyOf
43-
)?.map((schema) => {
44-
return new SchemaConverterNode({
45-
input: schema,
46-
context: this.context,
47-
accessPath: this.accessPath,
48-
pathId: this.pathId,
49-
});
50-
});
47+
this.undiscriminatedMapping = (this.input.oneOf ?? this.input.anyOf)
48+
?.map((schema) => {
49+
return resolveSchemaReference(schema, this.context.document)
50+
?.type !== "null"
51+
? new SchemaConverterNode({
52+
input: schema,
53+
context: this.context,
54+
accessPath: this.accessPath,
55+
pathId: this.pathId,
56+
})
57+
: undefined;
58+
})
59+
.filter(isNonNullish);
5160
} else {
5261
const maybeMapping = this.input.discriminator.mapping;
5362
if (maybeMapping != null) {
@@ -88,9 +97,12 @@ export class OneOfConverterNode extends BaseOpenApiV3_1ConverterNodeWithExample<
8897
| FernRegistry.api.latest.TypeShape[]
8998
| undefined {
9099
if (!this.isUnionOfObjects && !this.discriminated) {
91-
return this.undiscriminatedMapping
100+
const convertedNodes = this.undiscriminatedMapping
92101
?.flatMap((node) => node.convert())
93102
.filter(isNonNullish);
103+
return this.isNullable && convertedNodes != null
104+
? convertedNodes.map(wrapNullable).filter(isNonNullish)
105+
: convertedNodes;
94106
}
95107

96108
if (
@@ -104,12 +116,12 @@ export class OneOfConverterNode extends BaseOpenApiV3_1ConverterNodeWithExample<
104116
) {
105117
return undefined;
106118
}
107-
return this.discriminated &&
119+
const union =
120+
this.discriminated &&
108121
this.discriminant != null &&
109122
this.discriminatedMapping != null
110-
? [
111-
{
112-
type: "discriminatedUnion",
123+
? {
124+
type: "discriminatedUnion" as const,
113125
discriminant: FernRegistry.PropertyKey(this.discriminant),
114126
variants: Object.entries(this.discriminatedMapping)
115127
.flatMap(([key, node]) => {
@@ -131,12 +143,10 @@ export class OneOfConverterNode extends BaseOpenApiV3_1ConverterNodeWithExample<
131143
.filter(isNonNullish);
132144
})
133145
.filter(isNonNullish),
134-
},
135-
]
136-
: this.undiscriminatedMapping != null
137-
? [
138-
{
139-
type: "undiscriminatedUnion",
146+
}
147+
: this.undiscriminatedMapping != null
148+
? {
149+
type: "undiscriminatedUnion" as const,
140150
variants: this.undiscriminatedMapping
141151
.flatMap((node) => {
142152
const convertedShapes = maybeSingleValueToArray(
@@ -158,9 +168,10 @@ export class OneOfConverterNode extends BaseOpenApiV3_1ConverterNodeWithExample<
158168
.filter(isNonNullish);
159169
})
160170
.filter(isNonNullish),
161-
},
162-
]
163-
: undefined;
171+
}
172+
: undefined;
173+
const wrappedUnion = this.isNullable ? wrapNullable(union) : union;
174+
return wrappedUnion != null ? [wrappedUnion] : undefined;
164175
}
165176

166177
example(): Record<string, unknown> | undefined {

packages/parsers/src/openapi/3.1/schemas/SchemaConverter.node.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { isNonNullish } from "@fern-api/ui-core-utils";
12
import { OpenAPIV3_1 } from "openapi-types";
23
import { UnreachableCaseError } from "ts-essentials";
34
import { FernRegistry } from "../../../client/generated";
@@ -6,6 +7,7 @@ import {
67
BaseOpenApiV3_1ConverterNodeWithExample,
78
} from "../../BaseOpenApiV3_1Converter.node";
89
import { maybeSingleValueToArray } from "../../utils/maybeSingleValueToArray";
10+
import { wrapNullable } from "../../utils/wrapNullable";
911
import { AvailabilityConverterNode } from "../extensions/AvailabilityConverter.node";
1012
import { isArraySchema } from "../guards/isArraySchema";
1113
import { isBooleanSchema } from "../guards/isBooleanSchema";
@@ -29,6 +31,7 @@ import { IntegerConverterNode } from "./primitives/IntegerConverter.node";
2931
import { NullConverterNode } from "./primitives/NullConverter.node";
3032
import { NumberConverterNode } from "./primitives/NumberConverter.node";
3133
import { StringConverterNode } from "./primitives/StringConverter.node";
34+
import { UnknownConverterNode } from "./primitives/UnknownConverter.node";
3235
import { ReferenceConverterNode } from "./ReferenceConverter.node";
3336

3437
export type PrimitiveType =
@@ -220,6 +223,12 @@ export class SchemaConverterNode extends BaseOpenApiV3_1ConverterNodeWithExample
220223
}
221224

222225
if (this.typeShapeNode == null) {
226+
this.typeShapeNode = new UnknownConverterNode({
227+
input: this.input,
228+
context: this.context,
229+
accessPath: this.accessPath,
230+
pathId: this.pathId,
231+
});
223232
this.context.errors.error({
224233
message: "Expected type declaration. Received: null",
225234
path: this.accessPath,
@@ -232,21 +241,12 @@ export class SchemaConverterNode extends BaseOpenApiV3_1ConverterNodeWithExample
232241
| FernRegistry.api.latest.TypeShape[]
233242
| undefined {
234243
const maybeShapes = this.typeShapeNode?.convert();
235-
const mappedShapes = maybeSingleValueToArray(maybeShapes)?.map((shape) =>
236-
this.nullable
237-
? {
238-
type: "alias" as const,
239-
value: {
240-
type: "nullable" as const,
241-
shape,
242-
},
243-
}
244-
: shape
245-
);
246-
247244
if (maybeShapes == null) {
248245
return undefined;
249246
}
247+
const mappedShapes = maybeSingleValueToArray(maybeShapes)
248+
?.map((shape) => (this.nullable ? wrapNullable(shape) : shape))
249+
.filter(isNonNullish);
250250

251251
return Array.isArray(maybeShapes) && maybeShapes.length > 1
252252
? mappedShapes

packages/parsers/src/openapi/3.1/schemas/__test__/ArrayConverter.node.test.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,21 @@ describe("ArrayConverterNode", () => {
9191
message: "Expected type declaration. Received: null",
9292
path: ["test", "items"],
9393
});
94-
expect(converted).toBeUndefined();
94+
expect(converted).toEqual([
95+
{
96+
type: "alias",
97+
value: {
98+
type: "list",
99+
itemShape: {
100+
type: "alias",
101+
value: {
102+
type: "unknown",
103+
displayName: undefined,
104+
},
105+
},
106+
},
107+
},
108+
]);
95109
});
96110
});
97111
});

packages/parsers/src/openapi/3.1/schemas/__test__/SchemaConverter.node.test.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,13 @@ describe("SchemaConverterNode", () => {
201201
accessPath: [],
202202
pathId: "test",
203203
});
204-
expect(node.convert()).toBeUndefined();
204+
expect(node.convert()).toEqual({
205+
type: "alias",
206+
value: {
207+
type: "unknown",
208+
displayName: undefined,
209+
},
210+
});
205211
});
206212
});
207213
});

packages/parsers/src/openapi/3.1/schemas/primitives/NullConverter.node.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
BaseOpenApiV3_1ConverterNodeConstructorArgs,
55
BaseOpenApiV3_1ConverterNodeWithExample,
66
} from "../../../BaseOpenApiV3_1Converter.node";
7+
import { wrapNullable } from "../../../utils/wrapNullable";
78
import { SchemaConverterNode } from "../SchemaConverter.node";
89

910
export declare namespace NullConverterNode {
@@ -13,9 +14,7 @@ export declare namespace NullConverterNode {
1314

1415
export interface Output extends FernRegistry.api.latest.TypeShape.Alias {
1516
type: "alias";
16-
value:
17-
| FernRegistry.api.latest.TypeReference.Nullable
18-
| FernRegistry.api.latest.TypeReference.Unknown;
17+
value: FernRegistry.api.latest.TypeReference.Nullable;
1918
}
2019
}
2120

@@ -38,19 +37,13 @@ export class NullConverterNode extends BaseOpenApiV3_1ConverterNodeWithExample<
3837
}
3938

4039
convert(): NullConverterNode.Output | undefined {
41-
return {
40+
return wrapNullable({
4241
type: "alias",
4342
value: {
44-
type: "nullable",
45-
shape: {
46-
type: "alias",
47-
value: {
48-
type: "unknown",
49-
displayName: this.displayName,
50-
},
51-
},
43+
type: "unknown",
44+
displayName: this.displayName,
5245
},
53-
};
46+
});
5447
}
5548

5649
example(): null {
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { noop } from "ts-essentials";
2+
import { FernRegistry } from "../../../../client/generated";
3+
import {
4+
BaseOpenApiV3_1ConverterNodeConstructorArgs,
5+
BaseOpenApiV3_1ConverterNodeWithExample,
6+
} from "../../../BaseOpenApiV3_1Converter.node";
7+
import { SchemaConverterNode } from "../SchemaConverter.node";
8+
9+
export declare namespace UnknownConverterNode {
10+
export interface Output extends FernRegistry.api.latest.TypeShape.Alias {
11+
type: "alias";
12+
value: FernRegistry.api.latest.TypeReference.Unknown;
13+
}
14+
}
15+
16+
export class UnknownConverterNode extends BaseOpenApiV3_1ConverterNodeWithExample<
17+
unknown,
18+
UnknownConverterNode.Output
19+
> {
20+
displayName: string | undefined;
21+
shape: SchemaConverterNode | undefined;
22+
23+
constructor(args: BaseOpenApiV3_1ConverterNodeConstructorArgs<unknown>) {
24+
super(args);
25+
this.safeParse();
26+
}
27+
28+
parse(): void {
29+
noop();
30+
}
31+
32+
convert(): UnknownConverterNode.Output | undefined {
33+
return {
34+
type: "alias",
35+
value: {
36+
type: "unknown",
37+
displayName: undefined,
38+
},
39+
};
40+
}
41+
42+
example(): undefined {
43+
return undefined;
44+
}
45+
}

0 commit comments

Comments
 (0)