From 9cfd2a984616e54986dff4085cf5ef709c2f53e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C5=82awomir=20Rosiek?= Date: Fri, 8 Nov 2024 21:23:15 +0100 Subject: [PATCH 1/2] Extending Config class to support passing JsonSerializerOptions --- sdk/Pulumi.Tests/ConfigTests.cs | 125 ++++++++++++++++++++++++++++++++ sdk/Pulumi/Config.cs | 49 ++++++++++++- 2 files changed, 170 insertions(+), 4 deletions(-) create mode 100644 sdk/Pulumi.Tests/ConfigTests.cs diff --git a/sdk/Pulumi.Tests/ConfigTests.cs b/sdk/Pulumi.Tests/ConfigTests.cs new file mode 100644 index 00000000..bf27a7f0 --- /dev/null +++ b/sdk/Pulumi.Tests/ConfigTests.cs @@ -0,0 +1,125 @@ +using Moq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using System.Threading.Tasks; +using Xunit; + +namespace Pulumi.Tests +{ + public class ConfigTests + { + private Config target = new Config("cfg"); + private Mock deploymentInternal = new Mock(); + private const string PascalCaseJson = "{ \"Value\":\"Value\" }"; + private const string CamelCaseJson = "{ \"value\":\"Value\" }"; + private static readonly JsonSerializerOptions CamelCaseSerializerOptions = new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + }; + + public ConfigTests() + { + var runner = new Mock(); + deploymentInternal.Setup(x => x.Runner).Returns(runner.Object); + Deployment.Instance = new DeploymentInstance(deploymentInternal.Object); + } + + + private class TestConfig + { + public string? Value { get; set; } + } + + [Fact] + public void GetObjectReturnsObject() + { + deploymentInternal.Setup(x => x.GetConfig("cfg:test")).Returns(PascalCaseJson); + + var result = target.GetObject("test"); + + Assert.NotNull(result); + Assert.Equal("Value", result?.Value); + } + + [Fact] + public void GetObjectReturnsObjectWithCustomJsonSerializerOptions() + { + deploymentInternal.Setup(x => x.GetConfig("cfg:test")).Returns(CamelCaseJson); + + var result = target.GetObject("test", CamelCaseSerializerOptions); + + Assert.NotNull(result); + Assert.Equal("Value", result?.Value); + } + + [Fact] + public async Task GetSecretObjectReturnsObject() + { + deploymentInternal.Setup(x => x.GetConfig("cfg:test")).Returns(PascalCaseJson); + + var output = target.GetSecretObject("test"); + var result = await output.GetValueAsync(new TestConfig()); + + Assert.Equal("Value", result.Value); + } + + [Fact] + public async Task GetSecretObjectReturnsObjectWithCustomJsonSerializerOptions() + { + deploymentInternal.Setup(x => x.GetConfig("cfg:test")).Returns(CamelCaseJson); + + var output = target.GetSecretObject("test", CamelCaseSerializerOptions); + var result = await output.GetValueAsync(new TestConfig()); + + Assert.Equal("Value", result.Value); + } + + [Fact] + public void RequireObjectReturnsObject() + { + deploymentInternal.Setup(x => x.GetConfig("cfg:test")).Returns(PascalCaseJson); + + var result = target.RequireObject("test"); + + Assert.NotNull(result); + Assert.Equal("Value", result?.Value); + } + + [Fact] + public void RequireObjectReturnsObjectWithCustomJsonSerializerOptions() + { + deploymentInternal.Setup(x => x.GetConfig("cfg:test")).Returns(CamelCaseJson); + + var result = target.RequireObject("test", CamelCaseSerializerOptions); + + Assert.NotNull(result); + Assert.Equal("Value", result?.Value); + } + + [Fact] + public async Task RequireSecretObjectReturnsObject() + { + deploymentInternal.Setup(x => x.GetConfig("cfg:test")).Returns(PascalCaseJson); + + var output = target.RequireSecretObject("test"); + var result = await output.GetValueAsync(new TestConfig()); + + Assert.Equal("Value", result.Value); + } + + [Fact] + public async Task RequireSecretObjectReturnsObjectWithCustomJsonSerializerOptions() + { + deploymentInternal.Setup(x => x.GetConfig("cfg:test")).Returns(CamelCaseJson); + + var output = target.RequireSecretObject("test", CamelCaseSerializerOptions); + var result = await output.GetValueAsync(new TestConfig()); + + Assert.Equal("Value", result.Value); + } + } +} diff --git a/sdk/Pulumi/Config.cs b/sdk/Pulumi/Config.cs index 11abcf1c..01ae9cd7 100644 --- a/sdk/Pulumi/Config.cs +++ b/sdk/Pulumi/Config.cs @@ -155,12 +155,12 @@ private static Output MakeStructSecret(T value) where T : struct => MakeStructSecret(GetDoubleImpl(key)); [return: MaybeNull] - private T GetObjectImpl(string key, string? use = null, [CallerMemberName] string? insteadOf = null) + private T GetObjectImpl(string key, string? use = null, [CallerMemberName] string? insteadOf = null, JsonSerializerOptions? options = null) { var v = GetImpl(key, use, insteadOf); try { - return v == null ? default : JsonSerializer.Deserialize(v); + return v == null ? default : JsonSerializer.Deserialize(v, options); } catch (JsonException ex) { @@ -168,6 +168,15 @@ private T GetObjectImpl(string key, string? use = null, [CallerMemberName] st } } + /// + /// Loads an optional configuration value, as an object, by its key, or null if it doesn't + /// exist. This works by taking the value associated with and passing + /// it to . + /// + [return: MaybeNull] + public T GetObject(string key, JsonSerializerOptions? options) + => GetObjectImpl(key, nameof(GetSecretObject), options: options); + /// /// Loads an optional configuration value, as an object, by its key, or null if it doesn't /// exist. This works by taking the value associated with and passing @@ -177,6 +186,20 @@ private T GetObjectImpl(string key, string? use = null, [CallerMemberName] st public T GetObject(string key) => GetObjectImpl(key, nameof(GetSecretObject)); + /// + /// Loads an optional configuration value, as an object, by its key, marking it as a secret + /// or null if it doesn't exist. This works by taking the value associated with and passing it to . + /// + public Output? GetSecretObject(string key, JsonSerializerOptions options) + { + var v = GetImpl(key); + if (v == null) + return null; + + return Output.CreateSecret(GetObjectImpl(key, options: options)!); + } + /// /// Loads an optional configuration value, as an object, by its key, marking it as a secret /// or null if it doesn't exist. This works by taking the value associated with RequireSecretDouble(string key) => MakeStructSecret(RequireDoubleImpl(key)); - private T RequireObjectImpl(string key, string? use = null, [CallerMemberName] string? insteadOf = null) + private T RequireObjectImpl(string key, string? use = null, [CallerMemberName] string? insteadOf = null, JsonSerializerOptions? options = null) { var v = GetImpl(key); if (v == null) throw new ConfigMissingException(FullKey(key)); - return GetObjectImpl(key, use, insteadOf)!; + return GetObjectImpl(key, use, insteadOf, options)!; } + /// + /// Loads a configuration value as a JSON string and deserializes the JSON into an object. + /// object. If it doesn't exist, or the configuration value cannot be converted using , an error is + /// thrown. + /// + public T RequireObject(string key, JsonSerializerOptions options) + => RequireObjectImpl(key, nameof(RequireSecretObject), options: options); + /// /// Loads a configuration value as a JSON string and deserializes the JSON into an object. /// object. If it doesn't exist, or the configuration value cannot be converted using (string key, string? use = null, [CallerMemberName public T RequireObject(string key) => RequireObjectImpl(key, nameof(RequireSecretObject)); + /// + /// Loads a configuration value as a JSON string and deserializes the JSON into a JavaScript + /// object, marking it as a secret. If it doesn't exist, or the configuration value cannot + /// be converted using , + /// an error is thrown. + /// + public Output RequireSecretObject(string key, JsonSerializerOptions options) + => Output.CreateSecret(RequireObjectImpl(key, options: options)); + /// /// Loads a configuration value as a JSON string and deserializes the JSON into a JavaScript /// object, marking it as a secret. If it doesn't exist, or the configuration value cannot From 6d06b49070d22960b52d80bfa72c8920ff2b2d70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C5=82awomir=20Rosiek?= Date: Fri, 8 Nov 2024 21:33:47 +0100 Subject: [PATCH 2/2] Changelog file --- .changes/unreleased/Improvements-370.yaml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changes/unreleased/Improvements-370.yaml diff --git a/.changes/unreleased/Improvements-370.yaml b/.changes/unreleased/Improvements-370.yaml new file mode 100644 index 00000000..a0281b6c --- /dev/null +++ b/.changes/unreleased/Improvements-370.yaml @@ -0,0 +1,6 @@ +component: sdk +kind: Improvements +body: Extending Config class to support passing JsonSerializerOptions +time: 2024-11-08T21:31:30+01:00 +custom: + PR: "370" \ No newline at end of file