Skip to content

Commit c1d3664

Browse files
authored
Add an optional to allow clients to specify which section of the Consul key should be removed when parsing the config. (#65)
1 parent 11690c6 commit c1d3664

5 files changed

Lines changed: 54 additions & 17 deletions

File tree

src/Winton.Extensions.Configuration.Consul/ConsulConfigurationProvider.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,11 @@ private async Task DoLoad(bool reloading)
7171
return;
7272
}
7373

74+
string keyToRemove = _source.KeyToRemove ?? _source.Key;
75+
7476
Data = (result?.Response ?? new KVPair[0])
7577
.Where(kvp => kvp.HasValue())
76-
.SelectMany(kvp => kvp.ConvertToConfig(_source.Key, _source.Parser))
78+
.SelectMany(kvp => kvp.ConvertToConfig(keyToRemove, _source.Parser))
7779
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value, StringComparer.OrdinalIgnoreCase);
7880
}
7981
catch (Exception exception)

src/Winton.Extensions.Configuration.Consul/ConsulConfigurationSource.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public ConsulConfigurationSource(string key, CancellationToken cancellationToken
3535

3636
public string Key { get; }
3737

38+
public string KeyToRemove { get; set; }
39+
3840
public Action<ConsulLoadExceptionContext> OnLoadException { get; set; }
3941

4042
public Action<ConsulWatchExceptionContext> OnWatchException { get; set; }

src/Winton.Extensions.Configuration.Consul/Extensions/KVPairExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ internal static class KVPairExtensions
1313
{
1414
internal static IEnumerable<KeyValuePair<string, string>> ConvertToConfig(
1515
this KVPair kvPair,
16-
string rootKey,
16+
string keyToRemove,
1717
IConfigurationParser parser)
1818
{
1919
using (Stream stream = new MemoryStream(kvPair.Value))
@@ -23,7 +23,7 @@ internal static IEnumerable<KeyValuePair<string, string>> ConvertToConfig(
2323
.Select(
2424
pair =>
2525
{
26-
string key = $"{kvPair.Key.RemoveStart(rootKey).TrimEnd('/')}:{pair.Key}"
26+
string key = $"{kvPair.Key.RemoveStart(keyToRemove).TrimEnd('/')}:{pair.Key}"
2727
.Replace('/', ':')
2828
.Trim(':');
2929
if (string.IsNullOrEmpty(key))

src/Winton.Extensions.Configuration.Consul/IConsulConfigurationSource.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ public interface IConsulConfigurationSource : IConfigurationSource
5050
/// </summary>
5151
string Key { get; }
5252

53+
/// <summary>
54+
/// Gets the portion of the Consul key to remove from the configuration keys.
55+
/// By default, when the configuration is parsed, the keys are created by removing the root key in Consul
56+
/// where the configuration is located.
57+
/// If this property is set then this string is removed instead of the Consul root key.
58+
/// </summary>
59+
string KeyToRemove { get; }
60+
5361
/// <summary>
5462
/// Gets or sets an <see cref="Action" /> that is invoked when an exception is raised during config load.
5563
/// Used by clients to handle the exception if possible and prevent it from being thrown.

test/Winton.Extensions.Configuration.Consul.Test/ConsulConfigurationProviderTests.cs

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ public void ShouldThrowIfParserIsNull()
4545

4646
public sealed class Load : ConsulConfigurationProviderTests
4747
{
48-
private readonly IConsulConfigurationSource _source;
48+
private readonly ConsulConfigurationSource _source;
4949

5050
public Load()
5151
{
52-
_source = new ConsulConfigurationSource("Test", default(CancellationToken))
52+
_source = new ConsulConfigurationSource("path/test", default(CancellationToken))
5353
{
5454
Parser = _configParserMock.Object,
5555
ReloadOnChange = false
@@ -64,7 +64,7 @@ private void ShouldCallSourceOnLoadExceptionActionWhenException()
6464
var calledOnLoadException = false;
6565

6666
_consulConfigClientMock
67-
.Setup(ccc => ccc.GetConfig("Test", default(CancellationToken)))
67+
.Setup(ccc => ccc.GetConfig("path/test", default(CancellationToken)))
6868
.ThrowsAsync(new Exception());
6969
_source.OnLoadException = context =>
7070
{
@@ -82,7 +82,7 @@ private void ShouldHaveEmptyDataIfConfigDoesNotExistAndIsOptional()
8282
{
8383
_source.Optional = true;
8484
_consulConfigClientMock
85-
.Setup(ccc => ccc.GetConfig("Test", default(CancellationToken)))
85+
.Setup(ccc => ccc.GetConfig("path/test", default(CancellationToken)))
8686
.ReturnsAsync(new QueryResult<KVPair[]> { StatusCode = HttpStatusCode.NotFound });
8787

8888
_consulConfigProvider.Load();
@@ -95,13 +95,13 @@ private void ShouldNotParseIfConfigBytesIsNull()
9595
{
9696
_source.Optional = true;
9797
_consulConfigClientMock
98-
.Setup(ccc => ccc.GetConfig("Test", default(CancellationToken)))
98+
.Setup(ccc => ccc.GetConfig("path/test", default(CancellationToken)))
9999
.ReturnsAsync(
100100
new QueryResult<KVPair[]>
101101
{
102102
Response = new[]
103103
{
104-
new KVPair("Test") { Value = new List<byte>().ToArray() }
104+
new KVPair("path/test") { Value = new List<byte>().ToArray() }
105105
},
106106
StatusCode = HttpStatusCode.OK
107107
});
@@ -116,7 +116,7 @@ private void ShouldNotParseIfConfigBytesIsNull()
116116
private void ShouldNotThrowExceptionIfOnLoadExceptionIsSetToIgnore()
117117
{
118118
_consulConfigClientMock
119-
.Setup(ccc => ccc.GetConfig("Test", default(CancellationToken)))
119+
.Setup(ccc => ccc.GetConfig("path/test", default(CancellationToken)))
120120
.ThrowsAsync(new Exception("Failed to load from Consul agent"));
121121
_source.OnLoadException = exceptionContext => { exceptionContext.Ignore = true; };
122122

@@ -135,13 +135,13 @@ private void ShouldParseLoadedConfigIntoCaseInsensitiveDictionary(string lookupK
135135
.Setup(cp => cp.Parse(It.IsAny<MemoryStream>()))
136136
.Returns(new Dictionary<string, string> { { "kEy", "Value" } });
137137
_consulConfigClientMock
138-
.Setup(ccc => ccc.GetConfig("Test", default(CancellationToken)))
138+
.Setup(ccc => ccc.GetConfig("path/test", default(CancellationToken)))
139139
.ReturnsAsync(
140140
new QueryResult<KVPair[]>
141141
{
142142
Response = new[]
143143
{
144-
new KVPair("Test") { Value = new List<byte> { 1 }.ToArray() }
144+
new KVPair("path/test") { Value = new List<byte> { 1 }.ToArray() }
145145
},
146146
StatusCode = HttpStatusCode.OK
147147
});
@@ -152,14 +152,39 @@ private void ShouldParseLoadedConfigIntoCaseInsensitiveDictionary(string lookupK
152152
value.Should().Be("Value");
153153
}
154154

155+
[Fact]
156+
private void ShouldRemoveCustomKeySectionIfSpecified()
157+
{
158+
_source.KeyToRemove = "path";
159+
_configParserMock
160+
.Setup(cp => cp.Parse(It.IsAny<MemoryStream>()))
161+
.Returns(new Dictionary<string, string> { { "Key", "Value" } });
162+
_consulConfigClientMock
163+
.Setup(ccc => ccc.GetConfig("path/test", default(CancellationToken)))
164+
.ReturnsAsync(
165+
new QueryResult<KVPair[]>
166+
{
167+
Response = new[]
168+
{
169+
new KVPair("path/test") { Value = new List<byte> { 1 }.ToArray() }
170+
},
171+
StatusCode = HttpStatusCode.OK
172+
});
173+
174+
_consulConfigProvider.Load();
175+
176+
_consulConfigProvider.TryGet("test:Key", out string value);
177+
value.Should().Be("Value");
178+
}
179+
155180
[Fact]
156181
private void ShouldSetExceptionInLoadExceptionContextWhenExceptionDuringLoad()
157182
{
158183
ConsulLoadExceptionContext exceptionContext = null;
159184
var expectedException = new Exception("Failed to load from Consul agent");
160185

161186
_consulConfigClientMock
162-
.Setup(ccc => ccc.GetConfig("Test", default(CancellationToken)))
187+
.Setup(ccc => ccc.GetConfig("path/test", default(CancellationToken)))
163188
.ThrowsAsync(expectedException);
164189
_source.OnLoadException = context =>
165190
{
@@ -177,7 +202,7 @@ private void ShouldSetSourceInLoadExceptionContextWhenExceptionDuringLoad()
177202
{
178203
ConsulLoadExceptionContext exceptionContext = null;
179204
_consulConfigClientMock
180-
.Setup(ccc => ccc.GetConfig("Test", default(CancellationToken)))
205+
.Setup(ccc => ccc.GetConfig("path/test", default(CancellationToken)))
181206
.ThrowsAsync(new Exception());
182207
_source.OnLoadException = context =>
183208
{
@@ -194,7 +219,7 @@ private void ShouldSetSourceInLoadExceptionContextWhenExceptionDuringLoad()
194219
private void ShouldThrowExceptionIfOnLoadExceptionDoesNotSetIgnoreWhenExceptionDuringLoad()
195220
{
196221
_consulConfigClientMock
197-
.Setup(ccc => ccc.GetConfig("Test", default(CancellationToken)))
222+
.Setup(ccc => ccc.GetConfig("path/test", default(CancellationToken)))
198223
.ThrowsAsync(new Exception("Error"));
199224
_source.OnLoadException = exceptionContext => { exceptionContext.Ignore = false; };
200225

@@ -208,12 +233,12 @@ private void ShouldThrowIfConfigDoesNotExistAndIsNotOptonalWhenLoad()
208233
_source.Optional = false;
209234
_source.OnLoadException = context => context.Ignore = false;
210235
_consulConfigClientMock
211-
.Setup(ccc => ccc.GetConfig("Test", default(CancellationToken)))
236+
.Setup(ccc => ccc.GetConfig("path/test", default(CancellationToken)))
212237
.ReturnsAsync(new QueryResult<KVPair[]> { StatusCode = HttpStatusCode.NotFound });
213238

214239
Action loading = _consulConfigProvider.Invoking(ccp => ccp.Load());
215240
loading.Should().Throw<Exception>()
216-
.WithMessage("The configuration for key Test was not found and is not optional.");
241+
.WithMessage("The configuration for key path/test was not found and is not optional.");
217242
}
218243
}
219244

0 commit comments

Comments
 (0)