Skip to content

Commit

Permalink
[release/8.0] Fix Options Source Gen with Length attributes applied o…
Browse files Browse the repository at this point in the history
…n properties of Interface type (#93482)

* Fix Options Source Gen with LengthAttributes applied on properties of Interface type

* Add more to the test case

---------

Co-authored-by: Tarek Mahmoud Sayed <tarekms@microsoft.com>
  • Loading branch information
github-actions[bot] and tarekgh authored Oct 13, 2023
1 parent c47e96c commit fb1d8b4
Show file tree
Hide file tree
Showing 5 changed files with 525 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,26 @@ internal static bool TypeHasProperty(ITypeSymbol typeSymbol, string propertyName

if (type.GetMembers(propertyName).OfType<IPropertySymbol>().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;
}

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<T> extends ICollection<T> where the property we're looking for is defined.
foreach (var interfaceType in typeSymbol.AllInterfaces)
{
if (interfaceType.GetMembers(propertyName).OfType<IPropertySymbol>().Any(property =>
property.Type.SpecialType == returnType && property.Kind == SymbolKind.Property &&
!property.IsStatic && property.GetMethod != null && property.Parameters.IsEmpty))
{
return true;
}
}

return false;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@

// <auto-generated/>
#nullable enable
#pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103
namespace Test
{
partial class MyOptionsValidator
{
/// <summary>
/// Validates a specific named options instance (or all when <paramref name="name"/> is <see langword="null" />).
/// </summary>
/// <param name="name">The name of the options instance being validated.</param>
/// <param name="options">The options instance.</param>
/// <returns>Validation result.</returns>
[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<global::System.ComponentModel.DataAnnotations.ValidationResult>();
var validationAttributes = new global::System.Collections.Generic.List<global::System.ComponentModel.DataAnnotations.ValidationAttribute>(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<string>)
{
length = ((global::System.Collections.Generic.IList<string>)value).Count;
}
else if (value is global::System.Collections.Generic.ICollection<string>)
{
length = ((global::System.Collections.Generic.ICollection<string>)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<string>)
{
length = ((global::System.Collections.Generic.IList<string>)value).Count;
}
else if (value is global::System.Collections.Generic.ICollection<string>)
{
length = ((global::System.Collections.Generic.ICollection<string>)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<string>)
{
length = ((global::System.Collections.Generic.IList<string>)value).Count;
}
else if (value is global::System.Collections.Generic.ICollection<string>)
{
length = ((global::System.Collections.Generic.ICollection<string>)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);
}
}
Loading

0 comments on commit fb1d8b4

Please sign in to comment.