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:
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -524,7 +524,7 @@ namespace k8s
|
||||
process.StartInfo.RedirectStandardError = true;
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
process.StartInfo.CreateNoWindow = true;
|
||||
|
||||
|
||||
return process;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
79
tests/KubernetesClient.Tests/SerializationTests.cs
Normal file
79
tests/KubernetesClient.Tests/SerializationTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user