diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs index 1385861..19d269d 100644 --- a/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs +++ b/src/Serilog.Settings.Configuration/Settings/Configuration/ConfigurationReader.cs @@ -403,10 +403,12 @@ static bool HasImplicitValueWhenNotSpecified(ParameterInfo paramInfo) { return paramInfo.HasDefaultValue // parameters of type IConfiguration are implicitly populated with provided Configuration - || paramInfo.ParameterType == typeof(IConfiguration); + || paramInfo.ParameterType == typeof(IConfiguration) + || paramInfo.IsDefined(typeof(ParamArrayAttribute), false) + || paramInfo.CustomAttributes.Any(a => a.AttributeType.FullName == "System.Runtime.CompilerServices.ParamCollectionAttribute"); } - object? GetImplicitValueForNotSpecifiedKey(ParameterInfo parameter, MethodInfo methodToInvoke) + internal object? GetImplicitValueForNotSpecifiedKey(ParameterInfo parameter, MethodInfo methodToInvoke) { if (!HasImplicitValueWhenNotSpecified(parameter)) { @@ -429,7 +431,12 @@ static bool HasImplicitValueWhenNotSpecified(ParameterInfo paramInfo) $"This is not supported when only a `IConfigSection` has been provided. (method '{methodToInvoke}')"); } - return parameter.DefaultValue; + if (parameter.IsDefined(typeof(ParamArrayAttribute), false) && parameter.ParameterType.GetElementType() is { } elementType) + { + return Array.CreateInstance(elementType, 0); + } + + return parameter.HasDefaultValue ? parameter.DefaultValue : null; } internal static MethodInfo? SelectConfigurationMethod(IReadOnlyCollection candidateMethods, string name, IReadOnlyCollection suppliedArgumentNames) diff --git a/test/Serilog.Settings.Configuration.Tests/ConfigurationReaderTests.cs b/test/Serilog.Settings.Configuration.Tests/ConfigurationReaderTests.cs index be3c8c8..7f47c14 100644 --- a/test/Serilog.Settings.Configuration.Tests/ConfigurationReaderTests.cs +++ b/test/Serilog.Settings.Configuration.Tests/ConfigurationReaderTests.cs @@ -329,4 +329,67 @@ public void ParseLogEventLevelThrowsForInvalidValues(string value) { Assert.Throws(() => ConfigurationReader.ParseLogEventLevel(value)); } + + [Fact] + public void ParamsStringArrayParameter_WithNoArgsSupplied_IsMatchedAsOptional() + { + var candidateMethods = typeof(DummyLoggerConfigurationExtensions) + .GetTypeInfo() + .DeclaredMethods + .ToList(); + + var selected = ConfigurationReader.SelectConfigurationMethod( + candidateMethods, "DummyParamsArray", Array.Empty()); + + Assert.NotNull(selected); + } + + [Fact] + public void ParamsStringArrayParameter_ImplicitValueIsEmptyArray() + { + var reader = new ConfigurationReader( + JsonStringConfigSource.LoadSection("{}", "Serilog"), + AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies), + new ConfigurationReaderOptions()); + + var method = typeof(DummyLoggerConfigurationExtensions).GetMethod("DummyParamsArray")!; + var param = method.GetParameters().Last(); // params string[] values + + var result = reader.GetImplicitValueForNotSpecifiedKey(param, method); + + var array = Assert.IsType(result); + Assert.Empty(array); + } + + [Fact] + public void ParamsEnumerableParameter_GracefullyReturnsDefaultValue() + { + var reader = new ConfigurationReader( + JsonStringConfigSource.LoadSection("{}", "Serilog"), + AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies), + new ConfigurationReaderOptions()); + + var method = typeof(DummyLoggerConfigurationExtensions).GetMethod("DummyParamsEnumerable")!; + var param = method.GetParameters().Last(); // params IEnumerable + + var result = reader.GetImplicitValueForNotSpecifiedKey(param, method); + + Assert.Null(result); + } + + [Fact] + public void ParamsSpanParameter_GracefullyReturnsDefaultValue() + { + var reader = new ConfigurationReader( + JsonStringConfigSource.LoadSection("{}", "Serilog"), + AssemblyFinder.ForSource(ConfigurationAssemblySource.UseLoadedAssemblies), + new ConfigurationReaderOptions()); + + var method = typeof(DummyLoggerConfigurationExtensions).GetMethod("DummyParamsSpan")!; + var param = method.GetParameters().Last(); // params ReadOnlySpan + + var result = reader.GetImplicitValueForNotSpecifiedKey(param, method); + + Assert.Null(result); + } } diff --git a/test/Serilog.Settings.Configuration.Tests/DummyLoggerConfigurationExtensions.cs b/test/Serilog.Settings.Configuration.Tests/DummyLoggerConfigurationExtensions.cs index c8a00d6..6dcd8d2 100644 --- a/test/Serilog.Settings.Configuration.Tests/DummyLoggerConfigurationExtensions.cs +++ b/test/Serilog.Settings.Configuration.Tests/DummyLoggerConfigurationExtensions.cs @@ -1,6 +1,7 @@ using Serilog.Configuration; using Serilog.Events; using Serilog.Formatting; +using TestDummies; namespace Serilog.Settings.Configuration.Tests; @@ -24,4 +25,25 @@ static class DummyLoggerConfigurationExtensions { return null; } + + public static LoggerConfiguration DummyParamsArray( + this LoggerSinkConfiguration loggerSinkConfiguration, + params string[] values) + { + return loggerSinkConfiguration.Sink(new DummyParamsSink(values)); + } + + public static LoggerConfiguration DummyParamsEnumerable( + this LoggerSinkConfiguration loggerSinkConfiguration, + params IEnumerable values) + { + return loggerSinkConfiguration.Sink(new DummyParamsSink(values.ToArray())); + } + + public static LoggerConfiguration DummyParamsSpan( + this LoggerSinkConfiguration loggerSinkConfiguration, + params ReadOnlySpan values) + { + return loggerSinkConfiguration.Sink(new DummyParamsSink(values.ToArray())); + } } diff --git a/test/Serilog.Settings.Configuration.Tests/DummyParamsSink.cs b/test/Serilog.Settings.Configuration.Tests/DummyParamsSink.cs new file mode 100644 index 0000000..267fd13 --- /dev/null +++ b/test/Serilog.Settings.Configuration.Tests/DummyParamsSink.cs @@ -0,0 +1,21 @@ +using Serilog.Core; +using Serilog.Events; + +namespace TestDummies; + +public class DummyParamsSink : ILogEventSink +{ + public static string[]? LastValues { get; private set; } + + public DummyParamsSink(params string[] values) + { + LastValues = values; + } + + public void Emit(LogEvent logEvent) { } + + public static void Reset() + { + LastValues = null; + } +}