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

Support enum member as type #1924

Merged
merged 9 commits into from
Jul 18, 2023
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions packages/rlc-common/src/buildObjectTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export function buildObjectInterfaces(
}
const baseName = getObjectBaseName(objectSchema, schemaUsage);
const interfaceDeclaration = getObjectInterfaceDeclaration(
model,
baseName,
objectSchema,
schemaUsage,
Expand Down Expand Up @@ -175,6 +176,7 @@ function getPolymorphicTypeAlias(
* root node it will suffix it with Base.
*/
function getObjectInterfaceDeclaration(
model: RLCModel,
baseName: string,
objectSchema: ObjectSchema,
schemaUsage: SchemaContext[],
Expand All @@ -195,6 +197,7 @@ function getObjectInterfaceDeclaration(

// Add the polymorphic property if exists
propertySignatures = addDiscriminatorProperty(
model,
objectSchema,
propertySignatures
);
Expand All @@ -218,10 +221,11 @@ function isPolymorphicParent(objectSchema: ObjectSchema) {
}

function addDiscriminatorProperty(
model: RLCModel,
objectSchema: ObjectSchema,
properties: PropertySignatureStructure[]
): PropertySignatureStructure[] {
const polymorphicProperty = getDiscriminatorProperty(objectSchema);
const polymorphicProperty = getDiscriminatorProperty(model, objectSchema);

if (polymorphicProperty) {
// It is possible that the polymorphic property needs to override an existing property.
Expand All @@ -240,6 +244,7 @@ function addDiscriminatorProperty(
* Finds the name of the property used as discriminator and the discriminator value.
*/
function getDiscriminatorProperty(
model: RLCModel,
objectSchema: ObjectSchema
): PropertySignatureStructure | undefined {
const discriminatorValue = objectSchema.discriminatorValue;
Expand All @@ -256,11 +261,10 @@ function getDiscriminatorProperty(
`getDiscriminatorProperty: Expected object ${objectSchema.name} to have a discriminator in its hierarchy but found none`
);
}

return {
kind: StructureKind.PropertySignature,
name: `"${discriminatorPropertyName}"`,
type: discriminators
type: model.options?.sourceFrom === "Swagger" ? discriminators : `string`
};
}

Expand Down
46 changes: 34 additions & 12 deletions packages/typespec-ts/src/utils/modelUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ export function getSchemaForType(
return getSchemaForEnum(program, type);
} else if (type.kind === "Scalar") {
return getSchemaForScalar(dpgContext, type, relevantProperty);
} else if (type.kind === "EnumMember") {
return getSchemaForEnumMember(program, type);
}
if (isUnknownType(type)) {
const returnType: any = { type: "unknown" };
Expand Down Expand Up @@ -298,18 +300,26 @@ function isOasString(type: Type): boolean {
function isStringLiteral(type: Type): boolean {
return (
type.kind === "String" ||
(type.kind === "Union" && type.options.every((o) => o.kind === "String"))
(type.kind === "Union" && type.options.every((o) => o.kind === "String")) ||
(type.kind === "EnumMember" &&
typeof (type.value ?? type.name) === "string")
);
}

// Return any string literal values for type
function getStringValues(type: Type): string[] {
if (type.kind === "String") {
return [type.value];
} else if (type.kind === "Union") {
return type.options.flatMap(getStringValues).filter((v) => v);
switch (type.kind) {
case "String":
return [type.value];
case "Union":
return [...type.variants.values()]
.flatMap((x) => getStringValues(x.type))
.filter((x) => x !== undefined);
case "EnumMember":
return typeof type.value !== "number" ? [type.value ?? type.name] : [];
default:
return [];
}
return [];
}
function validateDiscriminator(
program: Program,
Expand All @@ -328,7 +338,11 @@ function validateDiscriminator(
return false;
}
let retval = true;
if (!isOasString(prop.type)) {
if (
!isOasString(prop.type) &&
prop.type.kind !== "EnumMember" &&
prop.type.kind !== "Enum"
) {
reportDiagnostic(program, {
code: "discriminator",
messageId: "type",
Expand Down Expand Up @@ -724,6 +738,13 @@ function applyIntrinsicDecorators(

return newTarget;
}

function getSchemaForEnumMember(program: Program, e: EnumMember) {
const value = e.value ?? e.name;
const type = enumMemberType(e) === "string" ? `"${value}"` : `${value}`;
return { type, description: getDoc(program, e) };
}

function getSchemaForEnum(program: Program, e: Enum) {
const values = [];
const type = enumMemberType(e.members.values().next().value);
Expand All @@ -749,12 +770,13 @@ function getSchemaForEnum(program: Program, e: Enum) {
}
}
return schema;
function enumMemberType(member: EnumMember) {
if (typeof member.value === "number") {
return "number";
}
return "string";
}

function enumMemberType(member: EnumMember) {
if (typeof member.value === "number") {
return "number";
}
return "string";
}
/**
* Map Cadl intrinsic models to open api definitions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ export interface Pet {
/** This is base model for polymorphic multiple levels inheritance with a discriminator. */
export interface FishParent {
age: number;
kind: "Fish" | "shark" | "salmon";
kind: string;
}

/** The second level model in polymorphic multiple levels inheritance and it defines a new discriminator. */
export interface SharkParent extends FishParent {
kind: "shark";
sharktype: "Shark" | "saw" | "goblin";
sharktype: string;
}

/** The third level model SawShark in polymorphic multiple levels inheritance. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ export interface PetOutput {
/** This is base model for polymorphic multiple levels inheritance with a discriminator. */
export interface FishOutputParent {
age: number;
kind: "Fish" | "shark" | "salmon";
kind: string;
}

/** The second level model in polymorphic multiple levels inheritance and it defines a new discriminator. */
export interface SharkOutputParent extends FishOutputParent {
kind: "shark";
sharktype: "Shark" | "saw" | "goblin";
sharktype: string;
}

/** The third level model SawShark in polymorphic multiple levels inheritance. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

/** This is a base model has discriminator name containing dot. */
export interface BaseModelParent {
"model.kind": "BaseModel" | "derived";
"model.kind": string;
}

/** This is a model has property names of special words or characters. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

/** This is a base model has discriminator name containing dot. */
export interface BaseModelOutputParent {
"model.kind": "BaseModel" | "derived";
"model.kind": string;
}

/** This is a model has property names of special words or characters. */
Expand Down
69 changes: 41 additions & 28 deletions packages/typespec-ts/test/integration/modelInheritance.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import TypeModelInheritanceClientFactory, {
InheritanceClient,
Salmon,
SharkOutput
Shark,
Siamese
} from "./generated/models/inheritance/src/index.js";
import { assert } from "chai";

Expand All @@ -14,7 +15,7 @@ describe("ModelsInheritance Rest Client", () => {
});
});

const validBody = { name: "abc", age: 32, smart: true };
const validBody: Siamese = { name: "abc", age: 32, smart: true };
MaryGao marked this conversation as resolved.
Show resolved Hide resolved
it("should get valid", async () => {
try {
const result = await client.path("/type/model/inheritance/valid").get();
Expand Down Expand Up @@ -60,93 +61,105 @@ describe("ModelsInheritance Rest Client", () => {
assert.strictEqual(result.status, "200");
assert.strictEqual(result.body.age, 1);
assert.strictEqual(result.body.kind, "shark");
assert.strictEqual((result.body as SharkOutput).sharktype, "goblin");
if (result.body.kind === "shark") {
assert.strictEqual(result.body.sharktype, "goblin");
}
} catch (err) {
assert.fail(err as string);
}
});

it("should put polymorphic body", async () => {
try {
const body: Shark = {
age: 1,
kind: "shark",
sharktype: "goblin"
};
const result = await client
.path("/type/model/inheritance/discriminated/model")
.put({
body: {
age: 1,
kind: "shark",
sharktype: "goblin"
}
body
});
assert.strictEqual(result.status, "200");
} catch (err) {
assert.fail(err as string);
}
});

const validRecursiveBody = {
const validRecursiveBody: Salmon = {
age: 1,
kind: "salmon",
partner: {
age: 2,
kind: "shark",
sharktype: "saw",
sharktype: "saw"
},
friends: [
{
age: 2,
kind: "salmon",
partner: {
age: 3,
kind: "salmon",
kind: "salmon"
},
hate: {
key1: {
age: 4,
kind: "salmon",
kind: "salmon"
},
key2: {
age: 2,
kind: "shark",
sharktype: "goblin",
},
},
sharktype: "goblin"
}
}
},
{
age: 3,
kind: "shark",
sharktype: "goblin",
},
sharktype: "goblin"
}
],
hate: {
key3: {
age: 3,
kind: "shark",
sharktype: "saw",
sharktype: "saw"
},
key4: {
age: 2,
kind: "salmon",
friends: [
{
age: 1,
kind: "salmon",
kind: "salmon"
},
{
age: 4,
kind: "shark",
sharktype: "goblin",
},
],
},
},
sharktype: "goblin"
}
]
}
}
};
it("should get recursive body", async () => {
try {
const result = await client
.path("/type/model/inheritance/discriminated/recursivemodel")
.get();
assert.strictEqual(result.status, "200");
assert.strictEqual(JSON.stringify(result.body), JSON.stringify(validRecursiveBody));
assert.strictEqual(
JSON.stringify(result.body),
JSON.stringify(validRecursiveBody)
);
if (result.body.kind === "salmon") {
assert.strictEqual(
result.body.partner?.kind,
validRecursiveBody.partner?.kind
);
}
} catch (err) {
assert.fail(err as string);
}
Expand All @@ -157,14 +170,14 @@ describe("ModelsInheritance Rest Client", () => {
const result = await client
.path("/type/model/inheritance/discriminated/recursivemodel")
.put({
body: validRecursiveBody as Salmon
body: validRecursiveBody
});
assert.strictEqual(result.status, "200");
} catch (err) {
assert.fail(err as string);
}
});

it("should get missing discriminator body", async () => {
try {
const result = await client
Expand All @@ -184,7 +197,7 @@ describe("ModelsInheritance Rest Client", () => {
.get();
assert.strictEqual(result.status, "200");
assert.strictEqual(result.body.age, 1);
assert.strictEqual(result.body.kind, "wrongKind")
assert.strictEqual(result.body.kind, "wrongKind");
} catch (err) {
assert.fail(err as string);
}
Expand Down
Loading
Loading