From d0a1ba01fb92bc92c0ad1c83dacc73e2aaa28c77 Mon Sep 17 00:00:00 2001 From: Christoffer Gersen Date: Sat, 24 Feb 2024 11:34:10 +0100 Subject: [PATCH 1/2] Support collections as constructor arguments --- .../Configuration/ObjectArgumentValue.cs | 222 ++++++++++----- .../ObjectArgumentValueTests.cs | 268 +++++++++++++++++- 2 files changed, 415 insertions(+), 75 deletions(-) diff --git a/src/Serilog.Settings.Configuration/Settings/Configuration/ObjectArgumentValue.cs b/src/Serilog.Settings.Configuration/Settings/Configuration/ObjectArgumentValue.cs index 151ce5b..4751a2a 100644 --- a/src/Serilog.Settings.Configuration/Settings/Configuration/ObjectArgumentValue.cs +++ b/src/Serilog.Settings.Configuration/Settings/Configuration/ObjectArgumentValue.cs @@ -1,5 +1,4 @@ using System.Diagnostics.CodeAnalysis; -using System.Linq.Expressions; using System.Reflection; using Microsoft.Extensions.Configuration; @@ -46,13 +45,16 @@ public ObjectArgumentValue(IConfigurationSection section, IReadOnlyCollection>(ctorExpression).Compile().Invoke(); - } + // Without a type explicitly specified, attempt to call ctor of toType + if (TryCallCtorImplicit(_section, toType, resolutionContext, out ctorResult)) + return ctorResult; // MS Config binding can work with a limited set of primitive types and collections return _section.Get(toType); @@ -76,33 +78,41 @@ bool TryCreateContainer([NotNullWhen(true)] out object? result) { result = null; - if (toType.GetConstructor(Type.EmptyTypes) == null) - return false; - - // https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers#collection-initializers - var addMethod = toType.GetMethods().FirstOrDefault(m => !m.IsStatic && m.Name == "Add" && m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == elementType); - if (addMethod == null) - return false; + if (IsConstructableDictionary(toType, elementType, out var concreteType, out var keyType, out var valueType, out var addMethod)) + { + result = Activator.CreateInstance(concreteType) ?? throw new InvalidOperationException($"Activator.CreateInstance returned null for {concreteType}"); - var configurationElements = _section.GetChildren().ToArray(); - result = Activator.CreateInstance(toType) ?? throw new InvalidOperationException($"Activator.CreateInstance returned null for {toType}"); + foreach (var section in _section.GetChildren()) + { + var argumentValue = ConfigurationReader.GetArgumentValue(section, _configurationAssemblies); + var key = new StringArgumentValue(section.Key).ConvertTo(keyType, resolutionContext); + var value = argumentValue.ConvertTo(valueType, resolutionContext); + addMethod.Invoke(result, new[] { key, value }); + } + return true; + } + else if (IsConstructableContainer(toType, elementType, out concreteType, out addMethod)) + { + result = Activator.CreateInstance(concreteType) ?? throw new InvalidOperationException($"Activator.CreateInstance returned null for {concreteType}"); - for (int i = 0; i < configurationElements.Length; ++i) + foreach (var section in _section.GetChildren()) + { + var argumentValue = ConfigurationReader.GetArgumentValue(section, _configurationAssemblies); + var value = argumentValue.ConvertTo(elementType, resolutionContext); + addMethod.Invoke(result, new[] { value }); + } + return true; + } + else { - var argumentValue = ConfigurationReader.GetArgumentValue(configurationElements[i], _configurationAssemblies); - var value = argumentValue.ConvertTo(elementType, resolutionContext); - addMethod.Invoke(result, new[] { value }); + return false; } - - return true; } } - internal static bool TryBuildCtorExpression( - IConfigurationSection section, Type parameterType, ResolutionContext resolutionContext, [NotNullWhen(true)] out NewExpression? ctorExpression) + bool TryCallCtorExplicit( + IConfigurationSection section, ResolutionContext resolutionContext, [NotNullWhen(true)] out object? value) { - ctorExpression = null; - var typeDirective = section.GetValue("$type") switch { not null => "$type", @@ -116,21 +126,39 @@ internal static bool TryBuildCtorExpression( var type = typeDirective switch { not null => Type.GetType(section.GetValue(typeDirective)!, throwOnError: false), - null => parameterType, + null => null, }; if (type is null or { IsAbstract: true }) { + value = null; return false; } + else + { + var suppliedArguments = section.GetChildren().Where(s => s.Key != typeDirective) + .ToDictionary(s => s.Key, StringComparer.OrdinalIgnoreCase); + return TryCallCtor(type, suppliedArguments, resolutionContext, out value); + } - var suppliedArguments = section.GetChildren().Where(s => s.Key != typeDirective) + } + + bool TryCallCtorImplicit( + IConfigurationSection section, Type parameterType, ResolutionContext resolutionContext, out object? value) + { + var suppliedArguments = section.GetChildren() .ToDictionary(s => s.Key, StringComparer.OrdinalIgnoreCase); + return TryCallCtor(parameterType, suppliedArguments, resolutionContext, out value); + } + + bool TryCallCtor(Type type, Dictionary suppliedArguments, ResolutionContext resolutionContext, [NotNullWhen(true)] out object? value) + { + value = null; if (suppliedArguments.Count == 0 && type.GetConstructor(Type.EmptyTypes) is ConstructorInfo parameterlessCtor) { - ctorExpression = Expression.New(parameterlessCtor); + value = parameterlessCtor.Invoke([]); return true; } @@ -163,76 +191,126 @@ where gr.All(z => z.argumentBindResult.success) return false; } - var ctorArguments = new List(); - foreach (var argumentValue in ctor.ArgumentValues) + var ctorArguments = new object?[ctor.ArgumentValues.Count]; + for (var i = 0; i < ctor.ArgumentValues.Count; i++) { - if (TryBindToCtorArgument(argumentValue.Value, argumentValue.Type, resolutionContext, out var argumentExpression)) + var argument = ctor.ArgumentValues[i]; + var valueValue = argument.Value; + if (valueValue is IConfigurationSection s) { - ctorArguments.Add(argumentExpression); - } - else - { - return false; + var argumentValue = ConfigurationReader.GetArgumentValue(s, _configurationAssemblies); + valueValue = argumentValue.ConvertTo(argument.Type, resolutionContext); } + ctorArguments[i] = valueValue; } - ctorExpression = Expression.New(ctor.ConstructorInfo, ctorArguments); + value = ctor.ConstructorInfo.Invoke(ctorArguments); return true; + } - static bool TryBindToCtorArgument(object value, Type type, ResolutionContext resolutionContext, [NotNullWhen(true)] out Expression? argumentExpression) + static bool IsContainer(Type type, [NotNullWhen(true)] out Type? elementType) + { + elementType = null; + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) { - argumentExpression = null; - - if (value is IConfigurationSection s) + elementType = type.GetGenericArguments()[0]; + return true; + } + foreach (var iface in type.GetInterfaces()) + { + if (iface.IsGenericType) { - if (s.Value is string argValue) + if (iface.GetGenericTypeDefinition() == typeof(IEnumerable<>)) { - var stringArgumentValue = new StringArgumentValue(argValue); - try - { - argumentExpression = Expression.Constant( - stringArgumentValue.ConvertTo(type, resolutionContext), - type); - - return true; - } - catch (Exception) - { - return false; - } + elementType = iface.GetGenericArguments()[0]; + return true; } - else if (s.GetChildren().Any()) - { - if (TryBuildCtorExpression(s, type, resolutionContext, out var ctorExpression)) - { - argumentExpression = ctorExpression; - return true; - } + } + } - return false; + return false; + } + + static bool IsConstructableDictionary(Type type, Type elementType, [NotNullWhen(true)] out Type? concreteType, [NotNullWhen(true)] out Type? keyType, [NotNullWhen(true)] out Type? valueType, [NotNullWhen(true)] out MethodInfo? addMethod) + { + concreteType = null; + keyType = null; + valueType = null; + addMethod = null; + if (!elementType.IsGenericType || elementType.GetGenericTypeDefinition() != typeof(KeyValuePair<,>)) + { + return false; + } + var argumentTypes = elementType.GetGenericArguments(); + keyType = argumentTypes[0]; + valueType = argumentTypes[1]; + if (type.IsAbstract) + { + concreteType = typeof(Dictionary<,>).MakeGenericType(argumentTypes); + if (!type.IsAssignableFrom(concreteType)) + { + return false; + } + } + else + { + concreteType = type; + } + if (concreteType.GetConstructor(Type.EmptyTypes) == null) + { + return false; + } + foreach (var method in concreteType.GetMethods()) + { + if (!method.IsStatic && method.Name == "Add") + { + var parameters = method.GetParameters(); + if (parameters.Length == 2 && parameters[0].ParameterType == keyType && parameters[1].ParameterType == valueType) + { + addMethod = method; + return true; } } - - argumentExpression = Expression.Constant(value, type); - return true; } + return false; } - static bool IsContainer(Type type, [NotNullWhen(true)] out Type? elementType) + static bool IsConstructableContainer(Type type, Type elementType, [NotNullWhen(true)] out Type? concreteType, [NotNullWhen(true)] out MethodInfo? addMethod) { - elementType = null; - foreach (var iface in type.GetInterfaces()) + addMethod = null; + if (type.IsAbstract) { - if (iface.IsGenericType) + concreteType = typeof(List<>).MakeGenericType(elementType); + if (!type.IsAssignableFrom(concreteType)) { - if (iface.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + concreteType = typeof(HashSet<>).MakeGenericType(elementType); + if (!type.IsAssignableFrom(concreteType)) { - elementType = iface.GetGenericArguments()[0]; + concreteType = null; + return false; + } + } + } + else + { + concreteType = type; + } + if (concreteType.GetConstructor(Type.EmptyTypes) == null) + { + return false; + } + foreach (var method in concreteType.GetMethods()) + { + if (!method.IsStatic && method.Name == "Add") + { + var parameters = method.GetParameters(); + if (parameters.Length == 1 && parameters[0].ParameterType == elementType) + { + addMethod = method; return true; } } } - return false; } } diff --git a/test/Serilog.Settings.Configuration.Tests/ObjectArgumentValueTests.cs b/test/Serilog.Settings.Configuration.Tests/ObjectArgumentValueTests.cs index 49f8d5a..51ff402 100644 --- a/test/Serilog.Settings.Configuration.Tests/ObjectArgumentValueTests.cs +++ b/test/Serilog.Settings.Configuration.Tests/ObjectArgumentValueTests.cs @@ -345,8 +345,11 @@ public void ConvertToUnsupportedContainerWillBeCreatedButWillRemainEmpty() } [Theory] + [InlineData(typeof(IEnumerable))] [InlineData(typeof(ICollection))] + [InlineData(typeof(IReadOnlyCollection))] [InlineData(typeof(IList))] + [InlineData(typeof(IReadOnlyList))] [InlineData(typeof(List))] public void ConvertToContainerUsingList(Type containerType) { @@ -364,8 +367,13 @@ public void ConvertToContainerUsingList(Type containerType) Assert.Equal([1, 1], list); } - [Fact] - public void ConvertToContainerUsingHashSet() + [Theory] + [InlineData(typeof(ISet))] +#if NET5_0_OR_GREATER + [InlineData(typeof(IReadOnlySet))] +#endif + [InlineData(typeof(HashSet))] + public void ConvertToContainerUsingHashSet(Type containerType) { // language=json var section = JsonStringConfigSource.LoadSection(""" @@ -375,13 +383,48 @@ public void ConvertToContainerUsingHashSet() """, "Container"); var value = new ObjectArgumentValue(section, []); - var container = value.ConvertTo(typeof(HashSet), new()); + var container = value.ConvertTo(containerType, new()); var set = Assert.IsType>(container); Assert.Equal([1, 2], set); } + [Fact] + public void ConvertToForcedHashSetImplementationWithCustomComparer() + { + // In .Net Framework HashSet is not part of mscorlib, but inside System.Core + // As a result the type string "System.Collections.Generic.HashSet`1[[System.String]]" will fail + // Using AssemblyQualifiedName to automatically switch to the correct type string, depending of framework + + // language=json + var json = $$""" + { + "Container": + { + "type": "{{typeof(HashSet).AssemblyQualifiedName}}", + "collection": [ + "a", + "A", + "b", + "b" + ], + "comparer": "System.StringComparer::OrdinalIgnoreCase" + } + } + """; + var section = JsonStringConfigSource.LoadSection(json, "Container"); + var value = new ObjectArgumentValue(section, []); + + var container = value.ConvertTo(typeof(IEnumerable), new()); + + var set = Assert.IsType>(container); + Assert.Equal(["a", "b"], set); + } + [Theory] + [InlineData(typeof(IEnumerable>))] + [InlineData(typeof(ICollection>))] + [InlineData(typeof(IReadOnlyCollection>))] [InlineData(typeof(IDictionary))] [InlineData(typeof(IReadOnlyDictionary))] [InlineData(typeof(Dictionary))] @@ -405,6 +448,9 @@ public void ConvertToContainerUsingDictionary(Type containerType) } [Theory] + [InlineData(typeof(IEnumerable>))] + [InlineData(typeof(ICollection>))] + [InlineData(typeof(IReadOnlyCollection>))] [InlineData(typeof(IDictionary))] [InlineData(typeof(IReadOnlyDictionary))] [InlineData(typeof(Dictionary))] @@ -500,6 +546,29 @@ IEnumerator IEnumerable.GetEnumerator() } } + [Fact] + public void ConvertToContainerUsingDictionaryWithoutPublicDefaultConstructor() + { + // language=json + var json = """ + { + "Container": { + "values": + { + "a": 1, + "b": 2 + } + } + } + """; + var section = JsonStringConfigSource.LoadSection(json, "Container"); + var value = new ObjectArgumentValue(section, []); + + var dictionary = ConvertToReturnsType(value); + + Assert.Equal(new Dictionary { { "a", 1 }, { "b", 2 } }, dictionary); + } + abstract class CustomAbstractDictionary : IDictionary { public abstract int this[string key] { get; set; } @@ -832,6 +901,199 @@ class WithParamsArray : IAmAnInterface public WithParamsArray(params int[] values) { Values = values; } } + [Fact] + public void ConvertToExplicitTypeWithParamsConstructorArgument() + { + // language=json + var json = """ + { + "Ctor": { + "type": "Serilog.Settings.Configuration.Tests.ObjectArgumentValueTests+WithParamsArray, Serilog.Settings.Configuration.Tests", + "values": [1, 2, 3] + } + } + """; + var section = JsonStringConfigSource.LoadSection(json, "Ctor"); + var value = new ObjectArgumentValue(section, []); + + var result = value.ConvertTo(typeof(IAmAnInterface), new()); + + var actual = Assert.IsType(result); + Assert.Equal([1, 2, 3], actual.Values); + } + + [Theory] + [InlineData(typeof(IEnumerable))] + [InlineData(typeof(ICollection))] + [InlineData(typeof(IReadOnlyCollection))] + [InlineData(typeof(IList))] + [InlineData(typeof(IReadOnlyList))] + [InlineData(typeof(List))] + public void ConvertToExplicitTypeWithContainerConstructorArgument(Type containerType) + { + var expectedType = typeof(GenericClass<>).MakeGenericType(containerType); + var valueProp = expectedType.GetProperty(nameof(GenericClass.Value)); + + // language=json + var json = $$""" + { + "Ctor": { + "type": "Serilog.Settings.Configuration.Tests.Support.GenericClass`1[[{{containerType.AssemblyQualifiedName}}]], Serilog.Settings.Configuration.Tests", + "value": [1, 2, 3] + } + } + """; + var section = JsonStringConfigSource.LoadSection(json, "Ctor"); + var value = new ObjectArgumentValue(section, []); + + var result = value.ConvertTo(typeof(IAmAnInterface), new()); + + Assert.IsType(expectedType, result); + var list = Assert.IsType>(valueProp?.GetValue(result)); + Assert.Equal([1, 2, 3], list); + } + + [Theory] + [InlineData(typeof(ISet))] +#if NET5_0_OR_GREATER + [InlineData(typeof(IReadOnlySet))] +#endif + [InlineData(typeof(HashSet))] + public void ConvertToExplicitTypeWithSetConstructorArgument(Type containerType) + { + var expectedType = typeof(GenericClass<>).MakeGenericType(containerType); + var valueProp = expectedType.GetProperty(nameof(GenericClass.Value)); + + // language=json + var json = $$""" + { + "Ctor": { + "type": "Serilog.Settings.Configuration.Tests.Support.GenericClass`1[[{{containerType.AssemblyQualifiedName}}]], Serilog.Settings.Configuration.Tests", + "value": [ 1, 1, 2, 2 ] + } + } + """; + var section = JsonStringConfigSource.LoadSection(json, "Ctor"); + var value = new ObjectArgumentValue(section, []); + + var result = value.ConvertTo(typeof(IAmAnInterface), new()); + + Assert.IsType(expectedType, result); + var set = Assert.IsType>(valueProp?.GetValue(result)); + Assert.Equal([1, 2], set); + } + + + [Theory] + [InlineData(typeof(IEnumerable>))] + [InlineData(typeof(ICollection>))] + [InlineData(typeof(IReadOnlyCollection>))] + [InlineData(typeof(IDictionary))] + [InlineData(typeof(IReadOnlyDictionary))] + [InlineData(typeof(Dictionary))] + public void ConvertToExplicitTypeWithDictionaryConstructorArgument(Type containerType) + { + var expectedType = typeof(GenericClass<>).MakeGenericType(containerType); + var valueProp = expectedType.GetProperty(nameof(GenericClass.Value)); + + // language=json + var json = $$""" + { + "Ctor": { + "type": "Serilog.Settings.Configuration.Tests.Support.GenericClass`1[[{{containerType.AssemblyQualifiedName}}]], Serilog.Settings.Configuration.Tests", + "value": { + "a": 1, + "b": 2 + } + } + } + """; + var section = JsonStringConfigSource.LoadSection(json, "Ctor"); + var value = new ObjectArgumentValue(section, []); + + var result = value.ConvertTo(typeof(IAmAnInterface), new()); + + Assert.IsType(expectedType, result); + var dictionary = Assert.IsType>(valueProp?.GetValue(result)); + Assert.Equal(new Dictionary { { "a", 1 }, { "b", 2 } }, dictionary); + } + + [Fact] + public void ConvertToExplicitTypeWithStructConstructorArgument() + { + // language=json + var json = """ + { + "Ctor": { + "type": "Serilog.Settings.Configuration.Tests.Support.GenericClass`1[[Serilog.Settings.Configuration.Tests.ObjectArgumentValueTests+PlainStruct, Serilog.Settings.Configuration.Tests]], Serilog.Settings.Configuration.Tests", + "value": { "A" : "1" } + } + } + """; + var section = JsonStringConfigSource.LoadSection(json, "Ctor"); + var value = new ObjectArgumentValue(section, []); + + var result = value.ConvertTo(typeof(IAmAnInterface), new()); + + var actual = Assert.IsType>(result); + Assert.Equal("1", actual.Value.A); + Assert.Null(actual.Value.B); + } + + [Fact] + public void ConvertToExplicitTypeWithClassConstructorArgument() + { + // language=json + var json = """ + { + "Ctor": { + "type": "Serilog.Settings.Configuration.Tests.Support.GenericClass`1[[TestDummies.DummyLoggerConfigurationExtensions+Binding, TestDummies]], Serilog.Settings.Configuration.Tests", + "value": { "foo" : "bar" } + } + } + """; + var section = JsonStringConfigSource.LoadSection(json, "Ctor"); + var value = new ObjectArgumentValue(section, []); + + var result = value.ConvertTo(typeof(IAmAnInterface), new()); + + var actual = Assert.IsType>(result); + Assert.Equal("bar", actual.Value.Foo); + Assert.Null(actual.Value.Abc); + } + + readonly struct Struct : IAmAnInterface + { + public readonly string String { get; } + + public Struct(string str) { String = str; } + } + + [Fact] + public void ConvertToExplicitTypeWithExplicitStructConstructorArgument() + { + // language=json + var json = """ + { + "Ctor": { + "type": "Serilog.Settings.Configuration.Tests.Support.GenericClass`1[[Serilog.Settings.Configuration.Tests.Support.IAmAnInterface, Serilog.Settings.Configuration.Tests]], Serilog.Settings.Configuration.Tests", + "value": { + "type": "Serilog.Settings.Configuration.Tests.ObjectArgumentValueTests+Struct, Serilog.Settings.Configuration.Tests", + "str" : "abc" + } + } + } + """; + var section = JsonStringConfigSource.LoadSection(json, "Ctor"); + var value = new ObjectArgumentValue(section, []); + + var result = value.ConvertTo(typeof(IAmAnInterface), new()); + + var actual = Assert.IsType>(result); + var structValue = Assert.IsType(actual.Value); + Assert.Equal("abc", structValue.String); + } + [Fact] public void ConvertToExplicitTypeWithExplicitTypeConstructorArgument() { From 64a0130b0437e9c9fedffaa30d8f2d4d7cb5df6b Mon Sep 17 00:00:00 2001 From: Christoffer Gersen Date: Sat, 24 Feb 2024 11:38:21 +0100 Subject: [PATCH 2/2] Place JSON strings in variable before use To avoid JSON001 Invalid JSON pattern analyzer errors in IDE --- .../ObjectArgumentValueTests.cs | 169 +++++++++++------- 1 file changed, 101 insertions(+), 68 deletions(-) diff --git a/test/Serilog.Settings.Configuration.Tests/ObjectArgumentValueTests.cs b/test/Serilog.Settings.Configuration.Tests/ObjectArgumentValueTests.cs index 51ff402..d038a6c 100644 --- a/test/Serilog.Settings.Configuration.Tests/ObjectArgumentValueTests.cs +++ b/test/Serilog.Settings.Configuration.Tests/ObjectArgumentValueTests.cs @@ -24,11 +24,12 @@ static T ConvertToReturnsType(ObjectArgumentValue value, ResolutionContext? r public void ConvertToIConfigurationSection() { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "Serilog": {} } - """, "Serilog"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Serilog"); var value = new ObjectArgumentValue(section, []); var actual = value.ConvertTo(typeof(IConfigurationSection), new()); @@ -40,7 +41,7 @@ public void ConvertToIConfigurationSection() public void ConvertToLoggerConfigurationCallback() { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "Serilog": { "WriteTo": [{ @@ -50,7 +51,8 @@ public void ConvertToLoggerConfigurationCallback() "Enrich": ["WithDummyThreadId"] } } - """, "Serilog"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Serilog"); var value = new ObjectArgumentValue(section, [typeof(DummyRollingFileSink).Assembly]); var configure = ConvertToReturnsType>(value); @@ -70,7 +72,7 @@ public void ConvertToLoggerConfigurationCallback() public void ConvertToLoggerSinkConfigurationCallback() { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "WriteTo": [{ "Name": "Dummy", @@ -79,7 +81,8 @@ public void ConvertToLoggerSinkConfigurationCallback() } }] } - """, "WriteTo"); + """; + var section = JsonStringConfigSource.LoadSection(json, "WriteTo"); var value = new ObjectArgumentValue(section, [typeof(DummyConfigurationSink).Assembly]); var configureSinks = ConvertToReturnsType>(value); @@ -100,7 +103,7 @@ public void ConvertToLoggerSinkConfigurationCallback() public void ConvertToLoggerEnrichmentConfiguration() { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "Enrich": [{ "Name": "AtLevel", @@ -110,7 +113,8 @@ public void ConvertToLoggerEnrichmentConfiguration() } }] } - """, "Enrich"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Enrich"); var value = new ObjectArgumentValue(section, [typeof(LoggerEnrichmentConfiguration).Assembly, typeof(DummyThreadIdEnricher).Assembly]); var configureEnrichment = ConvertToReturnsType>(value); @@ -134,11 +138,12 @@ public void ConvertToLoggerEnrichmentConfiguration() public void ConvertToConfigurationCallbackThrows() { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "Configure": {} } - """, "Configure"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Configure"); var value = new ObjectArgumentValue(section, []); var ex = Assert.Throws(() => value.ConvertTo(typeof(Action), new())); @@ -150,11 +155,12 @@ public void ConvertToConfigurationCallbackThrows() public void ConvertToArrayUsingStringArgumentValueForElements() { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "Array": [ "Information", 3, null ] } - """, "Array"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Array"); var value = new ObjectArgumentValue(section, []); var array = ConvertToReturnsType(value); @@ -190,11 +196,12 @@ public void ConvertToArrayOfArraysPassingContext() public void ConvertToArrayRecursingObjectArgumentValuePassingAssemblies() { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "Array": [{ "WriteTo": [{ "Name": "DummyConsole", "Args": {} }] }] } - """, "Array"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Array"); var value = new ObjectArgumentValue(section, [typeof(DummyConsoleSink).Assembly]); var configureCalls = ConvertToReturnsType[]>(value); @@ -214,14 +221,15 @@ public void ConvertToArrayRecursingObjectArgumentValuePassingAssemblies() public void ConvertToArrayWithDifferentImplementations() { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "Array": [ "Serilog.Settings.Configuration.Tests.Support.ConcreteImpl::Instance, Serilog.Settings.Configuration.Tests", "Serilog.Settings.Configuration.Tests.ObjectArgumentValueTests+PrivateImplWithPublicCtor, Serilog.Settings.Configuration.Tests" ] } - """, "Array"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Array"); var value = new ObjectArgumentValue(section, []); var array = ConvertToReturnsType(value); @@ -235,11 +243,12 @@ public void ConvertToArrayWithDifferentImplementations() public void ConvertToContainerUsingStringArgumentValueForElements() { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "List": [ "Information", 3, null ] } - """, "List"); + """; + var section = JsonStringConfigSource.LoadSection(json, "List"); var value = new ObjectArgumentValue(section, []); var list = ConvertToReturnsType>(value); @@ -258,11 +267,12 @@ public void ConvertToNestedContainerPassingContext() }; // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "List": [ [ 1, 2 ], [ 3, 4 ], [ "1.234,56" ] ] } - """, "List"); + """; + var section = JsonStringConfigSource.LoadSection(json, "List"); var value = new ObjectArgumentValue(section, []); var array = ConvertToReturnsType>>(value, new(readerOptions: new() { FormatProvider = formatProvider })); @@ -274,11 +284,12 @@ public void ConvertToNestedContainerPassingContext() public void ConvertToContainerRecursingObjectArgumentValuePassingAssemblies() { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "List": [{ "WriteTo": [{ "Name": "DummyConsole", "Args": {} }] }] } - """, "List"); + """; + var section = JsonStringConfigSource.LoadSection(json, "List"); var value = new ObjectArgumentValue(section, [typeof(DummyConsoleSink).Assembly]); var configureCalls = ConvertToReturnsType>>(value); @@ -298,14 +309,15 @@ public void ConvertToContainerRecursingObjectArgumentValuePassingAssemblies() public void ConvertToListWithDifferentImplementations() { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "List": [ "Serilog.Settings.Configuration.Tests.Support.ConcreteImpl::Instance, Serilog.Settings.Configuration.Tests", "Serilog.Settings.Configuration.Tests.ObjectArgumentValueTests+PrivateImplWithPublicCtor, Serilog.Settings.Configuration.Tests" ] } - """, "List"); + """; + var section = JsonStringConfigSource.LoadSection(json, "List"); var value = new ObjectArgumentValue(section, []); var list = ConvertToReturnsType>(value); @@ -332,11 +344,12 @@ IEnumerator IEnumerable.GetEnumerator() public void ConvertToUnsupportedContainerWillBeCreatedButWillRemainEmpty() { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "List": ["a", "b"] } - """, "List"); + """; + var section = JsonStringConfigSource.LoadSection(json, "List"); var value = new ObjectArgumentValue(section, []); var unsupported = ConvertToReturnsType(value); @@ -354,11 +367,12 @@ public void ConvertToUnsupportedContainerWillBeCreatedButWillRemainEmpty() public void ConvertToContainerUsingList(Type containerType) { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "Container": [ 1, 1 ] } - """, "Container"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Container"); var value = new ObjectArgumentValue(section, []); var container = value.ConvertTo(containerType, new()); @@ -376,11 +390,12 @@ public void ConvertToContainerUsingList(Type containerType) public void ConvertToContainerUsingHashSet(Type containerType) { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "Container": [ 1, 1, 2, 2 ] } - """, "Container"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Container"); var value = new ObjectArgumentValue(section, []); var container = value.ConvertTo(containerType, new()); @@ -431,14 +446,15 @@ public void ConvertToForcedHashSetImplementationWithCustomComparer() public void ConvertToContainerUsingDictionary(Type containerType) { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "Container": { "a": 1, "b": 2 } } - """, "Container"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Container"); var value = new ObjectArgumentValue(section, []); var container = value.ConvertTo(containerType, new()); @@ -457,14 +473,15 @@ public void ConvertToContainerUsingDictionary(Type containerType) public void ConvertToContainerUsingDictionaryUsingStringArgumentValueToConvertKey(Type containerType) { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "Container": { "1": 2, "3": 4 } } - """, "Container"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Container"); var value = new ObjectArgumentValue(section, []); var container = value.ConvertTo(containerType, new()); @@ -599,14 +616,15 @@ IEnumerator IEnumerable.GetEnumerator() public void ConvertToCustomAbstractDictionaryThrows() { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "Container": { "a": 1, "b": 2 } } - """, "Container"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Container"); var value = new ObjectArgumentValue(section, []); Assert.Throws(() => value.ConvertTo(typeof(CustomAbstractDictionary), new())); @@ -647,14 +665,15 @@ IEnumerator IEnumerable.GetEnumerator() public void ConvertToCustomReadOnlyDictionaryCreatesEmpty() { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "Container": { "a": 1, "b": 2 } } - """, "Container"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Container"); var value = new ObjectArgumentValue(section, []); ConvertToReturnsType(value); @@ -670,11 +689,12 @@ class PrivateImplWithPublicCtor : AnAbstractClass, IAmAnInterface; public void ConvertToExplicitType(Type targetType, Type expectedType) { // language=json - var section = JsonStringConfigSource.LoadSection($$""" + var json = $$""" { "Ctor": { "type": "{{expectedType.AssemblyQualifiedName}}"} } - """, "Ctor"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Ctor"); var value = new ObjectArgumentValue(section, []); var result = value.ConvertTo(targetType, new()); @@ -693,14 +713,15 @@ class WithTypeArgumentClassCtor : AnAbstractClass public void ConvertToExplicitTypeUsingTypeAsConstructorArgument() { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "Ctor": { "$type": "Serilog.Settings.Configuration.Tests.ObjectArgumentValueTests+WithTypeArgumentClassCtor, Serilog.Settings.Configuration.Tests", "type": "Serilog.Settings.Configuration.Tests.ObjectArgumentValueTests+PrivateImplWithPublicCtor, Serilog.Settings.Configuration.Tests" } } - """, "Ctor"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Ctor"); var value = new ObjectArgumentValue(section, []); var result = value.ConvertTo(typeof(AnAbstractClass), new()); @@ -738,7 +759,7 @@ public WithOverloads(int a, TimeSpan b, Uri c, string d = "d") public void ConvertToExplicitTypePickingConstructorOverloadWithMostMatchingArguments(string dJson, string? d) { // language=json - var section = JsonStringConfigSource.LoadSection($$""" + var json = $$""" { "Ctor": { "type": "Serilog.Settings.Configuration.Tests.ObjectArgumentValueTests+WithOverloads, Serilog.Settings.Configuration.Tests", @@ -748,7 +769,8 @@ public void ConvertToExplicitTypePickingConstructorOverloadWithMostMatchingArgum {{dJson}} } } - """, "Ctor"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Ctor"); var value = new ObjectArgumentValue(section, []); var result = value.ConvertTo(typeof(IAmAnInterface), new()); @@ -764,7 +786,7 @@ public void ConvertToExplicitTypePickingConstructorOverloadWithMostMatchingArgum public void ConvertToExplicitTypeMatchingArgumentsCaseInsensitively() { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "Ctor": { "type": "Serilog.Settings.Configuration.Tests.ObjectArgumentValueTests+WithOverloads, Serilog.Settings.Configuration.Tests", @@ -773,7 +795,8 @@ public void ConvertToExplicitTypeMatchingArgumentsCaseInsensitively() "C": "http://dot.com/" } } - """, "Ctor"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Ctor"); var value = new ObjectArgumentValue(section, []); var result = value.ConvertTo(typeof(IAmAnInterface), new()); @@ -800,7 +823,7 @@ class WithSimilarOverloads : IAmAnInterface public void ConvertToExplicitTypePickingConstructorOverloadWithMostStrings() { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "Ctor": { "type": "Serilog.Settings.Configuration.Tests.ObjectArgumentValueTests+WithSimilarOverloads, Serilog.Settings.Configuration.Tests", @@ -809,7 +832,8 @@ public void ConvertToExplicitTypePickingConstructorOverloadWithMostStrings() "c": 3 } } - """, "Ctor"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Ctor"); var value = new ObjectArgumentValue(section, []); var result = value.ConvertTo(typeof(IAmAnInterface), new()); @@ -836,14 +860,15 @@ class OnlyDifferentTypeOverloads : IAmAnInterface public void ConvertToExplicitTypePickingFirstMatchWhenOtherwiseAmbiguous() { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "Ctor": { "type": "Serilog.Settings.Configuration.Tests.ObjectArgumentValueTests+OnlyDifferentTypeOverloads, Serilog.Settings.Configuration.Tests", "value": 123 } } - """, "Ctor"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Ctor"); var value = new ObjectArgumentValue(section, []); var result = value.ConvertTo(typeof(IAmAnInterface), new()); @@ -872,18 +897,19 @@ public WithDefaults(int a, int b = 2, int c = 3) [InlineData(",\"b\": 5", 5, 3)] [InlineData(",\"c\": 6", 2, 6)] [InlineData(",\"b\": 7, \"c\": 8", 7, 8)] - public void ConvertToExplicitTypeFillingInDefaultsInConstructor(string json, int b, int c) + public void ConvertToExplicitTypeFillingInDefaultsInConstructor(string jsonPart, int b, int c) { // language=json - var section = JsonStringConfigSource.LoadSection($$""" + var json = $$""" { "Ctor": { "type": "Serilog.Settings.Configuration.Tests.ObjectArgumentValueTests+WithDefaults, Serilog.Settings.Configuration.Tests", "a": 1 - {{json}} + {{jsonPart}} } } - """, "Ctor"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Ctor"); var value = new ObjectArgumentValue(section, []); var result = value.ConvertTo(typeof(IAmAnInterface), new()); @@ -1098,7 +1124,7 @@ public void ConvertToExplicitTypeWithExplicitStructConstructorArgument() public void ConvertToExplicitTypeWithExplicitTypeConstructorArgument() { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "Ctor": { "type": "Serilog.Settings.Configuration.Tests.Support.GenericClass`1[[Serilog.Settings.Configuration.Tests.Support.IAmAnInterface, Serilog.Settings.Configuration.Tests]], Serilog.Settings.Configuration.Tests", @@ -1107,7 +1133,8 @@ public void ConvertToExplicitTypeWithExplicitTypeConstructorArgument() } } } - """, "Ctor"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Ctor"); var value = new ObjectArgumentValue(section, []); var result = value.ConvertTo(typeof(IAmAnInterface), new()); @@ -1134,11 +1161,12 @@ public void ConvertToExplicitTypeWithExplicitTypeConstructorArgument() public void ConvertToPrimitives(Type type, object expected, string sectionValue) { // language=json - var section = JsonStringConfigSource.LoadSection($$""" + var json = $$""" { "Serilog": {{sectionValue}} } - """, "Serilog"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Serilog"); var value = new ObjectArgumentValue(section, []); var actual = value.ConvertTo(type, new()); @@ -1152,11 +1180,12 @@ public void ConvertToPrimitives(Type type, object expected, string sectionValue) public void ConvertToNullablePrimitive() { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "Serilog": 123 } - """, "Serilog"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Serilog"); var value = new ObjectArgumentValue(section, []); var actual = value.ConvertTo(typeof(int?), new()); @@ -1169,11 +1198,12 @@ public void ConvertToNullablePrimitive() public void ConvertToNullWhenEmptyNullable() { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "Serilog": null } - """, "Serilog"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Serilog"); var value = new ObjectArgumentValue(section, []); var actual = value.ConvertTo(typeof(int?), new()); @@ -1185,11 +1215,12 @@ public void ConvertToNullWhenEmptyNullable() public void ConvertToPlainClass() { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "Serilog": { "foo" : "bar" } } - """, "Serilog"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Serilog"); var value = new ObjectArgumentValue(section, []); var actual = value.ConvertTo(typeof(TestDummies.DummyLoggerConfigurationExtensions.Binding), new()); @@ -1209,11 +1240,12 @@ struct PlainStruct public void ConvertToPlainStruct() { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "Serilog": { "A" : "1" } } - """, "Serilog"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Serilog"); var value = new ObjectArgumentValue(section, []); var actual = value.ConvertTo(typeof(PlainStruct), new()); @@ -1230,11 +1262,12 @@ public void ConvertToPlainStruct() public void ConvertToNullWhenStructIsNull() { // language=json - var section = JsonStringConfigSource.LoadSection(""" + var json = """ { "Serilog": null } - """, "Serilog"); + """; + var section = JsonStringConfigSource.LoadSection(json, "Serilog"); var value = new ObjectArgumentValue(section, []); var actual = value.ConvertTo(typeof(PlainStruct), new());