migrate to record (#1665)

* migrate to record

* chore: update project files and clean up unused references

* refactor: convert classes to records and simplify constructors for IntOrString, ResourceQuantity, and V1Patch

* fix: define IsExternalInit to resolve CS0518 error in IntOrString

* refactor: change IntOrString and ResourceQuantity from records to structs, update implicit conversions, and simplify null checks

* refactor: add JsonPropertyName attribute to Value property in IntOrString struct

* refactor: simplify V1Patch constructor and improve argument validation

* refactor: remove unnecessary CultureInfo parameter in ToInt method

* Update src/KubernetesClient/Models/ResourceQuantity.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/KubernetesClient/Models/IntOrString.cs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Revert "Update src/KubernetesClient/Models/ResourceQuantity.cs"

This reverts commit 62b20a691554659e28d419067220dc1a0620133b.

* refactor: remove commented-out formatting check and simplify build command

* refactor: remove IValidate.cs from project references in Aot and Classic

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Boshi Lian
2025-09-22 14:20:13 -07:00
committed by GitHub
parent 96955064cb
commit 5de1c25cf1
34 changed files with 215 additions and 454 deletions

View File

@@ -17,12 +17,8 @@ jobs:
dotnet-version: | dotnet-version: |
8.0.x 8.0.x
9.0.x 9.0.x
# - name: Check Format
# # don't check formatting on Windows b/c of CRLF issues.
# if: matrix.os == 'ubuntu-latest'
# run: dotnet format --severity error --verify-no-changes --exclude ./src/KubernetesClient/generated/
- name: Build - name: Build
run: dotnet build --configuration Release -v detailed run: dotnet build --configuration Release
- name: Test - name: Test
run: dotnet test --configuration Release --collect:"Code Coverage;Format=Cobertura" --logger trx --results-directory TestResults --settings CodeCoverage.runsettings --no-build run: dotnet test --configuration Release --collect:"Code Coverage;Format=Cobertura" --logger trx --results-directory TestResults --settings CodeCoverage.runsettings --no-build
- name: Upload coverage to Codecov - name: Upload coverage to Codecov

View File

@@ -67,7 +67,15 @@ var old = JsonSerializer.SerializeToDocument(readCert, serializeOptions);
var replace = new List<V1CertificateSigningRequestCondition> var replace = new List<V1CertificateSigningRequestCondition>
{ {
new ("True", "Approved", DateTime.UtcNow, DateTime.UtcNow, "This certificate was approved by k8s client", "Approve"), new V1CertificateSigningRequestCondition
{
Type = "Approved",
Status = "True",
Reason = "Approve",
Message = "This certificate was approved by k8s client",
LastUpdateTime = DateTime.UtcNow,
LastTransitionTime = DateTime.UtcNow,
},
}; };
readCert.Status.Conditions = replace; readCert.Status.Conditions = replace;

View File

@@ -19,13 +19,13 @@ namespace customResource
} }
} }
public class CResourceSpec public record CResourceSpec
{ {
[JsonPropertyName("cityName")] [JsonPropertyName("cityName")]
public string CityName { get; set; } public string CityName { get; set; }
} }
public class CResourceStatus : V1Status public record CResourceStatus : V1Status
{ {
[JsonPropertyName("temperature")] [JsonPropertyName("temperature")]
public string Temperature { get; set; } public string Temperature { get; set; }

View File

@@ -23,7 +23,7 @@ var pod = new V1Pod
{ {
Requests = new Dictionary<string, ResourceQuantity>() Requests = new Dictionary<string, ResourceQuantity>()
{ {
["cpu"] = new ResourceQuantity("100m"), ["cpu"] = "100m",
}, },
}, },
}, },

View File

@@ -25,15 +25,13 @@
<Compile Include="..\KubernetesClient\Models\IMetadata.cs" /> <Compile Include="..\KubernetesClient\Models\IMetadata.cs" />
<Compile Include="..\KubernetesClient\Models\IntOrStringJsonConverter.cs" /> <Compile Include="..\KubernetesClient\Models\IntOrStringJsonConverter.cs" />
<Compile Include="..\KubernetesClient\Models\IntOrStringYamlConverter.cs" /> <Compile Include="..\KubernetesClient\Models\IntOrStringYamlConverter.cs" />
<Compile Include="..\KubernetesClient\Models\IntstrIntOrString.cs" /> <Compile Include="..\KubernetesClient\Models\IntOrString.cs" />
<Compile Include="..\KubernetesClient\Models\ISpec.cs" /> <Compile Include="..\KubernetesClient\Models\ISpec.cs" />
<Compile Include="..\KubernetesClient\Models\IStatus.cs" /> <Compile Include="..\KubernetesClient\Models\IStatus.cs" />
<Compile Include="..\KubernetesClient\IValidate.cs" />
<Compile Include="..\KubernetesClient\Models\KubernetesEntityAttribute.cs" /> <Compile Include="..\KubernetesClient\Models\KubernetesEntityAttribute.cs" />
<Compile Include="..\KubernetesClient\Models\KubernetesList.cs" /> <Compile Include="..\KubernetesClient\Models\KubernetesList.cs" />
<Compile Include="..\KubernetesClient\KubernetesObject.cs" /> <Compile Include="..\KubernetesClient\KubernetesObject.cs" />
<Compile Include="..\KubernetesClient\Models\ModelExtensions.cs" /> <Compile Include="..\KubernetesClient\Models\ModelExtensions.cs" />
<Compile Include="..\KubernetesClient\Models\ModelVersionConverter.cs" />
<Compile Include="..\KubernetesClient\Models\NodeMetrics.cs" /> <Compile Include="..\KubernetesClient\Models\NodeMetrics.cs" />
<Compile Include="..\KubernetesClient\Models\NodeMetricsList.cs" /> <Compile Include="..\KubernetesClient\Models\NodeMetricsList.cs" />
<Compile Include="..\KubernetesClient\Models\PodMetrics.cs" /> <Compile Include="..\KubernetesClient\Models\PodMetrics.cs" />

View File

@@ -0,0 +1,5 @@
// IntOrString.cs(7,36): error CS0518: Predefined type 'System.Runtime.CompilerServices.IsExternalInit' is not defined or imported
namespace System.Runtime.CompilerServices
{
internal static class IsExternalInit { }
}

View File

@@ -27,17 +27,15 @@
<Compile Include="..\KubernetesClient\Models\IMetadata.cs" /> <Compile Include="..\KubernetesClient\Models\IMetadata.cs" />
<Compile Include="..\KubernetesClient\Models\IntOrStringJsonConverter.cs" /> <Compile Include="..\KubernetesClient\Models\IntOrStringJsonConverter.cs" />
<Compile Include="..\KubernetesClient\Models\IntOrStringYamlConverter.cs" /> <Compile Include="..\KubernetesClient\Models\IntOrStringYamlConverter.cs" />
<Compile Include="..\KubernetesClient\Models\IntstrIntOrString.cs" /> <Compile Include="..\KubernetesClient\Models\IntOrString.cs" />
<Compile Include="..\KubernetesClient\Models\ISpec.cs" /> <Compile Include="..\KubernetesClient\Models\ISpec.cs" />
<Compile Include="..\KubernetesClient\Models\IStatus.cs" /> <Compile Include="..\KubernetesClient\Models\IStatus.cs" />
<Compile Include="..\KubernetesClient\IValidate.cs" />
<Compile Include="..\KubernetesClient\Models\KubernetesEntityAttribute.cs" /> <Compile Include="..\KubernetesClient\Models\KubernetesEntityAttribute.cs" />
<Compile Include="..\KubernetesClient\KubernetesJson.cs" /> <Compile Include="..\KubernetesClient\KubernetesJson.cs" />
<Compile Include="..\KubernetesClient\Models\KubernetesList.cs" /> <Compile Include="..\KubernetesClient\Models\KubernetesList.cs" />
<Compile Include="..\KubernetesClient\KubernetesObject.cs" /> <Compile Include="..\KubernetesClient\KubernetesObject.cs" />
<Compile Include="..\KubernetesClient\KubernetesYaml.cs" /> <Compile Include="..\KubernetesClient\KubernetesYaml.cs" />
<Compile Include="..\KubernetesClient\Models\ModelExtensions.cs" /> <Compile Include="..\KubernetesClient\Models\ModelExtensions.cs" />
<Compile Include="..\KubernetesClient\Models\ModelVersionConverter.cs" />
<Compile Include="..\KubernetesClient\Models\NodeMetrics.cs" /> <Compile Include="..\KubernetesClient\Models\NodeMetrics.cs" />
<Compile Include="..\KubernetesClient\Models\NodeMetricsList.cs" /> <Compile Include="..\KubernetesClient\Models\NodeMetricsList.cs" />
<Compile Include="..\KubernetesClient\Models\PodMetrics.cs" /> <Compile Include="..\KubernetesClient\Models\PodMetrics.cs" />

View File

@@ -1,13 +0,0 @@
namespace k8s
{
/// <summary>
/// Object that allows self validation
/// </summary>
public interface IValidate
{
/// <summary>
/// Validate the object.
/// </summary>
void Validate();
}
}

View File

@@ -0,0 +1,38 @@
namespace k8s.Models
{
[JsonConverter(typeof(IntOrStringJsonConverter))]
public struct IntOrString
{
public string? Value { get; private init; }
public static implicit operator IntOrString(int v)
{
return Convert.ToString(v);
}
public static implicit operator IntOrString(long v)
{
return Convert.ToString(v);
}
public static implicit operator string(IntOrString v)
{
return v.Value;
}
public static implicit operator IntOrString(string v)
{
return new IntOrString { Value = v };
}
public override string ToString()
{
return Value;
}
public int ToInt()
{
return int.Parse(Value);
}
}
}

View File

@@ -1,8 +1,8 @@
namespace k8s.Models namespace k8s.Models
{ {
internal sealed class IntOrStringJsonConverter : JsonConverter<IntstrIntOrString> internal sealed class IntOrStringJsonConverter : JsonConverter<IntOrString>
{ {
public override IntstrIntOrString Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) public override IntOrString Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{ {
switch (reader.TokenType) switch (reader.TokenType)
{ {
@@ -17,14 +17,14 @@ namespace k8s.Models
throw new NotSupportedException(); throw new NotSupportedException();
} }
public override void Write(Utf8JsonWriter writer, IntstrIntOrString value, JsonSerializerOptions options) public override void Write(Utf8JsonWriter writer, IntOrString value, JsonSerializerOptions options)
{ {
if (writer == null) if (writer == null)
{ {
throw new ArgumentNullException(nameof(writer)); throw new ArgumentNullException(nameof(writer));
} }
var s = value?.Value; var s = value.Value;
if (long.TryParse(s, out var intv)) if (long.TryParse(s, out var intv))
{ {

View File

@@ -7,7 +7,7 @@ namespace k8s.Models
{ {
public bool Accepts(Type type) public bool Accepts(Type type)
{ {
return type == typeof(IntstrIntOrString); return type == typeof(IntOrString);
} }
public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer) public object ReadYaml(IParser parser, Type type, ObjectDeserializer rootDeserializer)
@@ -21,7 +21,7 @@ namespace k8s.Models
return null; return null;
} }
return new IntstrIntOrString(scalar?.Value); return scalar?.Value;
} }
finally finally
{ {
@@ -34,8 +34,8 @@ namespace k8s.Models
public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerializer serializer) public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerializer serializer)
{ {
var obj = (IntstrIntOrString)value; var obj = (IntOrString)value;
emitter?.Emit(new YamlDotNet.Core.Events.Scalar(obj?.Value)); emitter?.Emit(new YamlDotNet.Core.Events.Scalar(obj.Value));
} }
} }
} }

View File

@@ -1,56 +0,0 @@
namespace k8s.Models
{
[JsonConverter(typeof(IntOrStringJsonConverter))]
public partial class IntstrIntOrString
{
public static implicit operator IntstrIntOrString(int v)
{
return new IntstrIntOrString(Convert.ToString(v));
}
public static implicit operator IntstrIntOrString(long v)
{
return new IntstrIntOrString(Convert.ToString(v));
}
public static implicit operator string(IntstrIntOrString v)
{
return v?.Value;
}
public static implicit operator IntstrIntOrString(string v)
{
return new IntstrIntOrString(v);
}
protected bool Equals(IntstrIntOrString other)
{
return string.Equals(Value, other?.Value);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
if (obj.GetType() != GetType())
{
return false;
}
return Equals((IntstrIntOrString)obj);
}
public override int GetHashCode()
{
return Value != null ? Value.GetHashCode() : 0;
}
}
}

View File

@@ -40,24 +40,5 @@ namespace k8s.Models
/// </summary> /// </summary>
[JsonPropertyName("metadata")] [JsonPropertyName("metadata")]
public V1ListMeta Metadata { get; set; } public V1ListMeta Metadata { get; set; }
/// <summary>
/// Validate the object.
/// </summary>
public void Validate()
{
if (Items == null)
{
throw new ArgumentNullException("Items");
}
if (Items != null)
{
foreach (var element in Items.OfType<IValidate>())
{
element.Validate();
}
}
}
} }
} }

View File

@@ -1,21 +0,0 @@
namespace k8s.Models;
public static class ModelVersionConverter
{
public interface IModelVersionConverter
{
TTo Convert<TFrom, TTo>(TFrom from);
}
public static IModelVersionConverter Converter { get; set; }
internal static TTo Convert<TFrom, TTo>(TFrom from)
{
if (Converter == null)
{
throw new InvalidOperationException("Converter is not set");
}
return Converter.Convert<TFrom, TTo>(from);
}
}

View File

@@ -54,7 +54,7 @@ namespace k8s.Models
/// cause implementors to also use a fixed point implementation. /// cause implementors to also use a fixed point implementation.
/// </summary> /// </summary>
[JsonConverter(typeof(ResourceQuantityJsonConverter))] [JsonConverter(typeof(ResourceQuantityJsonConverter))]
public partial class ResourceQuantity public struct ResourceQuantity
{ {
public enum SuffixFormat public enum SuffixFormat
{ {
@@ -97,39 +97,6 @@ namespace k8s.Models
return CanonicalizeString(); return CanonicalizeString();
} }
protected bool Equals(ResourceQuantity other)
{
return _unitlessValue.Equals(other?._unitlessValue);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
if (obj.GetType() != GetType())
{
return false;
}
return Equals((ResourceQuantity)obj);
}
public override int GetHashCode()
{
unchecked
{
return ((int)Format * 397) ^ _unitlessValue.GetHashCode();
}
}
// //
// CanonicalizeString = go version CanonicalizeBytes // CanonicalizeString = go version CanonicalizeBytes
// CanonicalizeBytes returns the canonical form of q and its suffix (see comment on Quantity). // CanonicalizeBytes returns the canonical form of q and its suffix (see comment on Quantity).
@@ -157,10 +124,9 @@ namespace k8s.Models
return Suffixer.AppendMaxSuffix(_unitlessValue, suffixFormat); return Suffixer.AppendMaxSuffix(_unitlessValue, suffixFormat);
} }
// ctor public ResourceQuantity(string v)
partial void CustomInit()
{ {
if (Value == null) if (v == null)
{ {
// No value has been defined, initialize to 0. // No value has been defined, initialize to 0.
_unitlessValue = new Fraction(0); _unitlessValue = new Fraction(0);
@@ -168,7 +134,7 @@ namespace k8s.Models
return; return;
} }
var value = Value.Trim(); var value = v.Trim();
var si = value.IndexOfAny(SuffixChars); var si = value.IndexOfAny(SuffixChars);
if (si == -1) if (si == -1)
@@ -188,6 +154,11 @@ namespace k8s.Models
} }
} }
public static implicit operator ResourceQuantity(string v)
{
return new ResourceQuantity(v);
}
private static bool HasMantissa(Fraction value) private static bool HasMantissa(Fraction value)
{ {
if (value.IsZero) if (value.IsZero)
@@ -200,7 +171,7 @@ namespace k8s.Models
public static implicit operator decimal(ResourceQuantity v) public static implicit operator decimal(ResourceQuantity v)
{ {
return v?.ToDecimal() ?? 0; return v.ToDecimal();
} }
public static implicit operator ResourceQuantity(decimal v) public static implicit operator ResourceQuantity(decimal v)

View File

@@ -8,16 +8,16 @@ namespace k8s.Models
switch (reader.TokenType) switch (reader.TokenType)
{ {
case JsonTokenType.Null: case JsonTokenType.Null:
return new ResourceQuantity(null); return null;
case JsonTokenType.Number: case JsonTokenType.Number:
if (reader.TryGetDouble(out var val)) if (reader.TryGetDouble(out var val))
{ {
return new ResourceQuantity(Convert.ToString(val)); return Convert.ToString(val);
} }
return reader.GetDecimal(); return reader.GetDecimal();
default: default:
return new ResourceQuantity(reader.GetString()); return reader.GetString();
} }
} }
@@ -28,7 +28,7 @@ namespace k8s.Models
throw new ArgumentNullException(nameof(writer)); throw new ArgumentNullException(nameof(writer));
} }
writer.WriteStringValue(value?.ToString()); writer.WriteStringValue(value.ToString());
} }
} }
} }

View File

@@ -21,7 +21,7 @@ namespace k8s.Models
return null; return null;
} }
return new ResourceQuantity(scalar?.Value); return scalar?.Value;
} }
finally finally
{ {
@@ -35,7 +35,7 @@ namespace k8s.Models
public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerializer serializer) public void WriteYaml(IEmitter emitter, object value, Type type, ObjectSerializer serializer)
{ {
var obj = (ResourceQuantity)value; var obj = (ResourceQuantity)value;
emitter?.Emit(new YamlDotNet.Core.Events.Scalar(obj?.ToString())); emitter?.Emit(new YamlDotNet.Core.Events.Scalar(obj.ToString()));
} }
} }
} }

View File

@@ -1,7 +1,7 @@
namespace k8s.Models namespace k8s.Models
{ {
[JsonConverter(typeof(V1PatchJsonConverter))] [JsonConverter(typeof(V1PatchJsonConverter))]
public partial class V1Patch public record V1Patch
{ {
public enum PatchType public enum PatchType
{ {
@@ -31,26 +31,21 @@ namespace k8s.Models
ApplyPatch, ApplyPatch,
} }
[JsonPropertyName("content")]
[JsonInclude]
public object Content { get; private set; }
public PatchType Type { get; private set; } public PatchType Type { get; private set; }
public V1Patch(object body, PatchType type) public V1Patch(object body, PatchType type)
{ {
Content = body; if (type == PatchType.Unknown)
{
throw new ArgumentException("patch type must be set", nameof(type));
}
Content = body ?? throw new ArgumentNullException(nameof(body), "object must be set");
Type = type; Type = type;
CustomInit();
}
partial void CustomInit()
{
if (Content == null)
{
throw new ArgumentNullException(nameof(Content), "object must be set");
}
if (Type == PatchType.Unknown)
{
throw new ArgumentException("patch type must be set", nameof(Type));
}
} }
} }
} }

View File

@@ -4,7 +4,7 @@ namespace k8s.Models
/// Partial implementation of the IMetadata interface /// Partial implementation of the IMetadata interface
/// to open this class up to ModelExtensions methods /// to open this class up to ModelExtensions methods
/// </summary> /// </summary>
public partial class V1PodTemplateSpec : IMetadata<V1ObjectMeta> public partial record V1PodTemplateSpec : IMetadata<V1ObjectMeta>
{ {
} }
} }

View File

@@ -1,6 +1,6 @@
namespace k8s.Models namespace k8s.Models
{ {
public partial class V1Status public partial record V1Status
{ {
internal sealed class V1StatusObjectViewConverter : JsonConverter<V1Status> internal sealed class V1StatusObjectViewConverter : JsonConverter<V1Status>
{ {

View File

@@ -2,7 +2,7 @@ using System.Net;
namespace k8s.Models namespace k8s.Models
{ {
public partial class V1Status public partial record V1Status
{ {
/// <summary>Converts a <see cref="V1Status"/> object into a short description of the status.</summary> /// <summary>Converts a <see cref="V1Status"/> object into a short description of the status.</summary>
/// <returns>string description of the status</returns> /// <returns>string description of the status</returns>

View File

@@ -78,6 +78,12 @@ namespace LibKubernetesGenerator
} }
if (definition.Format == "int-or-string")
{
return "IntOrString";
}
return schemaToNameMapCooked[definition]; return schemaToNameMapCooked[definition];
} }
} }

View File

@@ -57,8 +57,6 @@ namespace LibKubernetesGenerator
} }
} }
interfaces.Add("IValidate");
return string.Join(", ", interfaces); return string.Join(", ", interfaces);
} }
@@ -68,7 +66,7 @@ namespace LibKubernetesGenerator
if (init == "true" && !parameter.IsRequired) if (init == "true" && !parameter.IsRequired)
{ {
name += " = null"; name += " = default";
} }
return name; return name;

View File

@@ -58,12 +58,10 @@ namespace LibKubernetesGenerator
builder.RegisterType<ScriptObjectFactory>() builder.RegisterType<ScriptObjectFactory>()
; ;
builder.RegisterType<ModelExtGenerator>();
builder.RegisterType<SourceGenerationContextGenerator>(); builder.RegisterType<SourceGenerationContextGenerator>();
builder.RegisterType<ModelGenerator>(); builder.RegisterType<ModelGenerator>();
builder.RegisterType<ApiGenerator>(); builder.RegisterType<ApiGenerator>();
builder.RegisterType<ClientSetGenerator>(); builder.RegisterType<ClientSetGenerator>();
builder.RegisterType<VersionConverterStubGenerator>();
builder.RegisterType<VersionGenerator>(); builder.RegisterType<VersionGenerator>();
return builder.Build(); return builder.Build();
@@ -79,9 +77,7 @@ namespace LibKubernetesGenerator
container.Resolve<VersionGenerator>().Generate(swagger, ctx); container.Resolve<VersionGenerator>().Generate(swagger, ctx);
container.Resolve<ModelGenerator>().Generate(swagger, ctx); container.Resolve<ModelGenerator>().Generate(swagger, ctx);
container.Resolve<ModelExtGenerator>().Generate(swagger, ctx);
container.Resolve<SourceGenerationContextGenerator>().Generate(swagger, ctx); container.Resolve<SourceGenerationContextGenerator>().Generate(swagger, ctx);
container.Resolve<VersionConverterStubGenerator>().Generate(swagger, ctx);
container.Resolve<ApiGenerator>().Generate(swagger, ctx); container.Resolve<ApiGenerator>().Generate(swagger, ctx);
container.Resolve<ClientSetGenerator>().Generate(swagger, ctx); container.Resolve<ClientSetGenerator>().Generate(swagger, ctx);
}); });

View File

@@ -1,36 +0,0 @@
using Microsoft.CodeAnalysis;
using NSwag;
using System.Collections.Generic;
using System.Linq;
namespace LibKubernetesGenerator
{
internal class ModelExtGenerator
{
private readonly ClassNameHelper classNameHelper;
private readonly ScriptObjectFactory scriptObjectFactory;
public ModelExtGenerator(ClassNameHelper classNameHelper, ScriptObjectFactory scriptObjectFactory)
{
this.classNameHelper = classNameHelper;
this.scriptObjectFactory = scriptObjectFactory;
}
public void Generate(OpenApiDocument swagger, IncrementalGeneratorPostInitializationContext context)
{
// Generate the interface declarations
var skippedTypes = new HashSet<string> { "V1WatchEvent" };
var definitions = swagger.Definitions.Values
.Where(
d => d.ExtensionData != null
&& d.ExtensionData.ContainsKey("x-kubernetes-group-version-kind")
&& !skippedTypes.Contains(classNameHelper.GetClassName(d)));
var sc = scriptObjectFactory.CreateScriptObject();
sc.SetValue("definitions", definitions, true);
context.RenderToContext("ModelExtensions.cs.template", sc, "ModelExtensions.g.cs");
}
}
}

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using NSwag; using NSwag;
@@ -18,15 +19,49 @@ namespace LibKubernetesGenerator
{ {
var sc = scriptObjectFactory.CreateScriptObject(); var sc = scriptObjectFactory.CreateScriptObject();
var genSkippedTypes = new HashSet<string>
{
"IntOrString",
"ResourceQuantity",
"V1Patch",
};
var extSkippedTypes = new HashSet<string>
{
"V1WatchEvent",
};
var typeOverrides = new Dictionary<string, string>
{
// not used at the moment
};
foreach (var kv in swagger.Definitions) foreach (var kv in swagger.Definitions)
{ {
var def = kv.Value; var def = kv.Value;
var clz = classNameHelper.GetClassNameForSchemaDefinition(def); var clz = classNameHelper.GetClassNameForSchemaDefinition(def);
if (genSkippedTypes.Contains(clz))
{
continue;
}
var hasExt = def.ExtensionData != null
&& def.ExtensionData.ContainsKey("x-kubernetes-group-version-kind")
&& !extSkippedTypes.Contains(clz);
var typ = "record";
if (typeOverrides.TryGetValue(clz, out var to))
{
typ = to;
}
sc.SetValue("clz", clz, true); sc.SetValue("clz", clz, true);
sc.SetValue("def", def, true); sc.SetValue("def", def, true);
sc.SetValue("properties", def.Properties.Values, true); sc.SetValue("properties", def.Properties.Values, true);
sc.SetValue("typ", typ, true);
sc.SetValue("hasExt", hasExt, true);
context.RenderToContext("Model.cs.template", sc, $"Models_{clz}.g.cs"); context.RenderToContext("Model.cs.template", sc, $"Models_{clz}.g.cs");
} }

View File

@@ -1,70 +0,0 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using NSwag;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace LibKubernetesGenerator
{
internal class VersionConverterStubGenerator
{
private readonly ClassNameHelper classNameHelper;
public VersionConverterStubGenerator(ClassNameHelper classNameHelper)
{
this.classNameHelper = classNameHelper;
}
public void Generate(OpenApiDocument swagger, IncrementalGeneratorPostInitializationContext context)
{
var allGeneratedModelClassNames = new List<string>();
foreach (var kv in swagger.Definitions)
{
var def = kv.Value;
var clz = classNameHelper.GetClassNameForSchemaDefinition(def);
allGeneratedModelClassNames.Add(clz);
}
var versionRegex = @"(^V|v)[0-9]+((alpha|beta)[0-9]+)?";
var typePairs = allGeneratedModelClassNames
.OrderBy(x => x)
.Select(x => new
{
Version = Regex.Match(x, versionRegex).Value?.ToLower(),
Kinda = Regex.Replace(x, versionRegex, string.Empty),
Type = x,
})
.Where(x => !string.IsNullOrEmpty(x.Version))
.GroupBy(x => x.Kinda)
.Where(x => x.Count() > 1)
.SelectMany(x =>
x.SelectMany((value, index) => x.Skip(index + 1), (first, second) => new { first, second }))
.OrderBy(x => x.first.Kinda)
.ThenBy(x => x.first.Version)
.Select(x => (x.first.Type, x.second.Type))
.ToList();
var sbmodel = new StringBuilder(@"// <auto-generated />
namespace k8s.Models;
");
foreach (var (t0, t1) in typePairs)
{
sbmodel.AppendLine($@"
public partial class {t0}
{{
public static explicit operator {t0}({t1} s) => ModelVersionConverter.Convert<{t1}, {t0}>(s);
}}
public partial class {t1}
{{
public static explicit operator {t1}({t0} s) => ModelVersionConverter.Convert<{t0}, {t1}>(s);
}}");
}
context.AddSource($"ModelOperators.g.cs", SourceText.From(sbmodel.ToString(), Encoding.UTF8));
}
}
}

View File

@@ -4,92 +4,29 @@
// regenerated. // regenerated.
// </auto-generated> // </auto-generated>
namespace k8s.Models namespace k8s.Models;
/// <summary>
/// {{ToXmlDoc def.description}}
/// </summary>
{{ if hasExt }}
[KubernetesEntity(Group=KubeGroup, Kind=KubeKind, ApiVersion=KubeApiVersion, PluralName=KubePluralName)]
{{ end }}
public partial {{typ}} {{clz}} {{ if hasExt }} : {{ GetInterfaceName def }} {{ end }}
{ {
{{ if hasExt}}
public const string KubeApiVersion = "{{ GetApiVersion def }}";
public const string KubeKind = "{{ GetKind def }}";
public const string KubeGroup = "{{ GetGroup def }}";
public const string KubePluralName = "{{ GetPlural def }}";
{{ end }}
{{ for property in properties }}
/// <summary> /// <summary>
/// {{ToXmlDoc def.description}} /// {{ToXmlDoc property.description}}
/// </summary> /// </summary>
public partial class {{clz}} [JsonPropertyName("{{property.name}}")]
{ public {{ if property.IsRequired }} required {{ end }} {{GetDotNetType property}} {{GetDotNetName property.name "field"}} { get; set; }
/// <summary> {{ end }}
/// Initializes a new instance of the {{GetClassName def}} class.
/// </summary>
public {{clz}}()
{
CustomInit();
}
/// <summary>
/// Initializes a new instance of the {{GetClassName def}} class.
/// </summary>
{{ for property in properties }}
{{ if property.IsRequired }}
/// <param name="{{GetDotNetName property.name "fieldctor"}}">
/// {{ToXmlDoc property.description}}
/// </param>
{{ end }}
{{ end }}
{{ for property in properties }}
{{ if !property.IsRequired }}
/// <param name="{{GetDotNetName property.name "fieldctor"}}">
/// {{ToXmlDoc property.description}}
/// </param>
{{ end }}
{{ end }}
public {{clz}}({{GetModelCtorParam def}})
{
{{ for property in properties }}
{{GetDotNetName property.name "field"}} = {{GetDotNetName property.name "fieldctor"}};
{{ end }}
CustomInit();
}
/// <summary>
/// An initialization method that performs custom operations like setting defaults
/// </summary>
partial void CustomInit();
{{ for property in properties }}
/// <summary>
/// {{ToXmlDoc property.description}}
/// </summary>
[JsonPropertyName("{{property.name}}")]
public {{GetDotNetType property}} {{GetDotNetName property.name "field"}} { get; set; }
{{ end }}
/// <summary>
/// Validate the object.
/// </summary>
public virtual void Validate()
{
{{ for property in properties }}
{{if IfType property "object" }}
{{ if property.IsRequired }}
if ({{GetDotNetName property.name "field"}} == null)
{
throw new ArgumentNullException("{{GetDotNetName property.name "field"}}");
}
{{ end }}
{{ end }}
{{ end }}
{{ for property in properties }}
{{if IfType property "object" }}
{{GetDotNetName property.name "field"}}?.Validate();
{{ end }}
{{if IfType property "objectarray" }}
if ({{GetDotNetName property.name "field"}} != null){
foreach(var obj in {{GetDotNetName property.name "field"}})
{
obj.Validate();
}
}
{{ end }}
{{ end }}
}
}
} }

View File

@@ -1,17 +0,0 @@
// <auto-generated>
// Code generated by https://github.com/kubernetes-client/csharp/tree/master/src/LibKubernetesGenerator
// Changes may cause incorrect behavior and will be lost if the code is regenerated.
// </auto-generated>
namespace k8s.Models
{
{{ for definition in definitions }}
[KubernetesEntity(Group=KubeGroup, Kind=KubeKind, ApiVersion=KubeApiVersion, PluralName=KubePluralName)]
public partial class {{ GetClassName definition }} : {{ GetInterfaceName definition }}
{
public const string KubeApiVersion = "{{ GetApiVersion definition }}";
public const string KubeKind = "{{ GetKind definition }}";
public const string KubeGroup = "{{ GetGroup definition }}";
public const string KubePluralName = "{{ GetPlural definition }}";
}
{{ end }}
}

View File

@@ -172,27 +172,33 @@ namespace k8s.E2E
using var kubernetes = CreateClient(); using var kubernetes = CreateClient();
await kubernetes.CoreV1.CreateNamespacedEventAsync( await kubernetes.CoreV1.CreateNamespacedEventAsync(
new Corev1Event( new Corev1Event
new V1ObjectReference( {
"v1alpha1", InvolvedObject = new V1ObjectReference
kind: "Test", {
name: "test", ApiVersion = "v1alpha1",
namespaceProperty: "default", Kind = "Test",
resourceVersion: "1", Name = "test",
uid: "1"), NamespaceProperty = "default",
new V1ObjectMeta() ResourceVersion = "1",
Uid = "1",
},
Metadata = new V1ObjectMeta
{ {
GenerateName = "started-", GenerateName = "started-",
}, },
action: "STARTED", Action = "STARTED",
type: "Normal", Type = "Normal",
reason: "STARTED", Reason = "STARTED",
message: "Started", Message = "Started",
eventTime: DateTime.Now, EventTime = DateTime.Now,
firstTimestamp: DateTime.Now, FirstTimestamp = DateTime.Now,
lastTimestamp: DateTime.Now, LastTimestamp = DateTime.Now,
reportingComponent: "37", ReportingComponent = "37",
reportingInstance: "38"), "default").ConfigureAwait(false); ReportingInstance = "38",
},
"default"
).ConfigureAwait(false);
} }
[MinikubeFact] [MinikubeFact]

View File

@@ -451,27 +451,33 @@ namespace k8s.E2E
using var kubernetes = CreateClient(); using var kubernetes = CreateClient();
await kubernetes.CoreV1.CreateNamespacedEventAsync( await kubernetes.CoreV1.CreateNamespacedEventAsync(
new Corev1Event( new Corev1Event
new V1ObjectReference( {
"v1alpha1", Metadata = new V1ObjectMeta
kind: "Test",
name: "test",
namespaceProperty: "default",
resourceVersion: "1",
uid: "1"),
new V1ObjectMeta()
{ {
GenerateName = "started-", GenerateName = "started-",
NamespaceProperty = "default",
}, },
action: "STARTED", InvolvedObject = new V1ObjectReference
type: "Normal", {
reason: "STARTED", ApiVersion = "v1alpha1",
message: "Started", Kind = "Test",
eventTime: DateTime.Now, Name = "test",
firstTimestamp: DateTime.Now, NamespaceProperty = "default",
lastTimestamp: DateTime.Now, ResourceVersion = "1",
reportingComponent: "37", Uid = "1",
reportingInstance: "38"), "default").ConfigureAwait(false); },
Action = "STARTED",
Type = "Normal",
Reason = "STARTED",
Message = "Started",
EventTime = DateTime.Now,
FirstTimestamp = DateTime.Now,
LastTimestamp = DateTime.Now,
ReportingComponent = "37",
ReportingInstance = "38",
},
"default").ConfigureAwait(false);
} }
[MinikubeFact] [MinikubeFact]

View File

@@ -7,7 +7,7 @@ using Xunit;
namespace k8s.tests; namespace k8s.tests;
public class BasicTests public class SimpleTests
{ {
// TODO: fail to setup asp.net core 6 on net48 // TODO: fail to setup asp.net core 6 on net48
private class DummyHttpServer : System.IDisposable private class DummyHttpServer : System.IDisposable

View File

@@ -10,13 +10,13 @@ namespace k8s.Tests
{ {
{ {
var v = 123; var v = 123;
IntstrIntOrString intorstr = v; IntOrString intorstr = v;
Assert.Equal("123", KubernetesJson.Serialize(intorstr)); Assert.Equal("123", KubernetesJson.Serialize(intorstr));
} }
{ {
IntstrIntOrString intorstr = "12%"; IntOrString intorstr = "12%";
Assert.Equal("\"12%\"", KubernetesJson.Serialize(intorstr)); Assert.Equal("\"12%\"", KubernetesJson.Serialize(intorstr));
} }
} }
@@ -25,12 +25,12 @@ namespace k8s.Tests
public void Deserialize() public void Deserialize()
{ {
{ {
var v = KubernetesJson.Deserialize<IntstrIntOrString>("1234"); var v = KubernetesJson.Deserialize<IntOrString>("1234");
Assert.Equal("1234", v.Value); Assert.Equal("1234", v.Value);
} }
{ {
var v = KubernetesJson.Deserialize<IntstrIntOrString>("\"12%\""); var v = KubernetesJson.Deserialize<IntOrString>("\"12%\"");
Assert.Equal("12%", v.Value); Assert.Equal("12%", v.Value);
} }
} }

View File

@@ -33,7 +33,7 @@ metadata:
} }
#pragma warning disable CA1812 // Class is used for YAML deserialization tests #pragma warning disable CA1812 // Class is used for YAML deserialization tests
private class MyPod : V1Pod private record MyPod : V1Pod
{ {
} }
#pragma warning restore CA1812 #pragma warning restore CA1812
@@ -531,7 +531,7 @@ spec:
var obj = new V1Service var obj = new V1Service
{ {
Kind = "Service", Kind = "Service",
Metadata = new V1ObjectMeta(labels: labels, name: "test-svc"), Metadata = new V1ObjectMeta { Name = "test-svc", Labels = labels },
ApiVersion = "v1", ApiVersion = "v1",
Spec = new V1ServiceSpec Spec = new V1ServiceSpec
{ {