Skip to content

Commit

Permalink
[http-client-csharp]: Don't include custom required literal propertie…
Browse files Browse the repository at this point in the history
…s in public ctor (#4783)

This PR addresses an issue where required spec properties that were
literals and then customized to another literal type were being
incorrectly included in the public constructor.

fixes: #4747
  • Loading branch information
jorgerangel-msft authored Oct 17, 2024
1 parent a689659 commit f1bfe9c
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -103,20 +103,10 @@ protected override PropertyProvider[] BuildProperties()
}

// handle customized enums - we need to pull the type information from the spec property
if (IsCustomizedEnumProperty(specProperty, customProperty.Type, out var specType))
{
customProperty.Type = new CSharpType(
customProperty.Type.Name,
customProperty.Type.Namespace,
customProperty.Type.IsValueType,
customProperty.Type.IsNullable,
customProperty.Type.DeclaringType,
customProperty.Type.Arguments,
customProperty.Type.IsPublic,
customProperty.Type.IsStruct,
customProperty.Type.BaseType,
TypeFactory.CreatePrimitiveCSharpTypeCore(specType));
}
customProperty.Type = EnsureEnum(specProperty, customProperty.Type);

// ensure literal types are correctly represented in the custom property using the info from the spec property
customProperty.Type = EnsureLiteral(specProperty, customProperty.Type);
}

return [..generatedProperties, ..customProperties];
Expand Down Expand Up @@ -181,20 +171,10 @@ protected override FieldProvider[] BuildFields()
}

// handle customized enums - we need to pull the type information from the spec property
if (IsCustomizedEnumProperty(specProperty, customField.Type, out var specType))
{
customField.Type = new CSharpType(
customField.Type.Name,
customField.Type.Namespace,
customField.Type.IsValueType,
customField.Type.IsNullable,
customField.Type.DeclaringType,
customField.Type.Arguments,
customField.Type.IsPublic,
customField.Type.IsStruct,
customField.Type.BaseType,
TypeFactory.CreatePrimitiveCSharpTypeCore(specType));
}
customField.Type = EnsureEnum(specProperty, customField.Type);

// ensure literal types are correctly represented in the custom field using the info from the spec property
customField.Type = EnsureLiteral(specProperty, customField.Type);
}

return [..generatedFields, ..customFields];
Expand All @@ -220,6 +200,35 @@ private static bool IsCustomizedEnumProperty(
return false;
}

private static CSharpType EnsureLiteral(InputModelProperty? specProperty, CSharpType customType)
{
if (specProperty?.Type is InputLiteralType inputLiteral && (customType.IsFrameworkType || customType.IsEnum))
{
return CSharpType.FromLiteral(customType, inputLiteral.Value);
}

return customType;
}

private static CSharpType EnsureEnum(InputModelProperty? specProperty, CSharpType customType)
{
if (IsCustomizedEnumProperty(specProperty, customType, out var specType))
{
return new CSharpType(
customType.Name,
customType.Namespace,
customType.IsValueType,
customType.IsNullable,
customType.DeclaringType,
customType.Arguments,
customType.IsPublic,
customType.IsStruct,
customType.BaseType,
TypeFactory.CreatePrimitiveCSharpTypeCore(specType));
}
return customType;
}

private static InputPrimitiveType? GetEnumValueType(InputType? type)
{
return type switch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -500,5 +500,75 @@ public async Task DoesNotReplaceDefaultConstructorIfNotCustomized()
Assert.IsTrue(ctors.Any(c => c.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public)));
Assert.IsTrue(ctors.Any(c => c.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Internal)));
}

// Validates that if a required literal property is customized, then the default ctor
// does not include the custom property as a parameter.
[Test]
public async Task DoesNotIncludeReqCustomLiteralInDefaultCtor()
{
var enumType = InputFactory.Enum(
"originalEnum",
InputPrimitiveType.String,
values: [InputFactory.EnumMember.String("bar", "bar")]);
var plugin = await MockHelpers.LoadMockPluginAsync(
inputModelTypes: [
InputFactory.Model(
"mockInputModel",
usage: InputModelTypeUsage.Input,
properties:
[
InputFactory.Property("Prop1", InputFactory.Literal.Enum(enumType, "bar"), isRequired: true),
InputFactory.Property("Prop2", InputPrimitiveType.String, isRequired: true),
])
],
compilation: async () => await Helpers.GetCompilationFromDirectoryAsync());
var csharpGen = new CSharpGen();

await csharpGen.ExecuteAsync();

var ctors = plugin.Object.OutputLibrary.TypeProviders.Single(t => t.Name == "MockInputModel").Constructors;
Assert.AreEqual(2, ctors.Count);

var publicCtor = ctors.FirstOrDefault(c => c.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public));
Assert.IsNotNull(publicCtor);

var ctorParams = publicCtor!.Signature.Parameters;

// should not have the custom required literal property
Assert.AreEqual(1, ctorParams.Count);
Assert.AreEqual("prop2", ctorParams[0].Name);
}

[Test]
public async Task DoesNotIncludeReqCustomFieldLiteralInDefaultCtor()
{
var plugin = await MockHelpers.LoadMockPluginAsync(
inputModelTypes: [
InputFactory.Model(
"mockInputModel",
usage: InputModelTypeUsage.Input,
properties:
[
InputFactory.Property("Prop1", InputFactory.Literal.String("bar"), isRequired: true),
InputFactory.Property("Prop2", InputPrimitiveType.String, isRequired: true),
])
],
compilation: async () => await Helpers.GetCompilationFromDirectoryAsync());
var csharpGen = new CSharpGen();

await csharpGen.ExecuteAsync();

var ctors = plugin.Object.OutputLibrary.TypeProviders.Single(t => t.Name == "MockInputModel").Constructors;
Assert.AreEqual(2, ctors.Count);

var publicCtor = ctors.FirstOrDefault(c => c.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public));
Assert.IsNotNull(publicCtor);

var ctorParams = publicCtor!.Signature.Parameters;

// should not have the custom required literal property
Assert.AreEqual(1, ctorParams.Count);
Assert.AreEqual("prop2", ctorParams[0].Name);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using Microsoft.Generator.CSharp.Customization;

namespace Sample.Models;

public partial class MockInputModel
{
[CodeGenMember("Prop1")]
private string _prop1 = "bar";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Microsoft.Generator.CSharp.Customization;

namespace Sample.Models;

public partial class MockInputModel
{
[CodeGenMember("Prop1")]
public CustomEnum Prop1 { get; } = CustomEnum.Bar;
}

public enum CustomEnum
{
Bar
}

0 comments on commit f1bfe9c

Please sign in to comment.