Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -423,12 +423,22 @@ protected internal override PropertyProvider[] BuildProperties()
var propertiesCount = _inputModel.Properties.Count;
var properties = new List<PropertyProvider>(propertiesCount + 1);
Dictionary<string, InputModelProperty> baseProperties = EnumerateBaseModels().SelectMany(m => m.Properties).GroupBy(x => x.Name).Select(g => g.First()).ToDictionary(p => p.Name) ?? [];
// Build a set of serialized names for base discriminator properties to handle cases where
// the derived model has a discriminator with a different C# name but the same wire name
HashSet<string> baseDiscriminatorSerializedNames = EnumerateBaseModels()
.SelectMany(m => m.Properties)
.Where(p => p.IsDiscriminator && p.SerializedName is not null)
.Select(p => p.SerializedName)
.ToHashSet();
for (int i = 0; i < propertiesCount; i++)
{
var property = _inputModel.Properties[i];
var isDiscriminator = IsDiscriminator(property);

if (isDiscriminator && baseProperties.ContainsKey(property.Name))
// Skip discriminator properties that already exist in the base class
// Check both by C# property name and by serialized name to handle cases where
// the derived model has a discriminator with a different C# name but the same wire name
if (isDiscriminator && (baseProperties.ContainsKey(property.Name) || (property.SerializedName is not null && baseDiscriminatorSerializedNames.Contains(property.SerializedName))))
{
continue;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -452,5 +452,90 @@ public async Task CanCustomizeDiscriminator()
Assert.IsNotNull(discriminatorParam);
Assert.IsTrue(discriminatorParam!.Property?.IsDiscriminator);
}

// This test validates that a derived model does not re-declare the discriminator property
// when the base model has a discriminator property with a different C# name but the same serialized name.
// This scenario can occur when the base model has a discriminator property named "OdataType"
// and the derived model has a discriminator property also named "OdataType" with the same
// serialized name "@odata.type".
[Test]
public void DerivedDoesNotDuplicateDiscriminatorWithSameSerializedName()
{
// Base model with discriminator named "OdataType" with serialized name "@odata.type"
var baseSkillModel = InputFactory.Model(
"SearchIndexerSkill",
properties: [InputFactory.Property("OdataType", InputPrimitiveType.String, isRequired: true, isDiscriminator: true, serializedName: "@odata.type")],
discriminatedModels: new Dictionary<string, InputModelType>());

// Derived model with discriminator named "OdataType" with same serialized name "@odata.type"
var derivedSkillModel = InputFactory.Model(
"ContentUnderstandingSkill",
discriminatedKind: "#Microsoft.Skills.Util.ContentUnderstandingSkill",
baseModel: baseSkillModel,
properties:
[
InputFactory.Property("OdataType", InputPrimitiveType.String, isRequired: true, isDiscriminator: true, serializedName: "@odata.type"),
InputFactory.Property("Inputs", InputPrimitiveType.String, isRequired: true)
]);

MockHelpers.LoadMockGenerator(inputModelTypes: [baseSkillModel, derivedSkillModel]);
var outputLibrary = CodeModelGenerator.Instance.OutputLibrary;

var derivedModel = outputLibrary.TypeProviders.OfType<ModelProvider>().FirstOrDefault(t => t.Name == "ContentUnderstandingSkill");
Assert.IsNotNull(derivedModel);

// The derived model should NOT have the OdataType property since it's already defined in the base class
var odataTypeProperty = derivedModel!.Properties.FirstOrDefault(p => p.Name == "OdataType");
Assert.IsNull(odataTypeProperty, "Derived model should not re-declare the discriminator property that exists in the base class");

// The public constructor should correctly pass the discriminator value to the base class
var publicCtor = derivedModel.Constructors.FirstOrDefault(c => c.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public));
Assert.IsNotNull(publicCtor);
// The public ctor should not have an odataType parameter
Assert.IsFalse(publicCtor!.Signature.Parameters.Any(p => p.Name.Equals("odataType", System.StringComparison.OrdinalIgnoreCase)));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could we also check that we are passing in the correct discriminator value to the base class?

}

// This test validates that a derived model does not re-declare the discriminator property
// when the base model has a discriminator property with a different C# name but the same serialized name.
// This scenario can occur when the base model has a discriminator property named "Type"
// and the derived model has a discriminator property named "OdataType" with the same
// serialized name "@odata.type".
[Test]
public void DerivedDoesNotDuplicateDiscriminatorWithDifferentNameButSameSerializedName()
{
// Base model with discriminator named "Type" with serialized name "@odata.type"
var baseSkillModel = InputFactory.Model(
"SearchIndexerSkill",
properties: [InputFactory.Property("Type", InputPrimitiveType.String, isRequired: true, isDiscriminator: true, serializedName: "@odata.type")],
discriminatedModels: new Dictionary<string, InputModelType>());

// Derived model with discriminator named "OdataType" with same serialized name "@odata.type"
var derivedSkillModel = InputFactory.Model(
"ContentUnderstandingSkill",
discriminatedKind: "#Microsoft.Skills.Util.ContentUnderstandingSkill",
baseModel: baseSkillModel,
properties:
[
InputFactory.Property("OdataType", InputPrimitiveType.String, isRequired: true, isDiscriminator: true, serializedName: "@odata.type"),
InputFactory.Property("Inputs", InputPrimitiveType.String, isRequired: true)
]);

MockHelpers.LoadMockGenerator(inputModelTypes: [baseSkillModel, derivedSkillModel]);
var outputLibrary = CodeModelGenerator.Instance.OutputLibrary;

var derivedModel = outputLibrary.TypeProviders.OfType<ModelProvider>().FirstOrDefault(t => t.Name == "ContentUnderstandingSkill");
Assert.IsNotNull(derivedModel);

// The derived model should NOT have the OdataType property since it's the same discriminator
// (same serialized name) as the base class "Type" property
var odataTypeProperty = derivedModel!.Properties.FirstOrDefault(p => p.Name == "OdataType");
Assert.IsNull(odataTypeProperty, "Derived model should not re-declare the discriminator property when base has same serialized name");

// The public constructor should correctly pass the discriminator value to the base class
var publicCtor = derivedModel.Constructors.FirstOrDefault(c => c.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public));
Assert.IsNotNull(publicCtor);
// The public ctor should not have an odataType parameter
Assert.IsFalse(publicCtor!.Signature.Parameters.Any(p => p.Name.Equals("odataType", System.StringComparison.OrdinalIgnoreCase)));
}
}
}