feat: implement read and write methods for json file storage provider
This commit is contained in:
+9
-6
@@ -25,8 +25,8 @@ indent_size = 2
|
|||||||
#### Core EditorConfig Options ####
|
#### Core EditorConfig Options ####
|
||||||
|
|
||||||
# Indentation and spacing
|
# Indentation and spacing
|
||||||
indent_size = 4
|
indent_size = 2
|
||||||
tab_width = 4
|
tab_width = 2
|
||||||
|
|
||||||
# New line preferences
|
# New line preferences
|
||||||
insert_final_newline = false
|
insert_final_newline = false
|
||||||
@@ -70,7 +70,7 @@ dotnet_style_prefer_auto_properties = true:suggestion
|
|||||||
dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion
|
dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion
|
||||||
dotnet_style_prefer_compound_assignment = true:suggestion
|
dotnet_style_prefer_compound_assignment = true:suggestion
|
||||||
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
|
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
|
||||||
dotnet_style_prefer_conditional_expression_over_return = true:suggestion
|
dotnet_style_prefer_conditional_expression_over_return = false:silent;
|
||||||
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed:suggestion
|
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed:suggestion
|
||||||
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
|
||||||
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
dotnet_style_prefer_inferred_tuple_names = true:suggestion
|
||||||
@@ -90,10 +90,13 @@ dotnet_remove_unnecessary_suppression_exclusions = none
|
|||||||
#### C# Coding Conventions ####
|
#### C# Coding Conventions ####
|
||||||
[*.cs]
|
[*.cs]
|
||||||
|
|
||||||
|
dotnet_diagnostic.IDE0058.severity = none
|
||||||
|
dotnet_diagnostic.IDE0100.severity = none
|
||||||
|
|
||||||
# var preferences
|
# var preferences
|
||||||
csharp_style_var_elsewhere = false:silent
|
csharp_style_var_elsewhere = true:suggestion
|
||||||
csharp_style_var_for_built_in_types = false:silent
|
csharp_style_var_for_built_in_types = true:suggestion
|
||||||
csharp_style_var_when_type_is_apparent = false:silent
|
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||||
|
|
||||||
# Expression-bodied members
|
# Expression-bodied members
|
||||||
csharp_style_expression_bodied_accessors = true:silent
|
csharp_style_expression_bodied_accessors = true:silent
|
||||||
|
|||||||
@@ -1,2 +1,8 @@
|
|||||||
<Solution>
|
<Solution>
|
||||||
|
<Folder Name="/src/">
|
||||||
|
<Project Path="src/StevanFreeborn.Extensions.Configuration.Secure/StevanFreeborn.Extensions.Configuration.Secure.csproj" />
|
||||||
|
</Folder>
|
||||||
|
<Folder Name="/tests/">
|
||||||
|
<Project Path="tests/StevanFreeborn.Extensions.Configuration.Secure.Tests/StevanFreeborn.Extensions.Configuration.Secure.Tests.csproj" />
|
||||||
|
</Folder>
|
||||||
</Solution>
|
</Solution>
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
namespace StevanFreeborn.Extensions.Configuration.Secure.Sample;
|
||||||
|
|
||||||
|
public sealed record ApiOptions
|
||||||
|
{
|
||||||
|
public string ApiKey { get; init; } = string.Empty;
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
using StevanFreeborn.Extensions.Configuration.Secure.Sample;
|
||||||
|
using StevanFreeborn.Extensions.Configuration.Secure.Storage;
|
||||||
|
|
||||||
|
var builder = Host.CreateApplicationBuilder();
|
||||||
|
|
||||||
|
const string configFileName = "appsettings.json";
|
||||||
|
var opts = new JsonStorageOptions()
|
||||||
|
{
|
||||||
|
FileName = configFileName,
|
||||||
|
};
|
||||||
|
|
||||||
|
var storageProvider = new JsonFileStorageProvider(opts);
|
||||||
|
|
||||||
|
builder.Configuration.AddSecureConfig(storageProvider);
|
||||||
|
|
||||||
|
builder.Services.Configure<ApiOptions>(
|
||||||
|
builder.Configuration.GetSection(nameof(ApiOptions))
|
||||||
|
);
|
||||||
|
|
||||||
|
builder.Services.AddSecureConfig()
|
||||||
|
.UseJsonFileStorage(opt => opt.FileName = configFileName);
|
||||||
|
|
||||||
|
var app = builder.Build();
|
||||||
|
|
||||||
|
var options = app.Services.GetRequiredService<IOptions<ApiOptions>>();
|
||||||
|
var secureConfig = app.Services.GetRequiredService<ISecureConfig>();
|
||||||
|
|
||||||
|
Console.WriteLine(options.Value);
|
||||||
|
|
||||||
|
await secureConfig.SetAsync(
|
||||||
|
nameof(ApiOptions),
|
||||||
|
new ApiOptions { ApiKey = "apiKey" }
|
||||||
|
);
|
||||||
|
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.5" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\StevanFreeborn.Extensions.Configuration.Secure\StevanFreeborn.Extensions.Configuration.Secure.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
+15
@@ -0,0 +1,15 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>netstandard2.1</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net8.0'))">true</IsAotCompatible>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="System.Text.Json" Version="10.0.5" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
namespace StevanFreeborn.Extensions.Configuration.Secure.Storage;
|
||||||
|
|
||||||
|
public sealed class JsonFileStorageProvider(JsonStorageOptions options)
|
||||||
|
{
|
||||||
|
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||||
|
{
|
||||||
|
WriteIndented = true,
|
||||||
|
};
|
||||||
|
private readonly JsonStorageOptions _options = options
|
||||||
|
?? throw new ArgumentNullException(nameof(options));
|
||||||
|
|
||||||
|
public async Task<string> ReadAsync(string key, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
if (File.Exists(_options.FullPath) is false)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
using var stream = new FileStream(_options.FullPath, FileMode.Open, FileAccess.Read);
|
||||||
|
|
||||||
|
if (stream.Length is 0)
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = await JsonSerializer.DeserializeAsync<Dictionary<string, string>>(stream, cancellationToken: ct);
|
||||||
|
|
||||||
|
if (data is not null && data.TryGetValue(key, out var v))
|
||||||
|
{
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task WriteAsync(string key, string encryptedData, CancellationToken ct = default)
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(_options.DirectoryPath);
|
||||||
|
|
||||||
|
var data = new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
[key] = encryptedData
|
||||||
|
};
|
||||||
|
|
||||||
|
using var stream = new FileStream(
|
||||||
|
_options.FullPath,
|
||||||
|
FileMode.Create,
|
||||||
|
FileAccess.Write,
|
||||||
|
FileShare.None
|
||||||
|
);
|
||||||
|
|
||||||
|
await JsonSerializer.SerializeAsync(stream, data, JsonOptions, ct);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
namespace StevanFreeborn.Extensions.Configuration.Secure.Storage;
|
||||||
|
|
||||||
|
public sealed class JsonStorageOptions
|
||||||
|
{
|
||||||
|
public string FileName { get; set; } = string.Empty;
|
||||||
|
public string DirectoryPath { get; set; } = AppContext.BaseDirectory;
|
||||||
|
public string FullPath => Path.Combine(DirectoryPath, FileName);
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
[*.cs]
|
||||||
|
|
||||||
|
dotnet_diagnostic.CA1707.severity = none
|
||||||
+27
@@ -0,0 +1,27 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net11.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<IsPackable>false</IsPackable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="AwesomeAssertions" Version="9.4.0" />
|
||||||
|
<PackageReference Include="coverlet.collector" Version="6.0.4" />
|
||||||
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
|
||||||
|
<PackageReference Include="xunit" Version="2.9.3" />
|
||||||
|
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.4" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="Xunit" />
|
||||||
|
<Using Include="AwesomeAssertions" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\src\StevanFreeborn.Extensions.Configuration.Secure\StevanFreeborn.Extensions.Configuration.Secure.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
using StevanFreeborn.Extensions.Configuration.Secure.Storage;
|
||||||
|
|
||||||
|
namespace StevanFreeborn.Extensions.Configuration.Secure.Tests.Unit.Storage;
|
||||||
|
|
||||||
|
public class JsonFileStorageProviderTests
|
||||||
|
{
|
||||||
|
private readonly string _tmpDirectory;
|
||||||
|
private readonly JsonStorageOptions _options;
|
||||||
|
private readonly JsonFileStorageProvider _sut;
|
||||||
|
|
||||||
|
public JsonFileStorageProviderTests()
|
||||||
|
{
|
||||||
|
_tmpDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString());
|
||||||
|
Directory.CreateDirectory(_tmpDirectory);
|
||||||
|
|
||||||
|
_options = new()
|
||||||
|
{
|
||||||
|
FileName = "testsettings.json"
|
||||||
|
};
|
||||||
|
|
||||||
|
_sut = new(_options);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Constructor_WhenCalledWithNullOptions_ItShouldThrowArgumentNullException()
|
||||||
|
{
|
||||||
|
var act = static () => new JsonFileStorageProvider(null!);
|
||||||
|
|
||||||
|
act.Should().Throw<ArgumentNullException>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task WriteAsync_And_ReadAsync_WhenCalled_ItShouldBeAbleToPersistAndRetrieveValues()
|
||||||
|
{
|
||||||
|
var configKey = "Key";
|
||||||
|
var configValue = "Value";
|
||||||
|
|
||||||
|
await _sut.WriteAsync(configKey, configValue);
|
||||||
|
var result = await _sut.ReadAsync(configKey);
|
||||||
|
|
||||||
|
result.Should().Be(configValue);
|
||||||
|
File.Exists(_options.FullPath).Should().BeTrue();
|
||||||
|
}
|
||||||
|
}
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
using StevanFreeborn.Extensions.Configuration.Secure.Storage;
|
||||||
|
|
||||||
|
namespace StevanFreeborn.Extensions.Configuration.Secure.Tests.Unit.Storage;
|
||||||
|
|
||||||
|
public class JsonStorageOptionsTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void Path_WhenCalledWithFileNameSet_ItShouldReturnExpectedPath()
|
||||||
|
{
|
||||||
|
var fileName = "appsettings.json";
|
||||||
|
var expectedPath = Path.Combine(AppContext.BaseDirectory, fileName);
|
||||||
|
|
||||||
|
var opts = new JsonStorageOptions()
|
||||||
|
{
|
||||||
|
FileName = fileName,
|
||||||
|
};
|
||||||
|
|
||||||
|
opts.FullPath.Should().Be(expectedPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Path_WhenCalledWithFileNameAndDirectoryPathSet_ItShouldReturnExpectedPath()
|
||||||
|
{
|
||||||
|
var fileName = "appsettings.json";
|
||||||
|
var directoryPath = @"C:\Path\To\Some\Directory";
|
||||||
|
var expectedPath = Path.Combine(directoryPath, fileName);
|
||||||
|
|
||||||
|
var opts = new JsonStorageOptions()
|
||||||
|
{
|
||||||
|
FileName = fileName,
|
||||||
|
DirectoryPath = directoryPath,
|
||||||
|
};
|
||||||
|
|
||||||
|
opts.FullPath.Should().Be(expectedPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user