From b267f3d15f2c62e89abac6ee4ec219677e10c61e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 26 Dec 2025 08:17:59 +0000 Subject: [PATCH 1/4] Initial plan From 98c52e093688d28da513c1fee1a6957ca53621ee Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 26 Dec 2025 08:31:22 +0000 Subject: [PATCH 2/4] Enable nullable reference types and update code generator Co-authored-by: tg123 <170430+tg123@users.noreply.github.com> --- Directory.Build.props | 1 + src/LibKubernetesGenerator/TypeHelper.cs | 32 +++++++++++++----------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 2a5ed0116..38297f8ce 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -29,6 +29,7 @@ true portable 13.0 + enable diff --git a/src/LibKubernetesGenerator/TypeHelper.cs b/src/LibKubernetesGenerator/TypeHelper.cs index db27dab97..6676bf039 100644 --- a/src/LibKubernetesGenerator/TypeHelper.cs +++ b/src/LibKubernetesGenerator/TypeHelper.cs @@ -81,7 +81,7 @@ private string GetDotNetType(JsonObjectType jsonType, string name, bool required switch (format) { case "byte": - return "byte[]"; + return required ? "byte[]" : "byte[]?"; case "date-time": // eventTime is required but should be optional, see https://github.com/kubernetes-client/csharp/issues/1197 @@ -100,58 +100,61 @@ private string GetDotNetType(JsonObjectType jsonType, string name, bool required } } - return "string"; + return required ? "string" : "string?"; case JsonObjectType.Object: - return "object"; + return required ? "object" : "object?"; default: throw new NotSupportedException(); } } - private string GetDotNetType(JsonSchema schema, JsonSchemaProperty parent) + private string GetDotNetType(JsonSchema schema, JsonSchemaProperty parent, bool isCollectionItem = false) { if (schema != null) { if (schema.IsArray) { - return $"IList<{GetDotNetType(schema.Item, parent)}>"; + return $"IList<{GetDotNetType(schema.Item, parent, isCollectionItem: true)}>?"; } if (schema.IsDictionary && schema.AdditionalPropertiesSchema != null) { - return $"IDictionary"; + return $"IDictionary?"; } if (schema?.Reference != null) { - return classNameHelper.GetClassNameForSchemaDefinition(schema.Reference); + var typeName = classNameHelper.GetClassNameForSchemaDefinition(schema.Reference); + // Collection items are always non-nullable, unless we're at the root level + return (isCollectionItem || parent.IsRequired) ? typeName : typeName + "?"; } if (schema != null) { - return GetDotNetType(schema.Type, parent.Name, parent.IsRequired, schema.Format); + return GetDotNetType(schema.Type, parent.Name, isCollectionItem || parent.IsRequired, schema.Format); } } - return GetDotNetType(parent.Type, parent.Name, parent.IsRequired, parent.Format); + return GetDotNetType(parent.Type, parent.Name, isCollectionItem || parent.IsRequired, parent.Format); } public string GetDotNetType(JsonSchemaProperty p) { if (p.Reference != null) { - return classNameHelper.GetClassNameForSchemaDefinition(p.Reference); + var typeName = classNameHelper.GetClassNameForSchemaDefinition(p.Reference); + return p.IsRequired ? typeName : typeName + "?"; } if (p.IsArray) { - // getType - return $"IList<{GetDotNetType(p.Item, p)}>"; + // getType - items in arrays are non-nullable + return $"IList<{GetDotNetType(p.Item, p, isCollectionItem: true)}>?"; } if (p.IsDictionary && p.AdditionalPropertiesSchema != null) { - return $"IDictionary"; + return $"IDictionary?"; } return GetDotNetType(p.Type, p.Name, p.IsRequired, p.Format); @@ -161,7 +164,8 @@ public string GetDotNetTypeOpenApiParameter(OpenApiParameter parameter) { if (parameter.Schema?.Reference != null) { - return classNameHelper.GetClassNameForSchemaDefinition(parameter.Schema.Reference); + var typeName = classNameHelper.GetClassNameForSchemaDefinition(parameter.Schema.Reference); + return parameter.IsRequired ? typeName : typeName + "?"; } else if (parameter.Schema != null) { From 0cfa7ad65aec83bb4f6909d63e9e0d7331694b07 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 26 Dec 2025 08:38:40 +0000 Subject: [PATCH 3/4] Add nullable annotations to TypeHelper and create tests Co-authored-by: tg123 <170430+tg123@users.noreply.github.com> --- src/LibKubernetesGenerator/TypeHelper.cs | 8 +- .../NullableReferenceTypesTests.cs | 95 +++++++++++++++++++ 2 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 tests/KubernetesClient.Tests/NullableReferenceTypesTests.cs diff --git a/src/LibKubernetesGenerator/TypeHelper.cs b/src/LibKubernetesGenerator/TypeHelper.cs index 6676bf039..4bb8481b9 100644 --- a/src/LibKubernetesGenerator/TypeHelper.cs +++ b/src/LibKubernetesGenerator/TypeHelper.cs @@ -23,7 +23,7 @@ public void RegisterHelper(ScriptObject scriptObject) scriptObject.Import(nameof(IfType), new Func(IfType)); } - private string GetDotNetType(JsonObjectType jsonType, string name, bool required, string format) + private string GetDotNetType(JsonObjectType jsonType, string name, bool required, string? format) { if (name == "pretty" && !required) { @@ -108,13 +108,13 @@ private string GetDotNetType(JsonObjectType jsonType, string name, bool required } } - private string GetDotNetType(JsonSchema schema, JsonSchemaProperty parent, bool isCollectionItem = false) + private string GetDotNetType(JsonSchema? schema, JsonSchemaProperty parent, bool isCollectionItem = false) { if (schema != null) { if (schema.IsArray) { - return $"IList<{GetDotNetType(schema.Item, parent, isCollectionItem: true)}>?"; + return $"IList<{GetDotNetType(schema.Item!, parent, isCollectionItem: true)}>?"; } if (schema.IsDictionary && schema.AdditionalPropertiesSchema != null) @@ -149,7 +149,7 @@ public string GetDotNetType(JsonSchemaProperty p) if (p.IsArray) { // getType - items in arrays are non-nullable - return $"IList<{GetDotNetType(p.Item, p, isCollectionItem: true)}>?"; + return $"IList<{GetDotNetType(p.Item!, p, isCollectionItem: true)}>?"; } if (p.IsDictionary && p.AdditionalPropertiesSchema != null) diff --git a/tests/KubernetesClient.Tests/NullableReferenceTypesTests.cs b/tests/KubernetesClient.Tests/NullableReferenceTypesTests.cs new file mode 100644 index 000000000..a1f142932 --- /dev/null +++ b/tests/KubernetesClient.Tests/NullableReferenceTypesTests.cs @@ -0,0 +1,95 @@ +using System.Collections.Generic; +using k8s.Models; +using Xunit; + +namespace k8s.Tests +{ + public class NullableReferenceTypesTests + { + [Fact] + public void ContainerVolumeMountsIsNullableProperty() + { + // Arrange & Act + var container = new V1Container(); + + // Assert + // VolumeMounts should be null by default (nullable property) + Assert.Null(container.VolumeMounts); + + // This should not throw NullReferenceException anymore - users should check for null + // container.VolumeMounts.Add(new V1VolumeMount()); // This would throw + + // Proper usage: Initialize the list first + container.VolumeMounts = new List + { + new V1VolumeMount(), + }; + + Assert.NotNull(container.VolumeMounts); + Assert.Single(container.VolumeMounts); + } + + [Fact] + public void ContainerNameIsRequiredProperty() + { + // Arrange & Act + var container = new V1Container + { + Name = "test-container", + }; + + // Assert + // Name is a required property (non-nullable string) + Assert.NotNull(container.Name); + Assert.Equal("test-container", container.Name); + } + + [Fact] + public void ContainerImageIsOptionalProperty() + { + // Arrange & Act + var container = new V1Container(); + + // Assert + // Image is optional (nullable string) + Assert.Null(container.Image); + + container.Image = "nginx:latest"; + Assert.Equal("nginx:latest", container.Image); + } + + [Fact] + public void ContainerLifecycleIsOptionalComplexProperty() + { + // Arrange & Act + var container = new V1Container(); + + // Assert + // Lifecycle is optional (nullable reference type) + Assert.Null(container.Lifecycle); + + container.Lifecycle = new V1Lifecycle(); + Assert.NotNull(container.Lifecycle); + } + + [Fact] + public void ContainerCollectionItemsAreNonNullable() + { + // Arrange + var container = new V1Container + { + // Initialize the list - the list itself can be null, but items cannot be null + VolumeMounts = new List + { + new V1VolumeMount { Name = "vol1", MountPath = "/data", }, + new V1VolumeMount { Name = "vol2", MountPath = "/config", }, + }, + }; + + // Act & Assert + Assert.NotNull(container.VolumeMounts); + Assert.Equal(2, container.VolumeMounts.Count); + Assert.All(container.VolumeMounts, vm => Assert.NotNull(vm)); + } + } +} From 096c19f7cc65f40876bae7bc224fb53d8620c782 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 26 Dec 2025 08:45:10 +0000 Subject: [PATCH 4/4] Address code review feedback - add null checks Co-authored-by: tg123 <170430+tg123@users.noreply.github.com> --- src/LibKubernetesGenerator/TypeHelper.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/LibKubernetesGenerator/TypeHelper.cs b/src/LibKubernetesGenerator/TypeHelper.cs index 4bb8481b9..5d4bb5e28 100644 --- a/src/LibKubernetesGenerator/TypeHelper.cs +++ b/src/LibKubernetesGenerator/TypeHelper.cs @@ -112,9 +112,9 @@ private string GetDotNetType(JsonSchema? schema, JsonSchemaProperty parent, bool { if (schema != null) { - if (schema.IsArray) + if (schema.IsArray && schema.Item != null) { - return $"IList<{GetDotNetType(schema.Item!, parent, isCollectionItem: true)}>?"; + return $"IList<{GetDotNetType(schema.Item, parent, isCollectionItem: true)}>?"; } if (schema.IsDictionary && schema.AdditionalPropertiesSchema != null) @@ -122,17 +122,14 @@ private string GetDotNetType(JsonSchema? schema, JsonSchemaProperty parent, bool return $"IDictionary?"; } - if (schema?.Reference != null) + if (schema.Reference != null) { var typeName = classNameHelper.GetClassNameForSchemaDefinition(schema.Reference); // Collection items are always non-nullable, unless we're at the root level return (isCollectionItem || parent.IsRequired) ? typeName : typeName + "?"; } - if (schema != null) - { - return GetDotNetType(schema.Type, parent.Name, isCollectionItem || parent.IsRequired, schema.Format); - } + return GetDotNetType(schema.Type, parent.Name, isCollectionItem || parent.IsRequired, schema.Format); } return GetDotNetType(parent.Type, parent.Name, isCollectionItem || parent.IsRequired, parent.Format); @@ -146,10 +143,10 @@ public string GetDotNetType(JsonSchemaProperty p) return p.IsRequired ? typeName : typeName + "?"; } - if (p.IsArray) + if (p.IsArray && p.Item != null) { // getType - items in arrays are non-nullable - return $"IList<{GetDotNetType(p.Item!, p, isCollectionItem: true)}>?"; + return $"IList<{GetDotNetType(p.Item, p, isCollectionItem: true)}>?"; } if (p.IsDictionary && p.AdditionalPropertiesSchema != null)