Add AddJsonOptions to KubernetesClientConfiguration and KubernetesJson (#1257)

* Use camelCase policy when serializing enums

* Revert "Use camelCase policy when serializing enums"

This reverts commit 467f49d8734bcbd6aabb87447fbd7d21840c4c48.

* Add jonSerializerOptions to GenericClient

* Add jsonSerializerOptions param

* Revert pass deserialization options in GenericClient

* Add JsonSerializerOptions to KubernetesClientConfiguration

* Use user JsonSerializerOptions in SendRequest and CreateResultAsync

* Improve comment

* Remove JsonSerializerOptions

* Cosmetic

* Add AddJsonOptions

* Fix example

* Fix test

* Add test

* Add summary

* Improve summary

* Remove configure from Kubernetes ctor and tests

* Add AddJsonOptions to config and test

* Support per client json serializer options

* Add ConfigureAwait for tests

* Check for nullargument
This commit is contained in:
Luis Cantero
2023-04-04 22:35:39 +02:00
committed by GitHub
parent c7ff3564b5
commit 2af57cade8
8 changed files with 150 additions and 19 deletions

View File

@@ -1,7 +1,6 @@
using k8s;
using Prometheus;
using System;
using System.Net.Http;
using System.Threading;
namespace prom
@@ -12,7 +11,7 @@ namespace prom
{
var config = KubernetesClientConfiguration.BuildDefaultConfig();
var handler = new PrometheusHandler();
IKubernetes client = new Kubernetes(config, new DelegatingHandler[] { handler });
IKubernetes client = new Kubernetes(config, handler);
var server = new MetricServer(hostname: "localhost", port: 1234);
server.Start();

View File

@@ -66,20 +66,36 @@ namespace k8s
JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
}
public static TValue Deserialize<TValue>(string json)
/// <summary>
/// Configures <see cref="JsonSerializerOptions"/> for the <see cref="JsonSerializer"/>.
/// To override existing converters, add them to the top of the <see cref="JsonSerializerOptions.Converters"/> list
/// e.g. as follows: <code>options.Converters.Insert(index: 0, new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));</code>
/// </summary>
/// <param name="configure">An <see cref="Action"/> to configure the <see cref="JsonSerializerOptions"/>.</param>
public static void AddJsonOptions(Action<JsonSerializerOptions> configure)
{
return JsonSerializer.Deserialize<TValue>(json, JsonSerializerOptions);
if (configure is null)
{
throw new ArgumentNullException(nameof(configure));
}
configure(JsonSerializerOptions);
}
public static TValue Deserialize<TValue>(Stream json)
public static TValue Deserialize<TValue>(string json, JsonSerializerOptions jsonSerializerOptions = null)
{
return JsonSerializer.Deserialize<TValue>(json, JsonSerializerOptions);
return JsonSerializer.Deserialize<TValue>(json, jsonSerializerOptions ?? JsonSerializerOptions);
}
public static TValue Deserialize<TValue>(Stream json, JsonSerializerOptions jsonSerializerOptions = null)
{
return JsonSerializer.Deserialize<TValue>(json, jsonSerializerOptions ?? JsonSerializerOptions);
}
public static string Serialize(object value)
public static string Serialize(object value, JsonSerializerOptions jsonSerializerOptions = null)
{
return JsonSerializer.Serialize(value, JsonSerializerOptions);
return JsonSerializer.Serialize(value, jsonSerializerOptions ?? JsonSerializerOptions);
}
}
}

View File

@@ -9,6 +9,8 @@ namespace k8s
{
public partial class Kubernetes
{
private readonly JsonSerializerOptions jsonSerializerOptions;
/// <summary>
/// Initializes a new instance of the <see cref="Kubernetes" /> class.
/// </summary>
@@ -27,6 +29,7 @@ namespace k8s
CreateHttpClient(handlers, config);
InitializeFromConfig(config);
HttpClientTimeout = config.HttpClientTimeout;
jsonSerializerOptions = config.JsonSerializerOptions;
#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER
DisableHttp2 = config.DisableHttp2;
#endif
@@ -155,8 +158,6 @@ namespace k8s
};
}
/// <summary>
/// Set credentials for the Client
/// </summary>

View File

@@ -87,7 +87,7 @@ namespace k8s
using (Stream stream = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false))
#endif
{
result.Body = KubernetesJson.Deserialize<T>(stream);
result.Body = KubernetesJson.Deserialize<T>(stream, jsonSerializerOptions);
}
}
catch (JsonException)
@@ -126,7 +126,7 @@ namespace k8s
if (body != null)
{
var requestContent = KubernetesJson.Serialize(body);
var requestContent = KubernetesJson.Serialize(body, jsonSerializerOptions);
httpRequest.Content = new StringContent(requestContent, System.Text.Encoding.UTF8);
httpRequest.Content.Headers.ContentType = GetHeader(body);
return SendRequestRaw(requestContent, httpRequest, cancellationToken);

View File

@@ -524,7 +524,7 @@ namespace k8s
process.StartInfo.RedirectStandardError = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.CreateNoWindow = true;
return process;
}

View File

@@ -9,6 +9,8 @@ namespace k8s
/// </summary>
public partial class KubernetesClientConfiguration
{
private JsonSerializerOptions jsonSerializerOptions;
/// <summary>
/// Gets current namespace
/// </summary>
@@ -108,5 +110,38 @@ namespace k8s
/// Do not use http2 even it is available
/// </summary>
public bool DisableHttp2 { get; set; } = false;
/// <summary>
/// Options for the <see cref="JsonSerializer"/> to override the default ones.
/// </summary>
public JsonSerializerOptions JsonSerializerOptions
{
get
{
// If not yet set, use defaults from KubernetesJson.
if (jsonSerializerOptions is null)
{
KubernetesJson.AddJsonOptions(options =>
{
jsonSerializerOptions = new JsonSerializerOptions(options);
});
}
return jsonSerializerOptions;
}
private set => jsonSerializerOptions = value;
}
/// <inheritdoc cref="KubernetesJson.AddJsonOptions(Action{JsonSerializerOptions})"/>
public void AddJsonOptions(Action<JsonSerializerOptions> configure)
{
if (configure is null)
{
throw new ArgumentNullException(nameof(configure));
}
configure(JsonSerializerOptions);
}
}
}

View File

@@ -0,0 +1,79 @@
using k8s.Tests.Mock;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Xunit;
using Xunit.Abstractions;
namespace k8s.Tests
{
public class SerializationTests
{
private readonly ITestOutputHelper testOutput;
private enum Animals
{
Dog,
Cat,
Mouse,
}
public SerializationTests(ITestOutputHelper testOutput)
{
this.testOutput = testOutput;
}
[Fact]
public async Task SerializeEnumUsingPascalCase()
{
using var server = new MockKubeApiServer(testOutput);
var config = new KubernetesClientConfiguration { Host = server.Uri.ToString() };
config.AddJsonOptions(options =>
{
// Insert the converter at the front of the list so it overrides any others.
options.Converters.Insert(index: 0, new JsonStringEnumConverter());
});
var client = new Kubernetes(config);
var customObject = Animals.Dog;
var result = await client.CustomObjects.CreateNamespacedCustomObjectWithHttpMessagesAsync(customObject, "TestGroup", "TestVersion", "TestNamespace", "TestPlural").ConfigureAwait(false);
var content = await result.Request.Content.ReadAsStringAsync().ConfigureAwait(false);
// Assert that the client serializes using the default options.
Assert.Equal(@"""Dog""", content);
// Assert that the underlying KubernetesJson serializes using the default options.
string animal = KubernetesJson.Serialize(Animals.Cat);
Assert.Equal(@"""Cat""", animal);
}
[Fact]
public async Task SerializeEnumUsingCamelCase()
{
using var server = new MockKubeApiServer(testOutput);
var config = new KubernetesClientConfiguration { Host = server.Uri.ToString() };
config.AddJsonOptions(options =>
{
// Insert the converter at the front of the list so it overrides
// the default JsonStringEnumConverter without namingPolicy.
options.Converters.Insert(index: 0, new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
});
var client = new Kubernetes(config);
var customObject = Animals.Dog;
var result = await client.CustomObjects.CreateNamespacedCustomObjectWithHttpMessagesAsync(customObject, "TestGroup", "TestVersion", "TestNamespace", "TestPlural").ConfigureAwait(false);
var content = await result.Request.Content.ReadAsStringAsync().ConfigureAwait(false);
// Assert that the client serializes using the specified options.
Assert.Equal(@"""dog""", content);
// Assert that the underlying KubernetesJson serializes using the default options.
string animal = KubernetesJson.Serialize(Animals.Cat);
Assert.Equal(@"""Cat""", animal);
}
}
}

View File

@@ -445,8 +445,8 @@ namespace k8s.Tests
var handler1 = new DummyHandler();
var handler2 = new DummyHandler();
var client = new Kubernetes(new KubernetesClientConfiguration { Host = server.Uri.ToString() }, handler1,
handler2);
var client = new Kubernetes(
new KubernetesClientConfiguration { Host = server.Uri.ToString() }, handler1, handler2);
Assert.False(handler1.Called);
Assert.False(handler2.Called);
@@ -732,12 +732,13 @@ namespace k8s.Tests
return false;
});
var h = new CheckHeaderDelegatingHandler();
var client = new Kubernetes(new KubernetesClientConfiguration { Host = server.Uri.ToString() }, h);
var handler = new CheckHeaderDelegatingHandler();
var client = new Kubernetes(
new KubernetesClientConfiguration { Host = server.Uri.ToString() }, handler);
Assert.Null(h.Version);
Assert.Null(handler.Version);
await client.CoreV1.ListNamespacedPodWithHttpMessagesAsync("default", watch: true).ConfigureAwait(false);
Assert.Equal(HttpVersion.Version20, h.Version);
Assert.Equal(HttpVersion.Version20, handler.Version);
}
}
}