Skip to content

Commit

Permalink
Closer hierarchy files should take precedence
Browse files Browse the repository at this point in the history
We should not override dictionary entries we have already set, since we walk the hierarchy from closer to farther.

To make tests less flaky, we also allow the AddDotNetConfig to receive the path argument that's ultimately passed to the Config.Build.

Fixes #78
  • Loading branch information
kzu committed Jul 30, 2021
1 parent ec6e91d commit a26b4ac
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 12 deletions.
34 changes: 27 additions & 7 deletions src/Config.Tests/ConfigurationTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System.Globalization;
using System;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.Configuration;
using Xunit;
using Xunit.Abstractions;
Expand All @@ -15,33 +18,50 @@ public ConfigurationTests(ITestOutputHelper output)
Config.GlobalLocation = Path.Combine(Constants.CurrentDirectory, "Content", "global.netconfig");
Config.SystemLocation = Path.Combine(Constants.CurrentDirectory, "Content", "system.netconfig");
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
Directory.SetCurrentDirectory(Path.Combine(Constants.CurrentDirectory, "Content", "web"));
this.output = output;
}

[FlakyFact]
[Fact]
public void can_load_hierarchical_values()
{
var config = new ConfigurationBuilder().AddDotNetConfig().Build();
var config = BuildConfiguration();

Assert.Equal("on", config["core:parent"]);
Assert.Equal("true", config["http:sslVerify"]);
Assert.Equal("false", config["http:https://weak.example.com:sslVerify"]);
Assert.Equal("yay", config["foo:bar:baz"]);
}

[FlakyFact]
[Fact]
public void can_save_values()
{
var config = new ConfigurationBuilder().AddDotNetConfig().Build();
var config = BuildConfiguration();

config["foo:enabled"] = "true";
config["foo:bar:baz"] = "bye";
config["http:https://weaker.example.com:sslVerify"] = "false";

var dotnet = Config.Build();
var dotnet = Config.Build(Path.Combine(Constants.CurrentDirectory, "Content", "web", nameof(can_save_values)));

Assert.Equal("true", dotnet.GetString("foo", "enabled"));
Assert.Equal("bye", dotnet.GetString("foo", "bar", "baz"));
Assert.Equal("false", dotnet.GetString("http", "https://weaker.example.com", "sslVerify"));
}

[Fact]
public void local_values_override_system_values()
{
var config = BuildConfiguration();

Assert.Equal("123", config["local:value"]);
}

IConfiguration BuildConfiguration([CallerMemberName] string? methodName = null)
{
var dir = Path.Combine(Constants.CurrentDirectory, "Content", "web", methodName);
Directory.CreateDirectory(dir);

return new ConfigurationBuilder().AddDotNetConfig(dir).Build();
}
}
}
2 changes: 2 additions & 0 deletions src/Config.Tests/Content/global.netconfig
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@
sslVerify = false
[core]
global = yes
[local]
value = 456
3 changes: 2 additions & 1 deletion src/Config.Tests/Content/web/.netconfig
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@

[local]
value = 123
21 changes: 20 additions & 1 deletion src/Configuration/DotNetConfigExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,25 @@ public static class DotNetConfigExtensions
/// </code>
/// </remarks>
public static IConfigurationBuilder AddDotNetConfig(this IConfigurationBuilder builder)
=> builder.Add(new DotNetConfigSource());
=> AddDotNetConfig(builder, null);

/// <summary>
/// Adds the DotNetConfig configuration provider to the <paramref name="builder"/>.
/// </summary>
/// <param name="builder">The <see cref="IConfigurationBuilder"/> to add dotnet config support to.</param>
/// <param name="path">Optional path to use when building the configuration. See <see cref="Config.Build(string?)"/>.
/// If not provided, the current directory will be used.</param>
/// <returns>The <see cref="IConfigurationBuilder"/>.</returns>
/// <remarks>
/// Simply invoke this method on a builder to add hierarchical dotnet-config support
/// to your app, for example:
/// <code>
/// var config = new ConfigurationBuilder().AddDotNetConfig().Build();
///
/// var ssl = config["http:sslVerify"];
/// </code>
/// </remarks>
public static IConfigurationBuilder AddDotNetConfig(this IConfigurationBuilder builder, string? path = null)
=> builder.Add(new DotNetConfigSource(path));
}
}
7 changes: 5 additions & 2 deletions src/Configuration/DotNetConfigProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ namespace DotNetConfig
{
class DotNetConfigProvider : ConfigurationProvider
{
Config configuration = Config.Build();
Config configuration;

public DotNetConfigProvider(string? path = null) => configuration = Config.Build(path);

public override void Load()
{
Expand All @@ -25,7 +27,8 @@ public override void Load()

key += ConfigurationPath.KeyDelimiter + entry.Variable;

data[key] = string.IsNullOrWhiteSpace(entry.RawValue) ? "true" : entry.RawValue!.Trim('"');
if (!data.ContainsKey(key))
data[key] = string.IsNullOrWhiteSpace(entry.RawValue) ? "true" : entry.RawValue!.Trim('"');
}

Data = data;
Expand Down
6 changes: 5 additions & 1 deletion src/Configuration/DotNetConfigSource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ namespace DotNetConfig
{
class DotNetConfigSource : IConfigurationSource
{
public IConfigurationProvider Build(IConfigurationBuilder builder) => new DotNetConfigProvider();
readonly string? path;

public DotNetConfigSource(string? path = null) => this.path = path;

public IConfigurationProvider Build(IConfigurationBuilder builder) => new DotNetConfigProvider(path);
}
}

0 comments on commit a26b4ac

Please sign in to comment.