Skip to content

Commit 2af57ca

Browse files
authoredApr 4, 2023
Add AddJsonOptions to KubernetesClientConfiguration and KubernetesJson (#1257)
* Use camelCase policy when serializing enums * Revert "Use camelCase policy when serializing enums" This reverts commit 467f49d. * 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
1 parent c7ff356 commit 2af57ca

File tree

8 files changed

+150
-19
lines changed

8 files changed

+150
-19
lines changed
 

‎examples/prometheus/Prometheus.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
using k8s;
22
using Prometheus;
33
using System;
4-
using System.Net.Http;
54
using System.Threading;
65

76
namespace prom
@@ -12,7 +11,7 @@ private static void Main(string[] args)
1211
{
1312
var config = KubernetesClientConfiguration.BuildDefaultConfig();
1413
var handler = new PrometheusHandler();
15-
IKubernetes client = new Kubernetes(config, new DelegatingHandler[] { handler });
14+
IKubernetes client = new Kubernetes(config, handler);
1615

1716
var server = new MetricServer(hostname: "localhost", port: 1234);
1817
server.Start();

‎src/KubernetesClient.Models/KubernetesJson.cs

+22-6
Original file line numberDiff line numberDiff line change
@@ -66,20 +66,36 @@ static KubernetesJson()
6666
JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
6767
}
6868

69-
public static TValue Deserialize<TValue>(string json)
69+
/// <summary>
70+
/// Configures <see cref="JsonSerializerOptions"/> for the <see cref="JsonSerializer"/>.
71+
/// To override existing converters, add them to the top of the <see cref="JsonSerializerOptions.Converters"/> list
72+
/// e.g. as follows: <code>options.Converters.Insert(index: 0, new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));</code>
73+
/// </summary>
74+
/// <param name="configure">An <see cref="Action"/> to configure the <see cref="JsonSerializerOptions"/>.</param>
75+
public static void AddJsonOptions(Action<JsonSerializerOptions> configure)
7076
{
71-
return JsonSerializer.Deserialize<TValue>(json, JsonSerializerOptions);
77+
if (configure is null)
78+
{
79+
throw new ArgumentNullException(nameof(configure));
80+
}
81+
82+
configure(JsonSerializerOptions);
83+
}
84+
85+
public static TValue Deserialize<TValue>(string json, JsonSerializerOptions jsonSerializerOptions = null)
86+
{
87+
return JsonSerializer.Deserialize<TValue>(json, jsonSerializerOptions ?? JsonSerializerOptions);
7288
}
7389

74-
public static TValue Deserialize<TValue>(Stream json)
90+
public static TValue Deserialize<TValue>(Stream json, JsonSerializerOptions jsonSerializerOptions = null)
7591
{
76-
return JsonSerializer.Deserialize<TValue>(json, JsonSerializerOptions);
92+
return JsonSerializer.Deserialize<TValue>(json, jsonSerializerOptions ?? JsonSerializerOptions);
7793
}
7894

7995

80-
public static string Serialize(object value)
96+
public static string Serialize(object value, JsonSerializerOptions jsonSerializerOptions = null)
8197
{
82-
return JsonSerializer.Serialize(value, JsonSerializerOptions);
98+
return JsonSerializer.Serialize(value, jsonSerializerOptions ?? JsonSerializerOptions);
8399
}
84100
}
85101
}

‎src/KubernetesClient/Kubernetes.ConfigInit.cs

+3-2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ namespace k8s
99
{
1010
public partial class Kubernetes
1111
{
12+
private readonly JsonSerializerOptions jsonSerializerOptions;
13+
1214
/// <summary>
1315
/// Initializes a new instance of the <see cref="Kubernetes" /> class.
1416
/// </summary>
@@ -27,6 +29,7 @@ public Kubernetes(KubernetesClientConfiguration config, params DelegatingHandler
2729
CreateHttpClient(handlers, config);
2830
InitializeFromConfig(config);
2931
HttpClientTimeout = config.HttpClientTimeout;
32+
jsonSerializerOptions = config.JsonSerializerOptions;
3033
#if NETSTANDARD2_1_OR_GREATER || NET5_0_OR_GREATER
3134
DisableHttp2 = config.DisableHttp2;
3235
#endif
@@ -155,8 +158,6 @@ private void CreateHttpClient(DelegatingHandler[] handlers, KubernetesClientConf
155158
};
156159
}
157160

158-
159-
160161
/// <summary>
161162
/// Set credentials for the Client
162163
/// </summary>

‎src/KubernetesClient/Kubernetes.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ protected override async Task<HttpOperationResponse<T>> CreateResultAsync<T>(Htt
8787
using (Stream stream = await httpResponse.Content.ReadAsStreamAsync().ConfigureAwait(false))
8888
#endif
8989
{
90-
result.Body = KubernetesJson.Deserialize<T>(stream);
90+
result.Body = KubernetesJson.Deserialize<T>(stream, jsonSerializerOptions);
9191
}
9292
}
9393
catch (JsonException)
@@ -126,7 +126,7 @@ protected override Task<HttpResponseMessage> SendRequest<T>(string relativeUri,
126126

127127
if (body != null)
128128
{
129-
var requestContent = KubernetesJson.Serialize(body);
129+
var requestContent = KubernetesJson.Serialize(body, jsonSerializerOptions);
130130
httpRequest.Content = new StringContent(requestContent, System.Text.Encoding.UTF8);
131131
httpRequest.Content.Headers.ContentType = GetHeader(body);
132132
return SendRequestRaw(requestContent, httpRequest, cancellationToken);

‎src/KubernetesClient/KubernetesClientConfiguration.ConfigFile.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,7 @@ public static Process CreateRunnableExternalProcess(ExternalExecution config)
524524
process.StartInfo.RedirectStandardError = true;
525525
process.StartInfo.UseShellExecute = false;
526526
process.StartInfo.CreateNoWindow = true;
527-
527+
528528
return process;
529529
}
530530

‎src/KubernetesClient/KubernetesClientConfiguration.cs

+35
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ namespace k8s
99
/// </summary>
1010
public partial class KubernetesClientConfiguration
1111
{
12+
private JsonSerializerOptions jsonSerializerOptions;
13+
1214
/// <summary>
1315
/// Gets current namespace
1416
/// </summary>
@@ -108,5 +110,38 @@ public partial class KubernetesClientConfiguration
108110
/// Do not use http2 even it is available
109111
/// </summary>
110112
public bool DisableHttp2 { get; set; } = false;
113+
114+
/// <summary>
115+
/// Options for the <see cref="JsonSerializer"/> to override the default ones.
116+
/// </summary>
117+
public JsonSerializerOptions JsonSerializerOptions
118+
{
119+
get
120+
{
121+
// If not yet set, use defaults from KubernetesJson.
122+
if (jsonSerializerOptions is null)
123+
{
124+
KubernetesJson.AddJsonOptions(options =>
125+
{
126+
jsonSerializerOptions = new JsonSerializerOptions(options);
127+
});
128+
}
129+
130+
return jsonSerializerOptions;
131+
}
132+
133+
private set => jsonSerializerOptions = value;
134+
}
135+
136+
/// <inheritdoc cref="KubernetesJson.AddJsonOptions(Action{JsonSerializerOptions})"/>
137+
public void AddJsonOptions(Action<JsonSerializerOptions> configure)
138+
{
139+
if (configure is null)
140+
{
141+
throw new ArgumentNullException(nameof(configure));
142+
}
143+
144+
configure(JsonSerializerOptions);
145+
}
111146
}
112147
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
using k8s.Tests.Mock;
2+
using System.Text.Json;
3+
using System.Text.Json.Serialization;
4+
using System.Threading.Tasks;
5+
using Xunit;
6+
using Xunit.Abstractions;
7+
8+
namespace k8s.Tests
9+
{
10+
public class SerializationTests
11+
{
12+
private readonly ITestOutputHelper testOutput;
13+
14+
private enum Animals
15+
{
16+
Dog,
17+
Cat,
18+
Mouse,
19+
}
20+
21+
public SerializationTests(ITestOutputHelper testOutput)
22+
{
23+
this.testOutput = testOutput;
24+
}
25+
26+
[Fact]
27+
public async Task SerializeEnumUsingPascalCase()
28+
{
29+
using var server = new MockKubeApiServer(testOutput);
30+
31+
var config = new KubernetesClientConfiguration { Host = server.Uri.ToString() };
32+
config.AddJsonOptions(options =>
33+
{
34+
// Insert the converter at the front of the list so it overrides any others.
35+
options.Converters.Insert(index: 0, new JsonStringEnumConverter());
36+
});
37+
var client = new Kubernetes(config);
38+
39+
var customObject = Animals.Dog;
40+
41+
var result = await client.CustomObjects.CreateNamespacedCustomObjectWithHttpMessagesAsync(customObject, "TestGroup", "TestVersion", "TestNamespace", "TestPlural").ConfigureAwait(false);
42+
var content = await result.Request.Content.ReadAsStringAsync().ConfigureAwait(false);
43+
44+
// Assert that the client serializes using the default options.
45+
Assert.Equal(@"""Dog""", content);
46+
47+
// Assert that the underlying KubernetesJson serializes using the default options.
48+
string animal = KubernetesJson.Serialize(Animals.Cat);
49+
Assert.Equal(@"""Cat""", animal);
50+
}
51+
52+
[Fact]
53+
public async Task SerializeEnumUsingCamelCase()
54+
{
55+
using var server = new MockKubeApiServer(testOutput);
56+
57+
var config = new KubernetesClientConfiguration { Host = server.Uri.ToString() };
58+
config.AddJsonOptions(options =>
59+
{
60+
// Insert the converter at the front of the list so it overrides
61+
// the default JsonStringEnumConverter without namingPolicy.
62+
options.Converters.Insert(index: 0, new JsonStringEnumConverter(JsonNamingPolicy.CamelCase));
63+
});
64+
var client = new Kubernetes(config);
65+
66+
var customObject = Animals.Dog;
67+
68+
var result = await client.CustomObjects.CreateNamespacedCustomObjectWithHttpMessagesAsync(customObject, "TestGroup", "TestVersion", "TestNamespace", "TestPlural").ConfigureAwait(false);
69+
var content = await result.Request.Content.ReadAsStringAsync().ConfigureAwait(false);
70+
71+
// Assert that the client serializes using the specified options.
72+
Assert.Equal(@"""dog""", content);
73+
74+
// Assert that the underlying KubernetesJson serializes using the default options.
75+
string animal = KubernetesJson.Serialize(Animals.Cat);
76+
Assert.Equal(@"""Cat""", animal);
77+
}
78+
}
79+
}

‎tests/KubernetesClient.Tests/WatchTests.cs

+7-6
Original file line numberDiff line numberDiff line change
@@ -445,8 +445,8 @@ public async Task TestWatchWithHandlers()
445445
var handler1 = new DummyHandler();
446446
var handler2 = new DummyHandler();
447447

448-
var client = new Kubernetes(new KubernetesClientConfiguration { Host = server.Uri.ToString() }, handler1,
449-
handler2);
448+
var client = new Kubernetes(
449+
new KubernetesClientConfiguration { Host = server.Uri.ToString() }, handler1, handler2);
450450

451451
Assert.False(handler1.Called);
452452
Assert.False(handler2.Called);
@@ -732,12 +732,13 @@ public async Task MustHttp2VersionSet()
732732
return false;
733733
});
734734

735-
var h = new CheckHeaderDelegatingHandler();
736-
var client = new Kubernetes(new KubernetesClientConfiguration { Host = server.Uri.ToString() }, h);
735+
var handler = new CheckHeaderDelegatingHandler();
736+
var client = new Kubernetes(
737+
new KubernetesClientConfiguration { Host = server.Uri.ToString() }, handler);
737738

738-
Assert.Null(h.Version);
739+
Assert.Null(handler.Version);
739740
await client.CoreV1.ListNamespacedPodWithHttpMessagesAsync("default", watch: true).ConfigureAwait(false);
740-
Assert.Equal(HttpVersion.Version20, h.Version);
741+
Assert.Equal(HttpVersion.Version20, handler.Version);
741742
}
742743
}
743744
}

0 commit comments

Comments
 (0)
Please sign in to comment.