Skip to content

Commit

Permalink
feat: support enum with null to make nullable (#2034)
Browse files Browse the repository at this point in the history
  • Loading branch information
RohinBhargava authored Jan 17, 2025
1 parent de7e150 commit 33574c1
Show file tree
Hide file tree
Showing 15 changed files with 585 additions and 170 deletions.
2 changes: 1 addition & 1 deletion packages/parsers/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@fern-api/docs-parsers",
"version": "0.0.38",
"version": "0.0.39",
"repository": {
"type": "git",
"url": "https://github.com/fern-api/fern-platform.git",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
BaseOpenApiV3_1ConverterNodeWithExample,
} from "../../BaseOpenApiV3_1Converter.node";
import { maybeSingleValueToArray } from "../../utils/maybeSingleValueToArray";
import { wrapNullable } from "../../utils/wrapNullable";
import { SchemaConverterNode } from "./SchemaConverter.node";

export declare namespace MixedSchemaConverterNode {
Expand Down Expand Up @@ -87,14 +88,7 @@ export class MixedSchemaConverterNode extends BaseOpenApiV3_1ConverterNodeWithEx
}));

return this.nullable
? unions.map((union) => ({
type: "alias" as const,
value: {
type: "nullable" as const,
default: union.variants[0],
shape: union,
},
}))
? unions.map(wrapNullable).filter(isNonNullish)
: unions;
}

Expand Down
59 changes: 35 additions & 24 deletions packages/parsers/src/openapi/3.1/schemas/OneOfConverter.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
} from "../../BaseOpenApiV3_1Converter.node";
import { resolveSchemaReference } from "../../utils/3.1/resolveSchemaReference";
import { maybeSingleValueToArray } from "../../utils/maybeSingleValueToArray";
import { wrapNullable } from "../../utils/wrapNullable";
import { SchemaConverterNode } from "./SchemaConverter.node";

export class OneOfConverterNode extends BaseOpenApiV3_1ConverterNodeWithExample<
Expand All @@ -16,6 +17,7 @@ export class OneOfConverterNode extends BaseOpenApiV3_1ConverterNodeWithExample<
| FernRegistry.api.latest.TypeShape[]
> {
isUnionOfObjects: boolean | undefined;
isNullable: boolean | undefined;
discriminated: boolean | undefined;
discriminant: string | undefined;
discriminatedMapping: Record<string, SchemaConverterNode> | undefined;
Expand All @@ -36,18 +38,25 @@ export class OneOfConverterNode extends BaseOpenApiV3_1ConverterNodeWithExample<
resolveSchemaReference(schema, this.context.document)?.type ===
"object"
);
this.isNullable = (this.input.oneOf ?? this.input.anyOf)?.some(
(schema) =>
resolveSchemaReference(schema, this.context.document)?.type === "null"
);
if (this.input.discriminator == null) {
this.discriminated = false;
this.undiscriminatedMapping = (
this.input.oneOf ?? this.input.anyOf
)?.map((schema) => {
return new SchemaConverterNode({
input: schema,
context: this.context,
accessPath: this.accessPath,
pathId: this.pathId,
});
});
this.undiscriminatedMapping = (this.input.oneOf ?? this.input.anyOf)
?.map((schema) => {
return resolveSchemaReference(schema, this.context.document)
?.type !== "null"
? new SchemaConverterNode({
input: schema,
context: this.context,
accessPath: this.accessPath,
pathId: this.pathId,
})
: undefined;
})
.filter(isNonNullish);
} else {
const maybeMapping = this.input.discriminator.mapping;
if (maybeMapping != null) {
Expand Down Expand Up @@ -88,9 +97,12 @@ export class OneOfConverterNode extends BaseOpenApiV3_1ConverterNodeWithExample<
| FernRegistry.api.latest.TypeShape[]
| undefined {
if (!this.isUnionOfObjects && !this.discriminated) {
return this.undiscriminatedMapping
const convertedNodes = this.undiscriminatedMapping
?.flatMap((node) => node.convert())
.filter(isNonNullish);
return this.isNullable && convertedNodes != null
? convertedNodes.map(wrapNullable).filter(isNonNullish)
: convertedNodes;
}

if (
Expand All @@ -104,12 +116,12 @@ export class OneOfConverterNode extends BaseOpenApiV3_1ConverterNodeWithExample<
) {
return undefined;
}
return this.discriminated &&
const union =
this.discriminated &&
this.discriminant != null &&
this.discriminatedMapping != null
? [
{
type: "discriminatedUnion",
? {
type: "discriminatedUnion" as const,
discriminant: FernRegistry.PropertyKey(this.discriminant),
variants: Object.entries(this.discriminatedMapping)
.flatMap(([key, node]) => {
Expand All @@ -131,12 +143,10 @@ export class OneOfConverterNode extends BaseOpenApiV3_1ConverterNodeWithExample<
.filter(isNonNullish);
})
.filter(isNonNullish),
},
]
: this.undiscriminatedMapping != null
? [
{
type: "undiscriminatedUnion",
}
: this.undiscriminatedMapping != null
? {
type: "undiscriminatedUnion" as const,
variants: this.undiscriminatedMapping
.flatMap((node) => {
const convertedShapes = maybeSingleValueToArray(
Expand All @@ -158,9 +168,10 @@ export class OneOfConverterNode extends BaseOpenApiV3_1ConverterNodeWithExample<
.filter(isNonNullish);
})
.filter(isNonNullish),
},
]
: undefined;
}
: undefined;
const wrappedUnion = this.isNullable ? wrapNullable(union) : union;
return wrappedUnion != null ? [wrappedUnion] : undefined;
}

example(): Record<string, unknown> | undefined {
Expand Down
24 changes: 12 additions & 12 deletions packages/parsers/src/openapi/3.1/schemas/SchemaConverter.node.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isNonNullish } from "@fern-api/ui-core-utils";
import { OpenAPIV3_1 } from "openapi-types";
import { UnreachableCaseError } from "ts-essentials";
import { FernRegistry } from "../../../client/generated";
Expand All @@ -6,6 +7,7 @@ import {
BaseOpenApiV3_1ConverterNodeWithExample,
} from "../../BaseOpenApiV3_1Converter.node";
import { maybeSingleValueToArray } from "../../utils/maybeSingleValueToArray";
import { wrapNullable } from "../../utils/wrapNullable";
import { AvailabilityConverterNode } from "../extensions/AvailabilityConverter.node";
import { isArraySchema } from "../guards/isArraySchema";
import { isBooleanSchema } from "../guards/isBooleanSchema";
Expand All @@ -29,6 +31,7 @@ import { IntegerConverterNode } from "./primitives/IntegerConverter.node";
import { NullConverterNode } from "./primitives/NullConverter.node";
import { NumberConverterNode } from "./primitives/NumberConverter.node";
import { StringConverterNode } from "./primitives/StringConverter.node";
import { UnknownConverterNode } from "./primitives/UnknownConverter.node";
import { ReferenceConverterNode } from "./ReferenceConverter.node";

export type PrimitiveType =
Expand Down Expand Up @@ -220,6 +223,12 @@ export class SchemaConverterNode extends BaseOpenApiV3_1ConverterNodeWithExample
}

if (this.typeShapeNode == null) {
this.typeShapeNode = new UnknownConverterNode({
input: this.input,
context: this.context,
accessPath: this.accessPath,
pathId: this.pathId,
});
this.context.errors.error({
message: "Expected type declaration. Received: null",
path: this.accessPath,
Expand All @@ -232,21 +241,12 @@ export class SchemaConverterNode extends BaseOpenApiV3_1ConverterNodeWithExample
| FernRegistry.api.latest.TypeShape[]
| undefined {
const maybeShapes = this.typeShapeNode?.convert();
const mappedShapes = maybeSingleValueToArray(maybeShapes)?.map((shape) =>
this.nullable
? {
type: "alias" as const,
value: {
type: "nullable" as const,
shape,
},
}
: shape
);

if (maybeShapes == null) {
return undefined;
}
const mappedShapes = maybeSingleValueToArray(maybeShapes)
?.map((shape) => (this.nullable ? wrapNullable(shape) : shape))
.filter(isNonNullish);

return Array.isArray(maybeShapes) && maybeShapes.length > 1
? mappedShapes
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,21 @@ describe("ArrayConverterNode", () => {
message: "Expected type declaration. Received: null",
path: ["test", "items"],
});
expect(converted).toBeUndefined();
expect(converted).toEqual([
{
type: "alias",
value: {
type: "list",
itemShape: {
type: "alias",
value: {
type: "unknown",
displayName: undefined,
},
},
},
},
]);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,13 @@ describe("SchemaConverterNode", () => {
accessPath: [],
pathId: "test",
});
expect(node.convert()).toBeUndefined();
expect(node.convert()).toEqual({
type: "alias",
value: {
type: "unknown",
displayName: undefined,
},
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
BaseOpenApiV3_1ConverterNodeConstructorArgs,
BaseOpenApiV3_1ConverterNodeWithExample,
} from "../../../BaseOpenApiV3_1Converter.node";
import { wrapNullable } from "../../../utils/wrapNullable";
import { SchemaConverterNode } from "../SchemaConverter.node";

export declare namespace NullConverterNode {
Expand All @@ -13,9 +14,7 @@ export declare namespace NullConverterNode {

export interface Output extends FernRegistry.api.latest.TypeShape.Alias {
type: "alias";
value:
| FernRegistry.api.latest.TypeReference.Nullable
| FernRegistry.api.latest.TypeReference.Unknown;
value: FernRegistry.api.latest.TypeReference.Nullable;
}
}

Expand All @@ -38,19 +37,13 @@ export class NullConverterNode extends BaseOpenApiV3_1ConverterNodeWithExample<
}

convert(): NullConverterNode.Output | undefined {
return {
return wrapNullable({
type: "alias",
value: {
type: "nullable",
shape: {
type: "alias",
value: {
type: "unknown",
displayName: this.displayName,
},
},
type: "unknown",
displayName: this.displayName,
},
};
});
}

example(): null {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { noop } from "ts-essentials";
import { FernRegistry } from "../../../../client/generated";
import {
BaseOpenApiV3_1ConverterNodeConstructorArgs,
BaseOpenApiV3_1ConverterNodeWithExample,
} from "../../../BaseOpenApiV3_1Converter.node";
import { SchemaConverterNode } from "../SchemaConverter.node";

export declare namespace UnknownConverterNode {
export interface Output extends FernRegistry.api.latest.TypeShape.Alias {
type: "alias";
value: FernRegistry.api.latest.TypeReference.Unknown;
}
}

export class UnknownConverterNode extends BaseOpenApiV3_1ConverterNodeWithExample<
unknown,
UnknownConverterNode.Output
> {
displayName: string | undefined;
shape: SchemaConverterNode | undefined;

constructor(args: BaseOpenApiV3_1ConverterNodeConstructorArgs<unknown>) {
super(args);
this.safeParse();
}

parse(): void {
noop();
}

convert(): UnknownConverterNode.Output | undefined {
return {
type: "alias",
value: {
type: "unknown",
displayName: undefined,
},
};
}

example(): undefined {
return undefined;
}
}
Loading

0 comments on commit 33574c1

Please sign in to comment.