From 39413cc05b4d162db5f616da83c5be96a16ed812 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Thu, 12 Oct 2023 15:40:38 -0700 Subject: [PATCH 1/2] Fix Options Source Gen with LengthAttributes applied on properties of Interface type --- .../gen/ParserUtilities.cs | 14 ++++++- .../tests/SourceGeneration.Unit.Tests/Main.cs | 40 +++++++++++++++++++ .../OptionsRuntimeTests.cs | 39 ++++++++++++++++++ 3 files changed, 92 insertions(+), 1 deletion(-) diff --git a/src/libraries/Microsoft.Extensions.Options/gen/ParserUtilities.cs b/src/libraries/Microsoft.Extensions.Options/gen/ParserUtilities.cs index 675202538e12d2..0b63cc90c800ed 100644 --- a/src/libraries/Microsoft.Extensions.Options/gen/ParserUtilities.cs +++ b/src/libraries/Microsoft.Extensions.Options/gen/ParserUtilities.cs @@ -80,7 +80,7 @@ internal static bool TypeHasProperty(ITypeSymbol typeSymbol, string propertyName if (type.GetMembers(propertyName).OfType().Any(property => property.Type.SpecialType == returnType && property.DeclaredAccessibility == Accessibility.Public && - !property.IsStatic && property.GetMethod != null && property.Parameters.IsEmpty)) + property.Kind == SymbolKind.Property && !property.IsStatic && property.GetMethod != null && property.Parameters.IsEmpty)) { return true; } @@ -88,6 +88,18 @@ internal static bool TypeHasProperty(ITypeSymbol typeSymbol, string propertyName type = type.BaseType; } while (type is not null && type.SpecialType != SpecialType.System_Object); + // When we have an interface type, we need to check all the interfaces that it extends. + // Like IList extends ICollection where the property we're looking for is defined. + foreach (var interfaceType in typeSymbol.AllInterfaces) + { + if (interfaceType.GetMembers(propertyName).OfType().Any(property => + property.Type.SpecialType == returnType && property.Kind == SymbolKind.Property && + !property.IsStatic && property.GetMethod != null && property.Parameters.IsEmpty)) + { + return true; + } + } + return false; } diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Main.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Main.cs index da893126a14b88..efd3107b34bb6a 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Main.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Main.cs @@ -1813,4 +1813,44 @@ public sealed partial class OptionsUsingGeneratedAttributesValidator : IValidate Assert.True(emitResult.Success); // Console.WriteLine(emittedSource); } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task UsingInterfaceAsPropertyTypeForLengthAttributesTests() + { + var (diagnostics, generatedSources) = await RunGenerator(@""" + using System.Collections.Generic; + + public class MyOptions + { + [Length(10, 20)] + public IList P1 { get; set; } + + [MinLength(4)] + public IList P2 { get; set; } + + [MaxLength(5)] + public IList P3 { get; set; } + + [Length(10, 20)] + public ICollection P4 { get; set; } + + [MinLength(4)] + public ICollection P5 { get; set; } + + [MaxLength(5)] + public ICollection P6 { get; set; } + } + + [OptionsValidator] + public partial class MyOptionsValidator : IValidateOptions + { + } + """); + + Assert.Empty(diagnostics); + Assert.Single(generatedSources); + + // string generatedSource = File.ReadAllText(@"Baselines/DataAnnotationAttributesWithParams.g.cs"); + // Assert.Equal(generatedSource.Replace("\r\n", "\n"), generatedSources[0].SourceText.ToString().Replace("\r\n", "\n")); + } } diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/OptionsRuntimeTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/OptionsRuntimeTests.cs index 78783d7a537e29..d9bd0808cf77f6 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/OptionsRuntimeTests.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/OptionsRuntimeTests.cs @@ -287,6 +287,12 @@ public void TestCustomGeneratedAttributes() P23 = new List() { "1", "2", "3", "4" }, P24 = new FakeCount(4), P25 = new FakeCountChild(4), + P27 = new List { "1", "2" }, + P28 = new HashSet { "1", "2" }, + P29 = new List { "1", "2", "3" }, + P30 = new HashSet { "1", "2", "3" }, + P31 = new List { 1, 2, 3, 4 }, + P32 = new HashSet { 1, 2, 3, 4 }, #endif // NET8_0_OR_GREATER P1 = 2, P2 = "12345", @@ -325,6 +331,12 @@ public void TestCustomGeneratedAttributes() P23 = new List() { "1", "2", "3", "4", "5" }, P24 = new FakeCount(5), P25 = new FakeCountChild(5), + P27 = new List { "1" }, + P28 = new HashSet { "1" }, + P29 = new List { "1", "2" }, + P30 = new HashSet { "1", "2" }, + P31 = new List { 1, 2, 3, 4, 5 }, + P32 = new HashSet { 1, 2, 3, 4, 5 }, #endif // NET8_0_OR_GREATER P1 = 4, P2 = "1234", @@ -362,6 +374,12 @@ public void TestCustomGeneratedAttributes() "P23: The field OptionsUsingGeneratedAttributes.P23 must be a string or array type with a maximum length of '4'.", "P24: The field OptionsUsingGeneratedAttributes.P24 must be a string or array type with a maximum length of '4'.", "P25: The field OptionsUsingGeneratedAttributes.P25 must be a string or array type with a maximum length of '4'.", + "P27: The field OptionsUsingGeneratedAttributes.P27 must be a string or collection type with a minimum length of '2' and maximum length of '10'.", + "P28: The field OptionsUsingGeneratedAttributes.P28 must be a string or collection type with a minimum length of '2' and maximum length of '10'.", + "P29: The field OptionsUsingGeneratedAttributes.P29 must be a string or array type with a minimum length of '3'.", + "P30: The field OptionsUsingGeneratedAttributes.P30 must be a string or array type with a minimum length of '3'.", + "P31: The field OptionsUsingGeneratedAttributes.P31 must be a string or array type with a maximum length of '4'.", + "P32: The field OptionsUsingGeneratedAttributes.P32 must be a string or array type with a maximum length of '4'.", #endif // NET8_0_OR_GREATER "P1: The field OptionsUsingGeneratedAttributes.P1 must be between 1 and 3.", "P2: The field OptionsUsingGeneratedAttributes.P2 must be a string or array type with a minimum length of '5'.", @@ -412,6 +430,9 @@ public class OptionsUsingGeneratedAttributes [LengthAttribute(2, 10)] public int[] P17 { get; set; } + // Although MinLength and MaxLength attributes defined in NETFX but the implementation there has a bug which can produce exception like the following when using types like List: + // System.InvalidCastException : Unable to cast object of type 'System.Collections.Generic.List`1[System.String]' to type 'System.Array'. + [MinLengthAttribute(3)] public List P18 { get; set; } @@ -429,6 +450,24 @@ public class OptionsUsingGeneratedAttributes [MaxLengthAttribute(4)] public FakeCountChild P25 { get; set; } + + [LengthAttribute(2, 10)] + public IList P27 { get; set; } + + [LengthAttribute(2, 10)] + public ICollection P28 { get; set; } + + [MinLengthAttribute(3)] + public IList P29 { get; set; } + + [MinLengthAttribute(3)] + public ICollection P30 { get; set; } + + [MaxLengthAttribute(4)] + public IList P31 { get; set; } + + [MaxLengthAttribute(4)] + public ICollection P32 { get; set; } #endif // NET8_0_OR_GREATER [RangeAttribute(1, 3)] From 12a29da8fa40e218b18add119b0cc225270fa4b5 Mon Sep 17 00:00:00 2001 From: Tarek Mahmoud Sayed Date: Thu, 12 Oct 2023 16:34:24 -0700 Subject: [PATCH 2/2] Add more to the test case --- ...yTypeForLengthAttributesTests.netcore.g.cs | 252 ++++++++++++++++++ ...rtyTypeForLengthAttributesTests.netfx.g.cs | 177 ++++++++++++ .../tests/SourceGeneration.Unit.Tests/Main.cs | 8 +- 3 files changed, 435 insertions(+), 2 deletions(-) create mode 100644 src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/UsingInterfaceAsPropertyTypeForLengthAttributesTests.netcore.g.cs create mode 100644 src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/UsingInterfaceAsPropertyTypeForLengthAttributesTests.netfx.g.cs diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/UsingInterfaceAsPropertyTypeForLengthAttributesTests.netcore.g.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/UsingInterfaceAsPropertyTypeForLengthAttributesTests.netcore.g.cs new file mode 100644 index 00000000000000..1cd942fab0f1bb --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/UsingInterfaceAsPropertyTypeForLengthAttributesTests.netcore.g.cs @@ -0,0 +1,252 @@ + + // + #nullable enable + #pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103 + namespace Test +{ + partial class MyOptionsValidator + { + /// + /// Validates a specific named options instance (or all when is ). + /// + /// The name of the options instance being validated. + /// The options instance. + /// Validation result. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + [System.Diagnostics.CodeAnalysis.UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode", + Justification = "The created ValidationContext object is used in a way that never call reflection")] + public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Test.MyOptions options) + { + global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null; + var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options); + var validationResults = new global::System.Collections.Generic.List(); + var validationAttributes = new global::System.Collections.Generic.List(1); + + context.MemberName = "P1"; + context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.P1" : $"{name}.P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + context.MemberName = "P2"; + context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.P2" : $"{name}.P2"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P2, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + context.MemberName = "P3"; + context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.P3" : $"{name}.P3"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A3); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P3, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + context.MemberName = "P4"; + context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.P4" : $"{name}.P4"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + context.MemberName = "P5"; + context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.P5" : $"{name}.P5"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + context.MemberName = "P6"; + context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.P6" : $"{name}.P6"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A3); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P6, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build(); + } + } +} +namespace __OptionValidationStaticInstances +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + file static class __Attributes + { + internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__LengthAttribute A1 = new __OptionValidationGeneratedAttributes.__SourceGen__LengthAttribute( + (int)10, + (int)20); + + internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__MinLengthAttribute A2 = new __OptionValidationGeneratedAttributes.__SourceGen__MinLengthAttribute( + (int)4); + + internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__MaxLengthAttribute A3 = new __OptionValidationGeneratedAttributes.__SourceGen__MaxLengthAttribute( + (int)5); + } +} +namespace __OptionValidationStaticInstances +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + file static class __Validators + { + } +} +namespace __OptionValidationGeneratedAttributes +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)] + file class __SourceGen__LengthAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute + { + private static string DefaultErrorMessageString => "The field {0} must be a string or collection type with a minimum length of '{1}' and maximum length of '{2}'."; + public __SourceGen__LengthAttribute(int minimumLength, int maximumLength) : base(() => DefaultErrorMessageString) { MinimumLength = minimumLength; MaximumLength = maximumLength; } + public int MinimumLength { get; } + public int MaximumLength { get; } + public override bool IsValid(object? value) + { + if (MinimumLength < 0) + { + throw new global::System.InvalidOperationException("LengthAttribute must have a MinimumLength value that is zero or greater."); + } + if (MaximumLength < MinimumLength) + { + throw new global::System.InvalidOperationException("LengthAttribute must have a MaximumLength value that is greater than or equal to MinimumLength."); + } + if (value == null) + { + return true; + } + + int length; + if (value is string stringValue) + { + length = stringValue.Length; + } + else if (value is System.Collections.ICollection collectionValue) + { + length = collectionValue.Count; + } + else if (value is global::System.Collections.Generic.IList) + { + length = ((global::System.Collections.Generic.IList)value).Count; + } + else if (value is global::System.Collections.Generic.ICollection) + { + length = ((global::System.Collections.Generic.ICollection)value).Count; + } + else + { + throw new global::System.InvalidCastException($"The field of type {value.GetType()} must be a string, array, or ICollection type."); + } + + return (uint)(length - MinimumLength) <= (uint)(MaximumLength - MinimumLength); + } + public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, MinimumLength, MaximumLength); + } + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)] + file class __SourceGen__MaxLengthAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute + { + private const int MaxAllowableLength = -1; + private static string DefaultErrorMessageString => "The field {0} must be a string or array type with a maximum length of '{1}'."; + public __SourceGen__MaxLengthAttribute(int length) : base(() => DefaultErrorMessageString) { Length = length; } + public __SourceGen__MaxLengthAttribute(): base(() => DefaultErrorMessageString) { Length = MaxAllowableLength; } + public int Length { get; } + public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, Length); + public override bool IsValid(object? value) + { + if (Length == 0 || Length < -1) + { + throw new global::System.InvalidOperationException("MaxLengthAttribute must have a Length value that is greater than zero. Use MaxLength() without parameters to indicate that the string or array can have the maximum allowable length."); + } + if (value == null || MaxAllowableLength == Length) + { + return true; + } + + int length; + if (value is string stringValue) + { + length = stringValue.Length; + } + else if (value is System.Collections.ICollection collectionValue) + { + length = collectionValue.Count; + } + else if (value is global::System.Collections.Generic.IList) + { + length = ((global::System.Collections.Generic.IList)value).Count; + } + else if (value is global::System.Collections.Generic.ICollection) + { + length = ((global::System.Collections.Generic.ICollection)value).Count; + } + else + { + throw new global::System.InvalidCastException($"The field of type {value.GetType()} must be a string, array, or ICollection type."); + } + + return length <= Length; + } + } + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)] + file class __SourceGen__MinLengthAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute + { + private static string DefaultErrorMessageString => "The field {0} must be a string or array type with a minimum length of '{1}'."; + + public __SourceGen__MinLengthAttribute(int length) : base(() => DefaultErrorMessageString) { Length = length; } + public int Length { get; } + public override bool IsValid(object? value) + { + if (Length < -1) + { + throw new global::System.InvalidOperationException("MinLengthAttribute must have a Length value that is zero or greater."); + } + if (value == null) + { + return true; + } + + int length; + if (value is string stringValue) + { + length = stringValue.Length; + } + else if (value is System.Collections.ICollection collectionValue) + { + length = collectionValue.Count; + } + else if (value is global::System.Collections.Generic.IList) + { + length = ((global::System.Collections.Generic.IList)value).Count; + } + else if (value is global::System.Collections.Generic.ICollection) + { + length = ((global::System.Collections.Generic.ICollection)value).Count; + } + else + { + throw new global::System.InvalidCastException($"The field of type {value.GetType()} must be a string, array, or ICollection type."); + } + + return length >= Length; + } + public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, Length); + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/UsingInterfaceAsPropertyTypeForLengthAttributesTests.netfx.g.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/UsingInterfaceAsPropertyTypeForLengthAttributesTests.netfx.g.cs new file mode 100644 index 00000000000000..603680a9ec732c --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Baselines/UsingInterfaceAsPropertyTypeForLengthAttributesTests.netfx.g.cs @@ -0,0 +1,177 @@ + + // + #nullable enable + #pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103 + namespace Test +{ + partial class MyOptionsValidator + { + /// + /// Validates a specific named options instance (or all when is ). + /// + /// The name of the options instance being validated. + /// The options instance. + /// Validation result. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + public global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Test.MyOptions options) + { + global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder? builder = null; + var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options); + var validationResults = new global::System.Collections.Generic.List(); + var validationAttributes = new global::System.Collections.Generic.List(1); + + context.MemberName = "P2"; + context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.P2" : $"{name}.P2"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P2, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + context.MemberName = "P3"; + context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.P3" : $"{name}.P3"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P3, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + context.MemberName = "P5"; + context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.P5" : $"{name}.P5"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + context.MemberName = "P6"; + context.DisplayName = string.IsNullOrEmpty(name) ? "MyOptions.P6" : $"{name}.P6"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P6, context, validationResults, validationAttributes)) + { + (builder ??= new()).AddResults(validationResults); + } + + return builder is null ? global::Microsoft.Extensions.Options.ValidateOptionsResult.Success : builder.Build(); + } + } +} +namespace __OptionValidationStaticInstances +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + file static class __Attributes + { + internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__MinLengthAttribute A1 = new __OptionValidationGeneratedAttributes.__SourceGen__MinLengthAttribute( + (int)4); + + internal static readonly __OptionValidationGeneratedAttributes.__SourceGen__MaxLengthAttribute A2 = new __OptionValidationGeneratedAttributes.__SourceGen__MaxLengthAttribute( + (int)5); + } +} +namespace __OptionValidationStaticInstances +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + file static class __Validators + { + } +} +namespace __OptionValidationGeneratedAttributes +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)] + file class __SourceGen__MaxLengthAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute + { + private const int MaxAllowableLength = -1; + private static string DefaultErrorMessageString => "The field {0} must be a string or array type with a maximum length of '{1}'."; + public __SourceGen__MaxLengthAttribute(int length) : base(() => DefaultErrorMessageString) { Length = length; } + public __SourceGen__MaxLengthAttribute(): base(() => DefaultErrorMessageString) { Length = MaxAllowableLength; } + public int Length { get; } + public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, Length); + public override bool IsValid(object? value) + { + if (Length == 0 || Length < -1) + { + throw new global::System.InvalidOperationException("MaxLengthAttribute must have a Length value that is greater than zero. Use MaxLength() without parameters to indicate that the string or array can have the maximum allowable length."); + } + if (value == null || MaxAllowableLength == Length) + { + return true; + } + + int length; + if (value is string stringValue) + { + length = stringValue.Length; + } + else if (value is System.Collections.ICollection collectionValue) + { + length = collectionValue.Count; + } + else if (value is global::System.Collections.Generic.IList) + { + length = ((global::System.Collections.Generic.IList)value).Count; + } + else if (value is global::System.Collections.Generic.ICollection) + { + length = ((global::System.Collections.Generic.ICollection)value).Count; + } + else + { + throw new global::System.InvalidCastException($"The field of type {value.GetType()} must be a string, array, or ICollection type."); + } + + return length <= Length; + } + } + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + [global::System.AttributeUsage(global::System.AttributeTargets.Property | global::System.AttributeTargets.Field | global::System.AttributeTargets.Parameter, AllowMultiple = false)] + file class __SourceGen__MinLengthAttribute : global::System.ComponentModel.DataAnnotations.ValidationAttribute + { + private static string DefaultErrorMessageString => "The field {0} must be a string or array type with a minimum length of '{1}'."; + + public __SourceGen__MinLengthAttribute(int length) : base(() => DefaultErrorMessageString) { Length = length; } + public int Length { get; } + public override bool IsValid(object? value) + { + if (Length < -1) + { + throw new global::System.InvalidOperationException("MinLengthAttribute must have a Length value that is zero or greater."); + } + if (value == null) + { + return true; + } + + int length; + if (value is string stringValue) + { + length = stringValue.Length; + } + else if (value is System.Collections.ICollection collectionValue) + { + length = collectionValue.Count; + } + else if (value is global::System.Collections.Generic.IList) + { + length = ((global::System.Collections.Generic.IList)value).Count; + } + else if (value is global::System.Collections.Generic.ICollection) + { + length = ((global::System.Collections.Generic.ICollection)value).Count; + } + else + { + throw new global::System.InvalidCastException($"The field of type {value.GetType()} must be a string, array, or ICollection type."); + } + + return length >= Length; + } + public override string FormatErrorMessage(string name) => string.Format(global::System.Globalization.CultureInfo.CurrentCulture, ErrorMessageString, name, Length); + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Main.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Main.cs index efd3107b34bb6a..623251707f87ba 100644 --- a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Main.cs +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Main.cs @@ -1850,7 +1850,11 @@ public partial class MyOptionsValidator : IValidateOptions Assert.Empty(diagnostics); Assert.Single(generatedSources); - // string generatedSource = File.ReadAllText(@"Baselines/DataAnnotationAttributesWithParams.g.cs"); - // Assert.Equal(generatedSource.Replace("\r\n", "\n"), generatedSources[0].SourceText.ToString().Replace("\r\n", "\n")); +#if NETCOREAPP + string generatedSource = File.ReadAllText(@"Baselines/UsingInterfaceAsPropertyTypeForLengthAttributesTests.netcore.g.cs"); +#else + string generatedSource = File.ReadAllText(@"Baselines/UsingInterfaceAsPropertyTypeForLengthAttributesTests.netfx.g.cs"); +#endif // NETCOREAPP + Assert.Equal(generatedSource.Replace("\r\n", "\n"), generatedSources[0].SourceText.ToString().Replace("\r\n", "\n")); } }