From 433a779eafa831d29dbeefda194f7355afe84528 Mon Sep 17 00:00:00 2001 From: Layomi Akinrinade Date: Mon, 24 Jul 2023 11:05:58 -0700 Subject: [PATCH] Fix config src-gen issues with binding nullable values --- .../gen/Helpers/Emitter/CoreBindingHelper.cs | 9 +- .../tests/Common/ConfigurationBinderTests.cs | 85 ++++++++++++++++--- 2 files changed, 78 insertions(+), 16 deletions(-) diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/CoreBindingHelper.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/CoreBindingHelper.cs index 08015d0abc59fe..bcd4195c192bb7 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/CoreBindingHelper.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/gen/Helpers/Emitter/CoreBindingHelper.cs @@ -98,11 +98,12 @@ private void EmitGetCoreMethod() foreach (TypeSpec type in types) { - TypeSpecKind kind = type.SpecKind; + TypeSpec effectiveType = type.EffectiveType; + TypeSpecKind kind = effectiveType.SpecKind; EmitStartBlock($"if (type == typeof({type.MinimalDisplayString}))"); - if (type is ParsableFromStringSpec stringParsableType) + if (effectiveType is ParsableFromStringSpec stringParsableType) { EmitCastToIConfigurationSection(); EmitBindLogicFromString( @@ -113,9 +114,9 @@ private void EmitGetCoreMethod() checkForNullSectionValue: stringParsableType.StringParsableTypeKind is not StringParsableTypeKind.AssignFromSectionValue, useIncrementalStringValueIdentifier: false); } - else if (!EmitInitException(type)) + else if (!EmitInitException(effectiveType)) { - EmitBindCoreCall(type, Identifier.obj, Identifier.configuration, InitializationKind.Declaration); + EmitBindCoreCall(effectiveType, Identifier.obj, Identifier.configuration, InitializationKind.Declaration); _writer.WriteLine($"return {Identifier.obj};"); } diff --git a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs index 098eeb44486eed..c48dfa8d8db0b6 100644 --- a/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs +++ b/src/libraries/Microsoft.Extensions.Configuration.Binder/tests/Common/ConfigurationBinderTests.cs @@ -217,7 +217,7 @@ public void EmptyStringIsNullable() } [Fact] - public void GetScalar() + public void Get_Scalar() { var dic = new Dictionary { @@ -239,7 +239,7 @@ public void GetScalar() } [Fact] - public void GetScalarNullable() + public void Get_ScalarNullable() { var dic = new Dictionary { @@ -260,6 +260,50 @@ public void GetScalarNullable() Assert.Equal(11, (int)config.GetValue(typeof(int?), "Nested:Integer")); } + [Fact] + public void GetValue_Scalar() + { + var dic = new Dictionary + { + {"Integer", "-2"}, + {"Boolean", "TRUe"}, + {"Nested:Integer", "11"} + }; + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(dic); + var config = configurationBuilder.Build(); + + Assert.True(config.GetSection("Boolean").Get()); + Assert.Equal(-2, config.GetSection("Integer").Get()); + Assert.Equal(11, config.GetSection("Nested:Integer").Get()); + + Assert.True((bool)config.GetSection("Boolean").Get(typeof(bool))); + Assert.Equal(-2, (int)config.GetSection("Integer").Get(typeof(int))); + Assert.Equal(11, (int)config.GetSection("Nested:Integer").Get(typeof(int))); + } + + [Fact] + public void GetValue_ScalarNullable() + { + var dic = new Dictionary + { + {"Integer", "-2"}, + {"Boolean", "TRUe"}, + {"Nested:Integer", "11"} + }; + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(dic); + var config = configurationBuilder.Build(); + + Assert.True(config.GetSection("Boolean").Get()); + Assert.Equal(-2, config.GetSection("Integer").Get()); + Assert.Equal(11, config.GetSection("Nested:Integer").Get()); + + Assert.True(config.GetSection("Boolean").Get(typeof(bool?)) is true); + Assert.Equal(-2, (int)config.GetSection("Integer").Get(typeof(int?))); + Assert.Equal(11, (int)config.GetSection("Nested:Integer").Get(typeof(int?))); + } + [Fact] public void CanBindToObjectProperty() { @@ -1358,18 +1402,35 @@ public void CanBindClassWithPrimaryCtor() [Fact] public void CanBindRecordStructOptions() { - var dic = new Dictionary + IConfiguration config = GetConfiguration("Length", "Color"); + Validate(config.Get()); + Validate(config.Get().Value); + + config = GetConfiguration("Options.Length", "Options.Color"); + // GetValue works for only primitives. + //Reflection impl handles them by honoring `TypeConverter` only. + // Source-gen supports based on an allow-list. + Assert.Equal(default(RecordStructTypeOptions), config.GetValue("Options")); + Assert.False(config.GetValue("Options").HasValue); + + static void Validate(RecordStructTypeOptions options) { - {"Length", "42"}, - {"Color", "Green"}, - }; - var configurationBuilder = new ConfigurationBuilder(); - configurationBuilder.AddInMemoryCollection(dic); - var config = configurationBuilder.Build(); + Assert.Equal(42, options.Length); + Assert.Equal("Green", options.Color); + } - var options = config.Get(); - Assert.Equal(42, options.Length); - Assert.Equal("Green", options.Color); + static IConfiguration GetConfiguration(string key1, string key2) + { + var dic = new Dictionary + { + { key1, "42" }, + { key2, "Green" }, + }; + + var configurationBuilder = new ConfigurationBuilder(); + configurationBuilder.AddInMemoryCollection(dic); + return configurationBuilder.Build(); + } } [Fact]