Skip to content

Commit 1b3abfe

Browse files
alexmgbuehler
andauthored
fix: CRD schema generator does not take JsonProperty.PropertyName value into consideration (#159)
* fix: CRD schema generator should use PropertyName from JsonPropertyAttribute * Minor performance tweak in ToCamelCase extension method Co-authored-by: Christoph Bühler <[email protected]>
1 parent 49d772f commit 1b3abfe

File tree

4 files changed

+48
-10
lines changed

4 files changed

+48
-10
lines changed

src/KubeOps/Operator/Entities/Extensions/EntityToCrdExtensions.cs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99
using k8s.Models;
1010
using KubeOps.Operator.Entities.Annotations;
1111
using KubeOps.Operator.Errors;
12+
using KubeOps.Operator.Util;
1213
using Namotion.Reflection;
14+
using Newtonsoft.Json;
1315

1416
namespace KubeOps.Operator.Entities.Extensions
1517
{
@@ -319,11 +321,11 @@ private static void ProcessType(
319321
!IgnoredToplevelProperties.Contains(info.Name.ToLowerInvariant()))
320322
.Select(
321323
prop => KeyValuePair.Create(
322-
CamelCase(prop.Name),
323-
MapProperty(prop, additionalColumns, $"{jsonPath}.{CamelCase(prop.Name)}"))));
324+
GetPropertyName(prop),
325+
MapProperty(prop, additionalColumns, $"{jsonPath}.{GetPropertyName(prop)}"))));
324326
props.Required = type.GetProperties()
325327
.Where(prop => prop.GetCustomAttribute<RequiredAttribute>() != null)
326-
.Select(prop => CamelCase(prop.Name))
328+
.Select(prop => GetPropertyName(prop))
327329
.ToList();
328330
if (props.Required.Count == 0)
329331
{
@@ -348,7 +350,12 @@ private static bool IsSimpleType(Type type) =>
348350
type.GetGenericTypeDefinition() == typeof(Nullable<>) &&
349351
IsSimpleType(type.GetGenericArguments()[0]));
350352

351-
private static string CamelCase(string str) => $"{str.Substring(0, 1).ToLower()}{str.Substring(1)}";
353+
private static string GetPropertyName(PropertyInfo property)
354+
{
355+
var attribute = property.GetCustomAttribute<JsonPropertyAttribute>();
356+
var propertyName = attribute?.PropertyName ?? property.Name;
357+
return propertyName.ToCamelCase();
358+
}
352359

353360
private static bool IsGenericEnumerableType(Type type, [NotNullWhen(true)] out Type? closingType)
354361
{
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace KubeOps.Operator.Util
2+
{
3+
internal static class StringExtensions
4+
{
5+
internal static string ToCamelCase(this string value) =>
6+
string.IsNullOrWhiteSpace(value) || char.IsLower(value[0])
7+
? value
8+
: char.ToLowerInvariant(value[0]) + value[1..];
9+
}
10+
}

tests/KubeOps.Test/Operator/Entities/CrdGeneration.Test.cs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
using KubeOps.Operator.Commands.Generators;
88
using KubeOps.Operator.Entities.Extensions;
99
using KubeOps.Operator.Errors;
10+
using KubeOps.Operator.Util;
1011
using KubeOps.Test.TestEntities;
12+
using Newtonsoft.Json;
1113
using Xunit;
1214

1315
namespace KubeOps.Test.Operator.Entities
@@ -84,7 +86,7 @@ public void Should_Set_The_Correct_Type_And_Format_For_Types(string fieldName, s
8486
[InlineData(nameof(TestSpecEntitySpec.IntegerIReadOnlySet), "integer", null)]
8587
public void Should_Set_The_Correct_Array_Type(string property, string expectedType, bool? expectedNullable)
8688
{
87-
var propertyName = char.ToLowerInvariant(property[0]) + property[1..];
89+
var propertyName = property.ToCamelCase();
8890
var crd = _testSpecEntity.CreateCrd();
8991
var specProperties = crd.Spec.Versions.First().Schema.OpenAPIV3Schema.Properties["spec"];
9092

@@ -105,7 +107,7 @@ public void Should_Set_The_Correct_Array_Type(string property, string expectedTy
105107
[InlineData(nameof(TestSpecEntitySpec.ComplexItemsDerivedList))]
106108
public void Should_Set_The_Correct_Complex_Array_Type(string property)
107109
{
108-
var propertyName = char.ToLowerInvariant(property[0]) + property[1..];
110+
var propertyName = property.ToCamelCase();
109111
var crd = _testSpecEntity.CreateCrd();
110112
var specProperties = crd.Spec.Versions.First().Schema.OpenAPIV3Schema.Properties["spec"];
111113

@@ -352,13 +354,18 @@ public void Should_Throw_On_Not_Supported_Type()
352354
}
353355

354356
[Fact]
355-
public void Should_Add_GenericAdditionalPrinterColumns()
357+
public void Should_Use_PropertyName_From_JsonPropertyAttribute()
356358
{
357359
var crd = _testSpecEntity.CreateCrd();
358-
var apc = crd.Spec.Versions.First().AdditionalPrinterColumns;
359360

360-
apc.Should().NotBeNull();
361-
apc.Should().ContainSingle(def => def.JsonPath == ".metadata.creationTimestamp" && def.Name == "Age");
361+
var specProperties = crd.Spec.Versions.First().Schema.OpenAPIV3Schema.Properties["spec"];
362+
var propertyNameFromType = nameof(TestSpecEntitySpec.PropertyWithJsonAttribute);
363+
var propertyNameFromAttribute = typeof(TestSpecEntitySpec)
364+
.GetProperty(propertyNameFromType)
365+
?.GetCustomAttribute<JsonPropertyAttribute>()
366+
?.PropertyName;
367+
specProperties.Properties.Should().ContainKey(propertyNameFromAttribute?.ToCamelCase());
368+
specProperties.Properties.Should().NotContainKey(propertyNameFromType.ToCamelCase());
362369
}
363370

364371
[Fact]
@@ -383,5 +390,15 @@ public void Must_Not_Contain_Ignored_TopLevel_Properties()
383390
var specProperties = crd.Spec.Versions.First().Schema.OpenAPIV3Schema.Properties;
384391
specProperties.Should().NotContainKeys("metadata", "apiVersion", "kind");
385392
}
393+
394+
[Fact]
395+
public void Should_Add_GenericAdditionalPrinterColumns()
396+
{
397+
var crd = _testSpecEntity.CreateCrd();
398+
var apc = crd.Spec.Versions.First().AdditionalPrinterColumns;
399+
400+
apc.Should().NotBeNull();
401+
apc.Should().ContainSingle(def => def.JsonPath == ".metadata.creationTimestamp" && def.Name == "Age");
402+
}
386403
}
387404
}

tests/KubeOps.Test/TestEntities/TestSpecEntity.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using k8s.Models;
77
using KubeOps.Operator.Entities;
88
using KubeOps.Operator.Entities.Annotations;
9+
using Newtonsoft.Json;
910

1011
namespace KubeOps.Test.TestEntities
1112
{
@@ -124,6 +125,9 @@ public class TestSpecEntitySpec
124125
[EmbeddedResource]
125126
public V1ConfigMap KubernetesObject { get; set; } = new V1ConfigMap();
126127

128+
[JsonProperty(PropertyName = "NameFromAttribute")]
129+
public string PropertyWithJsonAttribute { get; set; } = string.Empty;
130+
127131
public enum TestSpecEnum
128132
{
129133
Value1,

0 commit comments

Comments
 (0)