Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: Intermediate discriminated type #512

Merged
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking
changeKind: fix
packages:
- "@azure-tools/typespec-autorest"
---

Fix: Discriminated inheritance wasn't resolving the `x-ms-discriminator-value` when it had an intermediate model.
39 changes: 28 additions & 11 deletions packages/typespec-autorest/src/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1635,6 +1635,29 @@ function createOAPIEmitter(
);
}

function getDiscriminatorValue(model: Model): string | undefined {
let discriminator;
let current = model;
while (current.baseModel) {
discriminator = getDiscriminator(program, current.baseModel);
if (discriminator) {
break;
}
current = current.baseModel;
}
if (discriminator === undefined) {
return undefined;
}
const prop = getProperty(model, discriminator.propertyName);
if (prop) {
const values = getStringValues(prop.type);
if (values.length === 1) {
return values[0];
}
}
return undefined;
}

function getSchemaForModel(model: Model, visibility: Visibility) {
const array = getArrayType(model, visibility);
if (array) {
Expand All @@ -1647,17 +1670,11 @@ function createOAPIEmitter(
};

if (model.baseModel) {
const discriminator = getDiscriminator(program, model.baseModel);
if (discriminator) {
const prop = getProperty(model, discriminator.propertyName);
if (prop) {
const values = getStringValues(prop.type);
if (values.length === 1) {
const extensions = getExtensions(program, model);
if (!extensions.has("x-ms-discriminator-value")) {
modelSchema["x-ms-discriminator-value"] = values[0];
}
}
const discriminatorValue = getDiscriminatorValue(model);
if (discriminatorValue) {
const extensions = getExtensions(program, model);
if (!extensions.has("x-ms-discriminator-value")) {
modelSchema["x-ms-discriminator-value"] = discriminatorValue;
}
}
}
Expand Down
51 changes: 33 additions & 18 deletions packages/typespec-autorest/test/discriminator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,49 +111,64 @@ describe("typespec-autorest: polymorphic model inheritance with discriminator",
deepStrictEqual(openApi.definitions.CatCls.allOf, [{ $ref: "#/definitions/Pet" }]);
});

it("defines discriminated unions with more than one level of inheritance", async () => {
it("defines discriminated inheritance with extra non discriminated leaf types", async () => {
const openApi = await openApiFor(`
@discriminator("kind")
model Pet {
name: string;
weight?: float32;
kind: string;
}
model Cat extends Pet {
kind: "cat";
meow: int32;
}
model Dog extends Pet {
kind: "dog";
bark: string;
}
model Beagle extends Dog {
purebred: boolean;
}

op read(): { @body body: Pet };
`);
ok(openApi.definitions.Pet, "expected definition named Pet");
ok(openApi.definitions.Cat, "expected definition named Cat");
ok(openApi.definitions.Dog, "expected definition named Dog");
ok(openApi.definitions.Beagle, "expected definition named Beagle");
deepStrictEqual(openApi.paths["/"].get.responses["200"].schema, {
$ref: "#/definitions/Pet",
});
deepStrictEqual(openApi.definitions.Pet, {
type: "object",
properties: {
kind: { type: "string", description: "Discriminator property for Pet." },
name: { type: "string" },
weight: { type: "number", format: "float" },
kind: { type: "string" },
},
required: ["kind", "name"],
required: ["kind"],
discriminator: "kind",
});
deepStrictEqual(openApi.definitions.Cat.allOf, [{ $ref: "#/definitions/Pet" }]);
deepStrictEqual(openApi.definitions.Dog.allOf, [{ $ref: "#/definitions/Pet" }]);
deepStrictEqual(openApi.definitions.Beagle.allOf, [{ $ref: "#/definitions/Dog" }]);
});

it("defines discriminated inheritance with intermediate non discriminated model", async () => {
const openApi = await openApiFor(`
@discriminator("kind")
model Pet {
kind: string;
}

model CommonPet extends Pet {
name: string;
}

model Dog extends CommonPet {
kind: "dog-kind";
}
`);
deepStrictEqual(openApi.definitions.Pet, {
type: "object",
properties: {
kind: { type: "string" },
},
required: ["kind"],
discriminator: "kind",
});
deepStrictEqual(openApi.definitions.Dog.allOf, [{ $ref: "#/definitions/CommonPet" }]);
strictEqual(openApi.definitions.Dog["x-ms-discriminator-value"], "dog-kind");
deepStrictEqual(openApi.definitions.CommonPet.allOf, [{ $ref: "#/definitions/Pet" }]);
strictEqual(openApi.definitions.CommonPet["x-ms-discriminator-value"], undefined);
});

it("defines nested discriminated unions", async () => {
const openApi = await openApiFor(`
@discriminator("kind")
Expand Down
Loading