diff --git a/src/StevanFreeborn.Extensions.Configuration.Secure/Configuration/ISecureConfig.cs b/src/StevanFreeborn.Extensions.Configuration.Secure/Configuration/ISecureConfig.cs index 8b370df..9aba129 100644 --- a/src/StevanFreeborn.Extensions.Configuration.Secure/Configuration/ISecureConfig.cs +++ b/src/StevanFreeborn.Extensions.Configuration.Secure/Configuration/ISecureConfig.cs @@ -1,8 +1,12 @@ +using System.Text.Json.Serialization.Metadata; + namespace StevanFreeborn.Extensions.Configuration.Secure.Configuration; internal interface ISecureConfig { - Task DeleteAsync(string key, CancellationToken ct = default); - Task GetAsync(string key, CancellationToken ct = default); Task SetAsync(string key, T value, CancellationToken ct = default); -} + Task SetAsync(string key, T value, JsonTypeInfo typeInfo, CancellationToken ct = default); + Task GetAsync(string key, CancellationToken ct = default); + Task GetAsync(string key, JsonTypeInfo typeInfo, CancellationToken ct = default); + Task DeleteAsync(string key, CancellationToken ct = default); +} \ No newline at end of file diff --git a/src/StevanFreeborn.Extensions.Configuration.Secure/Configuration/SecureConfig.cs b/src/StevanFreeborn.Extensions.Configuration.Secure/Configuration/SecureConfig.cs index bc8974c..d744d44 100644 --- a/src/StevanFreeborn.Extensions.Configuration.Secure/Configuration/SecureConfig.cs +++ b/src/StevanFreeborn.Extensions.Configuration.Secure/Configuration/SecureConfig.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using System.Text.Json.Serialization.Metadata; using StevanFreeborn.Extensions.Configuration.Secure.Cryptography; using StevanFreeborn.Extensions.Configuration.Secure.Storage; @@ -7,7 +8,8 @@ namespace StevanFreeborn.Extensions.Configuration.Secure.Configuration; internal sealed class SecureConfig( ISecureStorageProvider storageProvider, - ICryptoProvider cryptoProvider + ICryptoProvider cryptoProvider, + JsonSerializerOptions jsonOptions ) : ISecureConfig { private readonly ISecureStorageProvider _storageProvider = storageProvider ?? @@ -16,11 +18,20 @@ internal sealed class SecureConfig( private readonly ICryptoProvider _cryptoProvider = cryptoProvider ?? throw new ArgumentNullException(nameof(cryptoProvider)); - public async Task SetAsync(string key, T value, CancellationToken ct = default) + private readonly JsonSerializerOptions _jsonOptions = jsonOptions ?? + throw new ArgumentNullException(nameof(jsonOptions)); + + public Task SetAsync(string key, T value, CancellationToken ct = default) + { + var typeInfo = GetTypeInfo(); + return SetAsync(key, value, typeInfo, ct); + } + + public async Task SetAsync(string key, T value, JsonTypeInfo typeInfo, CancellationToken ct = default) { if (string.IsNullOrWhiteSpace(key)) { - throw new ArgumentNullException(nameof(key)); + throw new ArgumentException("Cannot be null or whitespace.", nameof(key)); } if (value is null) @@ -28,17 +39,23 @@ internal sealed class SecureConfig( throw new ArgumentNullException(nameof(value)); } - var json = JsonSerializer.Serialize(value); + var json = JsonSerializer.Serialize(value, typeInfo); var encryptedValue = _cryptoProvider.Encrypt(json); await _storageProvider.WriteAsync(key, encryptedValue, ct).ConfigureAwait(false); } - public async Task GetAsync(string key, CancellationToken ct = default) + public Task GetAsync(string key, CancellationToken ct = default) { - if (string.IsNullOrEmpty(key)) + var typeInfo = GetTypeInfo(); + return GetAsync(key, typeInfo, ct); + } + + public async Task GetAsync(string key, JsonTypeInfo typeInfo, CancellationToken ct = default) + { + if (string.IsNullOrWhiteSpace(key)) { - throw new ArgumentNullException(nameof(key)); + throw new ArgumentException("Cannot be null or whitespace.", nameof(key)); } var encryptedData = await _storageProvider.ReadAsync(key, ct).ConfigureAwait(false); @@ -49,11 +66,24 @@ internal sealed class SecureConfig( } var data = _cryptoProvider.Decrypt(encryptedData); - return JsonSerializer.Deserialize(data); + return JsonSerializer.Deserialize(data, typeInfo); } public Task DeleteAsync(string key, CancellationToken ct = default) { return _storageProvider.DeleteAsync(key, ct); } + + private JsonTypeInfo GetTypeInfo() + { + try + { + return (JsonTypeInfo?)_jsonOptions.GetTypeInfo(typeof(T)) + ?? throw new InvalidOperationException($"AOT metadata for type '{typeof(T).Name}' is missing. Did you forget to register it via {nameof(SecureConfigBuilder.AddJsonAotContext)}()?"); + } + catch (NotSupportedException ex) + { + throw new InvalidOperationException($"AOT metadata for type '{typeof(T).Name}' is missing. Did you forget to register it via {nameof(SecureConfigBuilder.AddJsonAotContext)}()?", ex); + } + } } \ No newline at end of file