Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hocon improvement. #218

Merged
merged 7 commits into from
Feb 12, 2020
39 changes: 38 additions & 1 deletion src/Hocon.Configuration.Test/ConfigurationSpec.cs
Expand Up @@ -594,11 +594,48 @@ public void WithFallback_ShouldNotChangeOriginalConfig()
a.WithFallback(b);

a.Fallbacks.Count.Should().Be(0);
a.GetString("akka.other-key").Should().BeNull();
a.GetString("akka.other-key", null).Should().BeNull();
ReferenceEquals(oldA, a).Should().BeTrue();
oldAContent.Should().Equals(a);
}

[Fact]
public void WithFallback_ShouldMergeSubstitutionProperly()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, this is the reproduction case you were talking about. Good, glad we have it.

{
var a = ConfigurationFactory.ParseString("{ executor : fork-join-executor }");
var subbed = ConfigurationFactory.ParseString(@"
mystring = substring
myapp{
my-fork-join-dispatcher {
type = ForkJoinDispatcher
dedicated-thread-pool.thread-count = 4
dedicated-thread-pool.substring = ${mystring}
}
}
akka.actor.deployment{
/pool1 {
router = random-pool
pool-dispatcher = ${myapp.my-fork-join-dispatcher}
}
}");
var combined = a
.WithFallback(subbed.GetConfig("akka.actor.deployment./pool1.pool-dispatcher"));

var expectedConfig = ConfigurationFactory.ParseString(@"
executor : fork-join-executor
type = ForkJoinDispatcher
dedicated-thread-pool.thread-count = 4
dedicated-thread-pool.substring = substring
");

var result = combined.Root.ToString(1, 2);
var expected = expectedConfig.Root.ToString(1, 2);

expected.Should().BeEquivalentTo(result);
combined.GetInt("dedicated-thread-pool.thread-count").Should().Be(4);
combined.GetString("dedicated-thread-pool.substring").Should().Be("substring");
}

/// <summary>
/// Source issue: https://github.com/akkadotnet/HOCON/issues/175
/// </summary>
Expand Down
55 changes: 46 additions & 9 deletions src/Hocon.Configuration/Config.cs
Expand Up @@ -18,7 +18,7 @@ namespace Hocon
/// configuration string.
/// </summary>
[Serializable]
public class Config : HoconRoot, ISerializable
public class Config : HoconRoot, ISerializable, IEquatable<Config>
{
private static readonly HoconValue EmptyValue;

Expand All @@ -31,21 +31,21 @@ static Config()
}

[Obsolete("For json serialization/deserialization only", true)]
private Config()
protected Config()
{
}

/// <inheritdoc />
/// <summary>
/// Initializes a new instance of the <see cref="Config" /> class.
/// </summary>
private Config(HoconValue value)
protected Config(HoconValue value)
{
Value = (HoconValue)value.Clone(null);
}

/// <inheritdoc cref="Config(HoconValue)" />
private Config(HoconValue value, Config fallback) : this(value)
protected Config(HoconValue value, Config fallback) : this(value)
{
MergeConfig(fallback);
}
Expand Down Expand Up @@ -82,15 +82,15 @@ public Config(HoconRoot source, Config fallback) : this(source)
/// </remarks>
public static Config Empty => CreateEmpty();

private List<HoconValue> _fallbacks { get; } = new List<HoconValue>();
protected List<HoconValue> _fallbacks { get; } = new List<HoconValue>();
public virtual IReadOnlyList<HoconValue> Fallbacks => _fallbacks.ToList().AsReadOnly();

/// <summary>
/// Determines if this root node contains any values
/// </summary>
public virtual bool IsEmpty => Value == EmptyValue && _fallbacks.Count == 0;

private HoconValue _mergedValueCache = null;
protected HoconValue _mergedValueCache = null;
public HoconValue Root
{
get
Expand Down Expand Up @@ -122,7 +122,28 @@ public string ToString(bool useFallbackValues)
return Root.ToString();
}

protected override HoconValue GetNode(HoconPath path, bool throwIfNotFound = false)
protected override bool TryGetNode(HoconPath path, out HoconValue result)
{
result = null;
var currentObject = Value.GetObject();
if (currentObject == null)
return false;
if (currentObject.TryGetValue(path, out result))
return true;

foreach (var value in _fallbacks)
{
currentObject = value.GetObject();
if (currentObject == null)
return false;
if (currentObject.TryGetValue(path, out result))
return true;
}

return false;
}

protected override HoconValue GetNode(HoconPath path)
{
var currentObject = Value.GetObject();
if (currentObject.TryGetValue(path, out var returnValue))
Expand All @@ -135,7 +156,7 @@ protected override HoconValue GetNode(HoconPath path, bool throwIfNotFound = fal
return returnValue;
}

return null;
throw new HoconException($"Could not find accessible field at path `{path}` in all fallbacks.");
}

/// <summary>
Expand Down Expand Up @@ -267,14 +288,30 @@ public void GetObjectData(SerializationInfo info, StreamingContext context)
info.AddValue(SerializedPropertyName, ToString(useFallbackValues: true), typeof(string));
}

public virtual bool Equals(Config other)
{
if (IsEmpty && other.IsEmpty) return true;
if (Value == other.Value) return true;
return false;
}

public override bool Equals(object obj)
{
if (obj == null) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj is Config cfg)
return Equals(cfg);
return false;
}

[Obsolete("Used for serialization only", true)]
public Config(SerializationInfo info, StreamingContext context):base(null)
{
var config = ConfigurationFactory.ParseString(info.GetValue(SerializedPropertyName, typeof(string)) as string);

Value = config.Value;
_fallbacks.AddRange(config._fallbacks);
}
}
}

/// <summary>
Expand Down
7 changes: 7 additions & 0 deletions src/Hocon.Configuration/ConfigurationException.cs
Expand Up @@ -14,6 +14,13 @@ namespace Hocon
/// </summary>
public class ConfigurationException : Exception
{
public static ConfigurationException NullOrEmptyConfig<T>(string path = null)
{
if (!string.IsNullOrWhiteSpace(path))
return new ConfigurationException($"Failed to instantiate {typeof(T).Name}: Configuration does not contain `{path}` node");
return new ConfigurationException($"Failed to instantiate {typeof(T).Name}: Configuration is null or empty.");
}

/// <summary>
/// Initializes a new instance of the <see cref="ConfigurationException" /> class.
/// </summary>
Expand Down
9 changes: 8 additions & 1 deletion src/Hocon.Configuration/HoconConfigurationSection.cs
Expand Up @@ -33,7 +33,14 @@ public class HoconConfigurationSection : ConfigurationSection
/// Retrieves a <see cref="Config" /> from the contents of the
/// custom akka node within a configuration file.
/// </summary>
public Config Config => _config ?? (_config = ConfigurationFactory.ParseString(Hocon.Content));
public Config Config {
get
{
if (_config == null && !string.IsNullOrWhiteSpace(Hocon.Content))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good optimization

_config = ConfigurationFactory.ParseString(Hocon.Content);
return _config;
}
}

/// <summary>
/// Retrieves the HOCON (Human-Optimized Config Object Notation)
Expand Down
34 changes: 20 additions & 14 deletions src/Hocon.Tests/HoconTests.cs
Expand Up @@ -377,45 +377,49 @@ public void CanSetDefaultValuesWhenGettingData()
var emptyConfig = HoconParser.Parse("{}");
var missingKey = "a";

emptyConfig.GetInt(missingKey).Should().Be(0);
emptyConfig.GetDouble(missingKey).Should().Be(0);
emptyConfig.GetInt(missingKey, 0).Should().Be(0);
emptyConfig.GetDouble(missingKey, 0).Should().Be(0);

emptyConfig.GetBooleanList(missingKey, new List<bool>()).Should().Equal(new List<bool>());
emptyConfig.Invoking(c => c.GetBooleanList(missingKey)).Should().Throw<HoconValueException>().Which
.InnerException.Should().BeOfType<HoconParserException>();
.InnerException.Should().BeOfType<KeyNotFoundException>();

emptyConfig.GetByteList(missingKey, new List<byte>()).Should().Equal(new List<byte>());
emptyConfig.Invoking(c => c.GetByteList(missingKey)).Should().Throw<HoconValueException>().Which
.InnerException.Should().BeOfType<HoconParserException>();
.InnerException.Should().BeOfType<KeyNotFoundException>();

emptyConfig.GetDecimalList(missingKey, new List<decimal>()).Should().Equal(new List<decimal>());
emptyConfig.Invoking(c => c.GetDecimalList(missingKey)).Should().Throw<HoconValueException>().Which
.InnerException.Should().BeOfType<HoconParserException>();
.InnerException.Should().BeOfType<KeyNotFoundException>();

emptyConfig.GetDoubleList(missingKey, new List<double>()).Should().Equal(new List<double>());
emptyConfig.Invoking(c => c.GetDoubleList(missingKey)).Should().Throw<HoconValueException>().Which
.InnerException.Should().BeOfType<HoconParserException>();
.InnerException.Should().BeOfType<KeyNotFoundException>();

emptyConfig.GetFloatList(missingKey, new List<float>()).Should().Equal(new List<float>());
emptyConfig.Invoking(c => c.GetFloatList(missingKey)).Should().Throw<HoconValueException>().Which
.InnerException.Should().BeOfType<HoconParserException>();
.InnerException.Should().BeOfType<KeyNotFoundException>();

emptyConfig.GetIntList(missingKey, new List<int>()).Should().Equal(new List<int>());
emptyConfig.Invoking(c => c.GetIntList(missingKey)).Should().Throw<HoconValueException>().Which
.InnerException.Should().BeOfType<HoconParserException>();
.InnerException.Should().BeOfType<KeyNotFoundException>();

emptyConfig.GetLongList(missingKey, new List<long>()).Should().Equal(new List<long>());
emptyConfig.Invoking(c => c.GetLongList(missingKey)).Should().Throw<HoconValueException>().Which
.InnerException.Should().BeOfType<HoconParserException>();
.InnerException.Should().BeOfType<KeyNotFoundException>();

emptyConfig.GetObjectList(missingKey, new List<HoconObject>()).Should().Equal(new List<HoconObject>());
emptyConfig.Invoking(c => c.GetObjectList(missingKey)).Should().Throw<HoconValueException>().Which
.InnerException.Should().BeOfType<HoconParserException>();
.InnerException.Should().BeOfType<KeyNotFoundException>();

emptyConfig.GetStringList(missingKey, new List<string>()).Should().Equal(new List<string>());

emptyConfig.Invoking(c => c.GetStringList(missingKey)).Should().Throw<HoconValueException>().Which
.InnerException.Should().BeOfType<KeyNotFoundException>();

/*
emptyConfig.Invoking(c => c.GetStringList(missingKey)).Should().NotThrow("String list is an exception of the rule")
.And.Subject().Should().BeEquivalentTo(new List<string>());
*/
}

[Fact]
Expand Down Expand Up @@ -710,18 +714,20 @@ public void Getter_failures_Should_include_bad_path()
}

[Fact]
public void GettingArrayFromLiteralsReturnsNull()
public void GettingArrayFromLiteralsShouldThrow()
{
var hocon = " literal : a b c";
HoconParser.Parse(hocon).Invoking(c => c.GetStringList("literal")).Should()
.Throw<HoconException>("Anything converted to array should throw instead");
}

[Fact]
public void GettingStringFromArrayReturnsNull()
public void GettingStringFromArrayShouldThrow()
{
var hocon = " array : [1,2,3]";
Assert.Null(HoconParser.Parse(hocon).GetString("array"));
HoconParser.Parse(hocon).Invoking(c => c.GetStringList("literal")).Should()
.Throw<HoconException>("Anything converted to array should throw instead");
//Assert.Null(HoconParser.Parse(hocon).GetString("array"));
}

[Fact]
Expand Down