Skip to content

Commit 901f7c1

Browse files
committed
Refactor ConfigurationReader to share common things
and simplify some method signatures
1 parent b1e4661 commit 901f7c1

File tree

3 files changed

+63
-47
lines changed

3 files changed

+63
-47
lines changed

src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Reflection;
@@ -325,50 +325,66 @@ static void CallConfigurationMethods(ILookup<string, Dictionary<string, IConfigu
325325
{
326326
foreach (var method in methods.SelectMany(g => g.Select(x => new { g.Key, Value = x })))
327327
{
328-
var methodInfo = SelectConfigurationMethod(configurationMethods, method.Key, method.Value);
328+
var methodInfo = SelectConfigurationMethod(configurationMethods, method.Key, method.Value.Keys);
329329

330330
if (methodInfo != null)
331331
{
332332
var call = (from p in methodInfo.GetParameters().Skip(1)
333-
let directive = method.Value.FirstOrDefault(s => s.Key.Equals(p.Name, StringComparison.OrdinalIgnoreCase))
333+
let directive = method.Value.FirstOrDefault(s => ParameterNameMatches(p.Name, s.Key))
334334
select directive.Key == null
335-
? p.DefaultValue
335+
? GetImplicitValueForNotSpecifiedKey(p, valueResolver, methodInfo)
336336
: directive.Value.ConvertTo(p.ParameterType, valueResolver)).ToList();
337337

338-
var parm = methodInfo.GetParameters().FirstOrDefault(i => i.ParameterType == typeof(IConfiguration));
339-
if (parm != null && !parm.HasDefaultValue)
340-
{
341-
if (valueResolver.AppConfiguration is null)
342-
{
343-
throw new InvalidOperationException("Trying to invoke a configuration method accepting a `IConfiguration` argument. " +
344-
$"This is not supported when only a `IConfigSection` has been provided. (method '{methodInfo}')");
345-
}
346-
call[parm.Position - 1] = valueResolver.AppConfiguration;
347-
}
348-
349338
call.Insert(0, receiver);
350-
351339
methodInfo.Invoke(null, call.ToArray());
352340
}
353341
}
354342
}
355343

356-
internal static MethodInfo SelectConfigurationMethod(IEnumerable<MethodInfo> candidateMethods, string name, Dictionary<string, IConfigurationArgumentValue> suppliedArgumentValues)
344+
static bool HasImplicitValueWhenNotSpecified(ParameterInfo paramInfo)
345+
{
346+
return paramInfo.HasDefaultValue
347+
// parameters of type IConfiguration are implicitly populated with provided Configuration
348+
|| paramInfo.ParameterType == typeof(IConfiguration);
349+
}
350+
351+
static object GetImplicitValueForNotSpecifiedKey(ParameterInfo parameter, SettingValueResolver valueResolver, MethodInfo methodToInvoke)
352+
{
353+
if (!HasImplicitValueWhenNotSpecified(parameter))
354+
{
355+
throw new InvalidOperationException("GetImplicitValueForNotSpecifiedKey() should only be called for parameters for which HasImplicitValueWhenNotSpecified() is true. " +
356+
"This means something is wrong in the Serilog.Settings.Configuration code.");
357+
}
358+
359+
if (parameter.ParameterType == typeof(IConfiguration))
360+
{
361+
if (parameter.HasDefaultValue)
362+
{
363+
return valueResolver.AppConfiguration ?? parameter.DefaultValue;
364+
}
365+
366+
return valueResolver.AppConfiguration
367+
?? throw new InvalidOperationException("Trying to invoke a configuration method accepting a `IConfiguration` argument. " +
368+
$"This is not supported when only a `IConfigSection` has been provided. (method '{methodToInvoke}')");
369+
}
370+
371+
return parameter.DefaultValue;
372+
}
373+
374+
internal static MethodInfo SelectConfigurationMethod(IEnumerable<MethodInfo> candidateMethods, string name, IEnumerable<string> suppliedArgumentNames)
357375
{
358376
// Per issue #111, it is safe to use case-insensitive matching on argument names. The CLR doesn't permit this type
359377
// of overloading, and the Microsoft.Extensions.Configuration keys are case-insensitive (case is preserved with some
360378
// config sources, but key-matching is case-insensitive and case-preservation does not appear to be guaranteed).
361379
return candidateMethods
362-
.Where(m => m.Name == name &&
363-
m.GetParameters().Skip(1)
364-
.All(p => p.HasDefaultValue
365-
|| suppliedArgumentValues.Any(s => s.Key.Equals(p.Name, StringComparison.OrdinalIgnoreCase))
366-
// parameters of type IConfiguration are implicitly populated with provided Configuration
367-
|| p.ParameterType == typeof(IConfiguration)
368-
))
380+
.Where(m => m.Name == name)
381+
.Where(m => m.GetParameters()
382+
.Skip(1)
383+
.All(p => HasImplicitValueWhenNotSpecified(p) ||
384+
ParameterNameMatches(p.Name, suppliedArgumentNames)))
369385
.OrderByDescending(m =>
370386
{
371-
var matchingArgs = m.GetParameters().Where(p => suppliedArgumentValues.Any(s => s.Key.Equals(p.Name, StringComparison.OrdinalIgnoreCase))).ToList();
387+
var matchingArgs = m.GetParameters().Where(p => ParameterNameMatches(p.Name, suppliedArgumentNames)).ToList();
372388

373389
// Prefer the configuration method with most number of matching arguments and of those the ones with
374390
// the most string type parameters to predict best match with least type casting
@@ -379,6 +395,16 @@ internal static MethodInfo SelectConfigurationMethod(IEnumerable<MethodInfo> can
379395
.FirstOrDefault();
380396
}
381397

398+
static bool ParameterNameMatches(string actualParameterName, string suppliedName)
399+
{
400+
return suppliedName.Equals(actualParameterName, StringComparison.OrdinalIgnoreCase);
401+
}
402+
403+
static bool ParameterNameMatches(string actualParameterName, IEnumerable<string> suppliedNames)
404+
{
405+
return suppliedNames.Any(s => ParameterNameMatches(actualParameterName, s));
406+
}
407+
382408
static IList<MethodInfo> FindSinkConfigurationMethods(IReadOnlyCollection<Assembly> configurationAssemblies)
383409
{
384410
var found = FindConfigurationExtensionMethods(configurationAssemblies, typeof(LoggerSinkConfiguration));

test/Serilog.Settings.Configuration.Tests/ConfigurationReaderTests.cs

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
using System.Collections.Generic;
2-
using Serilog.Formatting;
3-
using Xunit;
1+
using Xunit;
42
using System.Reflection;
53
using System.Linq;
4+
using Serilog.Formatting;
65
using Serilog.Settings.Configuration.Tests.Support;
76

87
namespace Serilog.Settings.Configuration.Tests
@@ -141,12 +140,9 @@ public void CallableMethodsAreSelected()
141140
{
142141
var options = typeof(DummyLoggerConfigurationExtensions).GetTypeInfo().DeclaredMethods.ToList();
143142
Assert.Equal(2, options.Count(mi => mi.Name == "DummyRollingFile"));
144-
var suppliedArguments = new Dictionary<string, IConfigurationArgumentValue>
145-
{
146-
{"pathFormat", new StringArgumentValue(() => "C:\\") }
147-
};
143+
var suppliedArgumentNames = new[] { "pathFormat" };
148144

149-
var selected = ConfigurationReader.SelectConfigurationMethod(options, "DummyRollingFile", suppliedArguments);
145+
var selected = ConfigurationReader.SelectConfigurationMethod(options, "DummyRollingFile", suppliedArgumentNames);
150146
Assert.Equal(typeof(string), selected.GetParameters()[1].ParameterType);
151147
}
152148

@@ -155,13 +151,10 @@ public void MethodsAreSelectedBasedOnCountOfMatchedArguments()
155151
{
156152
var options = typeof(DummyLoggerConfigurationExtensions).GetTypeInfo().DeclaredMethods.ToList();
157153
Assert.Equal(2, options.Count(mi => mi.Name == "DummyRollingFile"));
158-
var suppliedArguments = new Dictionary<string, IConfigurationArgumentValue>()
159-
{
160-
{ "pathFormat", new StringArgumentValue(() => "C:\\") },
161-
{ "formatter", new StringArgumentValue(() => "SomeFormatter, SomeAssembly") }
162-
};
163154

164-
var selected = ConfigurationReader.SelectConfigurationMethod(options, "DummyRollingFile", suppliedArguments);
155+
var suppliedArgumentNames = new[] { "pathFormat", "formatter" };
156+
157+
var selected = ConfigurationReader.SelectConfigurationMethod(options, "DummyRollingFile", suppliedArgumentNames);
165158
Assert.Equal(typeof(ITextFormatter), selected.GetParameters()[1].ParameterType);
166159
}
167160

@@ -170,13 +163,10 @@ public void MethodsAreSelectedBasedOnCountOfMatchedArgumentsAndThenStringType()
170163
{
171164
var options = typeof(DummyLoggerConfigurationWithMultipleMethodsExtensions).GetTypeInfo().DeclaredMethods.ToList();
172165
Assert.Equal(3, options.Count(mi => mi.Name == "DummyRollingFile"));
173-
var suppliedArguments = new Dictionary<string, IConfigurationArgumentValue>()
174-
{
175-
{ "pathFormat", new StringArgumentValue(() => "C:\\") },
176-
{ "formatter", new StringArgumentValue(() => "SomeFormatter, SomeAssembly") }
177-
};
178166

179-
var selected = ConfigurationReader.SelectConfigurationMethod(options, "DummyRollingFile", suppliedArguments);
167+
var suppliedArgumentNames = new[] { "pathFormat", "formatter" };
168+
169+
var selected = ConfigurationReader.SelectConfigurationMethod(options, "DummyRollingFile", suppliedArgumentNames);
180170
Assert.Equal(typeof(string), selected.GetParameters()[2].ParameterType);
181171
}
182172
}

test/Serilog.Settings.Configuration.Tests/ConfigurationSettingsTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -462,8 +462,8 @@ public void SinkWithOptionalIConfigurationArguments()
462462

463463
log.Write(Some.InformationEvent());
464464

465-
// null is the default value
466-
Assert.Null(DummyConfigurationSink.Configuration);
465+
// null is the default value, but we have a configuration to provide
466+
Assert.NotNull(DummyConfigurationSink.Configuration);
467467
}
468468

469469
[Fact]

0 commit comments

Comments
 (0)