Skip to content

Commit

Permalink
Fix: Literal unions with the same variants keep adding duplicate entr…
Browse files Browse the repository at this point in the history
…ies (#3090)

fix #3087
  • Loading branch information
timotheeguerin authored Apr 2, 2024
1 parent cda7ccf commit c6506bc
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@typespec/openapi3"
---

Fix: Literal unions with the same variants keep adding duplicate entries
28 changes: 19 additions & 9 deletions packages/openapi3/src/schema-emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ export class OpenAPI3SchemaEmitter extends TypeEmitter<
return new ObjectBuilder({});
}
const variants = Array.from(union.variants.values());
const literalVariantEnumByType: Record<string, any> = {};
const literalVariantEnumByType: Record<string, any[]> = {};
const ofType = getOneOf(program, union) ? "oneOf" : "anyOf";
const schemaMembers: { schema: any; type: Type | null }[] = [];
let nullable = false;
Expand All @@ -491,15 +491,14 @@ export class OpenAPI3SchemaEmitter extends TypeEmitter<

if (isLiteralType(variant.type)) {
if (!literalVariantEnumByType[variant.type.kind]) {
const enumSchema = this.emitter.emitTypeReference(variant.type);
compilerAssert(
enumSchema.kind === "code",
"Unexpected enum schema. Should be kind: code"
);
literalVariantEnumByType[variant.type.kind] = enumSchema.value;
schemaMembers.push({ schema: enumSchema.value, type: null });
const enumValue: any[] = [variant.type.value];
literalVariantEnumByType[variant.type.kind] = enumValue;
schemaMembers.push({
schema: { type: literalType(variant.type), enum: enumValue },
type: null,
});
} else {
literalVariantEnumByType[variant.type.kind].enum.push(variant.type.value);
literalVariantEnumByType[variant.type.kind].push(variant.type.value);
}
} else {
const enumSchema = this.emitter.emitTypeReference(variant.type, {
Expand Down Expand Up @@ -943,6 +942,17 @@ function isLiteralType(type: Type): type is StringLiteral | NumericLiteral | Boo
return type.kind === "Boolean" || type.kind === "String" || type.kind === "Number";
}

function literalType(type: StringLiteral | NumericLiteral | BooleanLiteral) {
switch (type.kind) {
case "String":
return "string";
case "Number":
return "number";
case "Boolean":
return "boolean";
}
}

function includeDerivedModel(model: Model): boolean {
return (
!isTemplateDeclaration(model) &&
Expand Down
68 changes: 68 additions & 0 deletions packages/openapi3/test/union-schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,74 @@ describe("openapi3: union type", () => {
});
});

describe("union literals", () => {
it("produce an enum from union of string literal", async () => {
const res = await openApiFor(
`
model Pet {
prop: "a" | "b";
};
`
);
deepStrictEqual(res.components.schemas.Pet.properties.prop, {
type: "string",
enum: ["a", "b"],
});
});
it("produce an enum from union of numeric literal", async () => {
const res = await openApiFor(
`
model Pet {
prop: 0 | 1;
};
`
);
deepStrictEqual(res.components.schemas.Pet.properties.prop, {
type: "number",
enum: [0, 1],
});
});

// Regression test for https://github.com/microsoft/typespec/issues/3087
it("string literals union with same variants don't conflict", async () => {
const res = await openApiFor(
`
model Pet {
prop1: "a" | "b";
prop2: "a" | "b";
}
`
);
deepStrictEqual(res.components.schemas.Pet.properties.prop1, {
type: "string",
enum: ["a", "b"],
});
deepStrictEqual(res.components.schemas.Pet.properties.prop2, {
type: "string",
enum: ["a", "b"],
});
});
// Regression test for https://github.com/microsoft/typespec/issues/3087
it("numeric literals union with same variants don't conflict", async () => {
const res = await openApiFor(
`
model Pet {
prop1: 0 | 1;
prop2: 0 | 1;
}
`
);
deepStrictEqual(res.components.schemas.Pet.properties.prop1, {
type: "number",
enum: [0, 1],
});
deepStrictEqual(res.components.schemas.Pet.properties.prop2, {
type: "number",
enum: [0, 1],
});
});
});

it("defines nullable properties with multiple variants", async () => {
const res = await oapiForModel(
"Pet",
Expand Down

0 comments on commit c6506bc

Please sign in to comment.