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..5d4bb5e28 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)
{
@@ -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,58 @@ 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)
+ if (schema.IsArray && schema.Item != null)
{
- 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)
+ 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)
+ if (p.IsArray && p.Item != null)
{
- // 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 +161,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)
{
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));
+ }
+ }
+}