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

Add .NET 9's JsonStringEnumMemberNameAttribute #833

Merged
merged 11 commits into from
Jan 24, 2025
37 changes: 36 additions & 1 deletion src/KubeOps.Transpiler/Crds.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections;
using System.Collections.ObjectModel;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text.Json.Serialization;

using k8s;
Expand Down Expand Up @@ -339,12 +340,46 @@ private static V1JSONSchemaProps Map(this MetadataLoadContext context, Type type
"System.Enum" => new V1JSONSchemaProps
{
Type = String,
EnumProperty = Enum.GetNames(type).Cast<object>().ToList(),
EnumProperty = GetEnumNames(context, type),
},
_ => throw InvalidType(type),
};
}

private static IList<object> GetEnumNames(this MetadataLoadContext context, Type type)
{
#if NET9_0_OR_GREATER
var attributeNameByFieldName = new Dictionary<string, string>();

foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Static))
{
if (field.GetCustomAttributeData<JsonStringEnumMemberNameAttribute>() is { } jsonMemberNameAttribute &&
jsonMemberNameAttribute.GetCustomAttributeCtorArg<string>(context, 0) is { } jsonMemberNameAtributeName)
{
attributeNameByFieldName.Add(field.Name, jsonMemberNameAtributeName);
}
}

var enumNames = new List<object>();

foreach (var value in Enum.GetNames(type))
{
if (attributeNameByFieldName.TryGetValue(value, out var name))
{
enumNames.Add(name);
}
else
{
enumNames.Add(value);
}
}

return enumNames;
#else
return Enum.GetNames(type);
#endif
}

private static V1JSONSchemaProps MapObjectType(this MetadataLoadContext context, Type type)
{
switch (type.FullName)
Expand Down
12 changes: 12 additions & 0 deletions src/KubeOps.Transpiler/Utilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@ public static class Utilities
.GetCustomAttributes(type)
.FirstOrDefault(a => a.AttributeType.Name == typeof(TAttribute).Name);

/// <summary>
/// Load a custom attribute from a read-only-reflected field.
/// </summary>
/// <param name="field">The field.</param>
/// <typeparam name="TAttribute">The type of the attribute to load.</typeparam>
/// <returns>The custom attribute data if an attribute is found.</returns>
public static CustomAttributeData? GetCustomAttributeData<TAttribute>(this FieldInfo field)
where TAttribute : Attribute
=> CustomAttributeData
.GetCustomAttributes(field)
.FirstOrDefault(a => a.AttributeType.Name == typeof(TAttribute).Name);

/// <summary>
/// Load a custom attribute from a read-only-reflected property.
/// </summary>
Expand Down
23 changes: 23 additions & 0 deletions test/KubeOps.Transpiler.Test/Crds.Mlc.Test.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public class CrdsMlcTest(MlcProvider provider) : TranspilerTestBase(provider)
[InlineData(typeof(SetIntEntity), "array", null, null)]
[InlineData(typeof(InheritedEnumerableEntity), "array", null, null)]
[InlineData(typeof(EnumEntity), "string", null, null)]
[InlineData(typeof(NamedEnumEntity), "string", null, null)]
[InlineData(typeof(NullableEnumEntity), "string", null, true)]
[InlineData(typeof(DictionaryEntity), "object", null, null)]
[InlineData(typeof(EnumerableKeyPairsEntity), "object", null, null)]
Expand Down Expand Up @@ -475,6 +476,14 @@ public void Should_Correctly_Use_Entity_Scope_Attribute()
clusterCrd.Spec.Scope.Should().Be("Cluster");
}

[Fact]
public void Should_Correctly_Get_Enum_Value_From_JsonStringEnumMemberNameAttribute()
{
var crd = _mlc.Transpile(typeof(NamedEnumEntity));
var specProperties = crd.Spec.Versions.First().Schema.OpenAPIV3Schema.Properties["property"];
specProperties.EnumProperty.Should().BeEquivalentTo(["enumValue1", "enumValue2"]);
}

#region Test Entity Classes

[KubernetesEntity(Group = "testing.dev", ApiVersion = "v1", Kind = "TestEntity")]
Expand Down Expand Up @@ -659,6 +668,20 @@ public enum TestSpecEnum
}
}

[KubernetesEntity(Group = "testing.dev", ApiVersion = "v1", Kind = "TestEntity")]
private class NamedEnumEntity : CustomKubernetesEntity
{
public TestSpecEnum Property { get; set; }

public enum TestSpecEnum
{
[JsonStringEnumMemberName("enumValue1")]
Value1,
[JsonStringEnumMemberName("enumValue2")]
Value2,
}
}

[KubernetesEntity(Group = "testing.dev", ApiVersion = "v1", Kind = "TestEntity")]
private class SimpleDictionaryEntity : CustomKubernetesEntity
{
Expand Down
Loading