Skip to content

Commit df0657b

Browse files
authored
Add initial support for polymorphism (#1393)
1 parent e2130bd commit df0657b

File tree

2 files changed

+77
-55
lines changed

2 files changed

+77
-55
lines changed

packages/typespec-powershell/src/convertor/convertor.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { getServers } from "@typespec/http";
88
import { join } from "path";
99
import { PwshModel } from "@autorest/powershell";
1010
// import { CodeModel as PwshModel } from "@autorest/codemodel";
11-
import { constantSchemaForApiVersion, getDefaultService, getSchemaForType, schemaCache, stringSchemaForEnum, numberSchemaForEnum, getSchemaForApiVersion, getEnrichedDefaultApiVersion } from "../utils/modelUtils.js";
11+
import { constantSchemaForApiVersion, getDefaultService, getSchemaForType, schemaCache, stringSchemaForEnum, numberSchemaForEnum, getSchemaForApiVersion, getEnrichedDefaultApiVersion, delayedModelSet } from "../utils/modelUtils.js";
1212
import { Info, Language, Schemas, AllSchemaTypes, SchemaType, ArraySchema, StringSchema, Languages, ObjectSchema } from "@autorest/codemodel";
1313
import { camelCase, deconstruct, pascalCase, serialize } from "@azure-tools/codegen";
1414
import { PSOptions } from "../types/interfaces.js";
@@ -54,6 +54,9 @@ function handleCircleReference(schemas: Schemas) {
5454
}
5555

5656
function getSchemas(program: Program, client: SdkClient, psContext: SdkContext, model: PwshModel): Schemas {
57+
for (const eachModel of delayedModelSet) {
58+
getSchemaForType(psContext, eachModel);
59+
}
5760
const schemas = new Schemas();
5861
for (const schema of schemaCache.values()) {
5962
if (schema.type === SchemaType.Any) {

packages/typespec-powershell/src/utils/modelUtils.ts

Lines changed: 73 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ export let constantSchemaForApiVersion: ConstantSchema | undefined;
176176
export const schemaCache = new Map<Type, Schema>();
177177
// Add this to the modelSet to avoid circular reference
178178
export const modelSet = new Set<Type>();
179+
// For the models that are delayed to be set, currently the only case is the model that is derived from the model with discriminator
180+
export const delayedModelSet = new Set<Type>();
179181
export function getSchemaForApiVersion(dpgContext: SdkContext, typeInput: Type) {
180182
if (constantSchemaForApiVersion) {
181183
return constantSchemaForApiVersion;
@@ -568,7 +570,10 @@ function getSchemaForUnionVariant(
568570
variant: UnionVariant,
569571
options?: GetSchemaOptions
570572
): Schema {
571-
return getSchemaForType(dpgContext, variant, options);
573+
const schema = new ConstantSchema(variant.name.toString(), getDoc(dpgContext.program, variant) || "");
574+
schema.valueType = getSchemaForType(dpgContext, variant.type, options);
575+
schema.value = new ConstantValue(variant.name.toString());
576+
return schema;
572577
}
573578

574579
// An openapi "string" can be defined in several different ways in typespec
@@ -582,6 +587,9 @@ function isOasString(type: Type): boolean {
582587
} else if (type.kind === "Union") {
583588
// A union where all variants are an OasString
584589
return type.options.every((o) => isOasString(o));
590+
} else if (type.kind === "UnionVariant") {
591+
// A union variant where the type is an OasString
592+
return isOasString(type.type);
585593
}
586594
return false;
587595
}
@@ -591,7 +599,8 @@ function isStringLiteral(type: Type): boolean {
591599
type.kind === "String" ||
592600
(type.kind === "Union" && type.options.every((o) => o.kind === "String")) ||
593601
(type.kind === "EnumMember" &&
594-
typeof (type.value ?? type.name) === "string")
602+
typeof (type.value ?? type.name) === "string") ||
603+
(type.kind === "UnionVariant" && type.type.kind === "String")
595604
);
596605
}
597606

@@ -761,6 +770,23 @@ function getSchemaForModel(
761770
// NameType.Interface,
762771
// true /** shouldGuard */
763772
// );
773+
// by xiaogang, skip ArmResourceBase
774+
if (model.baseModel && model.baseModel.name !== "ArmResourceBase") {
775+
modelSchema.parents = {
776+
all: [
777+
getSchemaForType(dpgContext, model.baseModel, {
778+
usage,
779+
needRef: true
780+
})
781+
],
782+
immediate: [
783+
getSchemaForType(dpgContext, model.baseModel, {
784+
usage,
785+
needRef: true
786+
})
787+
]
788+
};
789+
}
764790
modelSchema.language.default.name = pascalCase(deconstruct(modelSchema.language.default.name));
765791
if (isRecordModelType(program, model)) {
766792
return getSchemaForRecordModel(dpgContext, model, { usage });
@@ -814,50 +840,41 @@ function getSchemaForModel(
814840
const derivedModels = model.derivedModels.filter((dm) => {
815841
return includeDerivedModel(dm, discriminator ? false : needRef);
816842
});
817-
if (derivedModels.length > 0) {
818-
modelSchema.children = {
819-
all: [],
820-
immediate: []
821-
};
822-
}
843+
823844
for (const child of derivedModels) {
824-
const childSchema = getSchemaForType(dpgContext, child, {
825-
usage,
826-
needRef: true
827-
});
828-
for (const [name, prop] of child.properties) {
829-
if (name === discriminator?.propertyName) {
830-
const propSchema = getSchemaForType(dpgContext, prop.type, {
831-
usage,
832-
needRef: !isAnonymousModelType(prop.type),
833-
relevantProperty: prop
834-
});
835-
childSchema.discriminatorValue = propSchema.type.replace(/"/g, "");
836-
break;
837-
}
838-
}
839-
modelSchema.children?.all?.push(childSchema);
840-
modelSchema.children?.immediate?.push(childSchema);
845+
// Delay schema generation of those models to avoiding circular reference
846+
delayedModelSet.add(child);
847+
// const childSchema = getSchemaForType(dpgContext, child, {
848+
// usage,
849+
// needRef: true
850+
// });
851+
// for (const [name, prop] of child.properties) {
852+
// if (name === discriminator?.propertyName) {
853+
// const propSchema = getSchemaForType(dpgContext, prop.type, {
854+
// usage,
855+
// needRef: !isAnonymousModelType(prop.type),
856+
// relevantProperty: prop
857+
// });
858+
// childSchema.discriminatorValue = propSchema.type.replace(/"/g, "");
859+
// break;
860+
// }
861+
// }
862+
// modelSchema.children?.all?.push(childSchema);
863+
// modelSchema.children?.immediate?.push(childSchema);
841864
}
842865

843866
// Enable option `isPolyParent` and discriminator only when it has valid children
844867
if (
845868
discriminator &&
846-
modelSchema?.children?.all?.length &&
847-
modelSchema?.children?.all?.length > 0
869+
derivedModels &&
870+
derivedModels.length > 0
848871
) {
849872
if (!validateDiscriminator(program, discriminator, derivedModels)) {
850873
// appropriate diagnostic is generated in the validate function
851874
return {};
852875
}
853876

854877
const { propertyName } = discriminator;
855-
// ToDo polymorphism by xiaogang
856-
// modelSchema.discriminator = {
857-
// name: propertyName,
858-
// type: "string",
859-
// description: `Discriminator property for ${model.name}.`
860-
// };
861878
modelSchema.discriminatorValue = propertyName;
862879
// ToDo: need to confirm whether still need this.
863880
// modelSchema.isPolyParent = true;
@@ -926,8 +943,23 @@ function getSchemaForModel(
926943
property.extensions = property.extensions || {};
927944
property.extensions['circle-ref'] = pascalCase(deconstruct(prop.type.name));
928945
}
929-
930-
modelSchema.properties.push(property);
946+
let isDiscriminatorInChild = false;
947+
if (modelSchema.parents && modelSchema.parents.all) {
948+
modelSchema.parents.all.forEach((parent) => {
949+
if (parent.type === "object" && (<ObjectSchema>parent).discriminator?.property.serializedName === propName) {
950+
isDiscriminatorInChild = true;
951+
}
952+
});
953+
}
954+
if (!isDiscriminatorInChild) {
955+
modelSchema.properties.push(property);
956+
} else {
957+
modelSchema.discriminatorValue = (<ConstantSchema>propSchema).value.value;
958+
}
959+
if (discriminator && propName === discriminator.propertyName) {
960+
property.isDiscriminator = true;
961+
modelSchema.discriminator = new M4Discriminator(property);
962+
}
931963
// if this property is a discriminator property, remove it to keep autorest validation happy
932964
//const { propertyName } = getDiscriminator(program, model) || {};
933965
// ToDo: by xiaoang, skip polymorphism for the time being.
@@ -973,33 +1005,20 @@ function getSchemaForModel(
9731005
// modelSchema.properties = modelSchema.properties?.filter(p => p.language.default.name != name);
9741006
// modelSchema.properties.push(newPropSchema);
9751007
}
976-
// by xiaogang, skip ArmResourceBase
977-
if (model.baseModel && model.baseModel.name !== "ArmResourceBase") {
978-
modelSchema.parents = {
979-
all: [
980-
getSchemaForType(dpgContext, model.baseModel, {
981-
usage,
982-
needRef: true
983-
})
984-
],
985-
immediate: [
986-
getSchemaForType(dpgContext, model.baseModel, {
987-
usage,
988-
needRef: true
989-
})
990-
]
991-
};
992-
}
1008+
9931009
return modelSchema;
9941010
}
9951011
// Map an typespec type to an OA schema. Returns undefined when the resulting
9961012
// OA schema is just a regular object schema.
9971013
function getSchemaForLiteral(type: Type): any {
1014+
// ToDo: by xiaogang, need to implement other kinds as String
9981015
switch (type.kind) {
9991016
case "Number":
10001017
return { type: `${type.value}`, isConstant: true };
1001-
case "String":
1002-
return { type: `"${type.value}"`, isConstant: true };
1018+
case "String": {
1019+
const schema = new StringSchema(type.value, "");
1020+
return schema;
1021+
}
10031022
case "Boolean":
10041023
return { type: `${type.value}`, isConstant: true };
10051024
}

0 commit comments

Comments
 (0)