diff --git a/docs/project/list-of-diagnostics.md b/docs/project/list-of-diagnostics.md index 2d1eab1db0293..c3732de3b02a1 100644 --- a/docs/project/list-of-diagnostics.md +++ b/docs/project/list-of-diagnostics.md @@ -235,7 +235,24 @@ The diagnostic id values reserved for .NET Libraries analyzer warnings are `SYSL | __`SYSLIB1116`__ | *_`SYSLIB1100`-`SYSLIB1118` reserved for Microsoft.Extensions.Configuration.Binder.SourceGeneration.* | | __`SYSLIB1117`__ | *_`SYSLIB1100`-`SYSLIB1118` reserved for Microsoft.Extensions.Configuration.Binder.SourceGeneration.* | | __`SYSLIB1118`__ | *_`SYSLIB1100`-`SYSLIB1118` reserved for Microsoft.Extensions.Configuration.Binder.SourceGeneration.* | - +| __`SYSLIB1201`__ | Options validation generator: Can't use 'ValidateObjectMembersAttribute' or `ValidateEnumeratedItemsAttribute` on fields or properties with open generic types. | +| __`SYSLIB1202`__ | Options validation generator: A member type has no fields or properties to validate. | +| __`SYSLIB1203`__ | Options validation generator: A type has no fields or properties to validate. | +| __`SYSLIB1204`__ | Options validation generator: A type annotated with `OptionsValidatorAttribute` doesn't implement the necessary interface. | +| __`SYSLIB1205`__ | Options validation generator: A type already includes an implementation of the 'Validate' method. | +| __`SYSLIB1206`__ | Options validation generator: Can't validate private fields or properties. | +| __`SYSLIB1207`__ | Options validation generator: Member type is not enumerable. | +| __`SYSLIB1208`__ | Options validation generator: Validators used for transitive or enumerable validation must have a constructor with no parameters. | +| __`SYSLIB1209`__ | Options validation generator: `OptionsValidatorAttribute` can't be applied to a static class. | +| __`SYSLIB1210`__ | Options validation generator: Null validator type specified for the `ValidateObjectMembersAttribute` or 'ValidateEnumeratedItemsAttribute' attributes. | +| __`SYSLIB1211`__ | Options validation generator: Unsupported circular references in model types. | +| __`SYSLIB1212`__ | Options validation generator: Member potentially missing transitive validation. | +| __`SYSLIB1213`__ | Options validation generator: Member potentially missing enumerable validation. | +| __`SYSLIB1214`__ | *_`SYSLIB1214`-`SYSLIB1218` reserved for Microsoft.Extensions.Options.SourceGeneration.* | +| __`SYSLIB1215`__ | *_`SYSLIB1214`-`SYSLIB1218` reserved for Microsoft.Extensions.Options.SourceGeneration.* | +| __`SYSLIB1216`__ | *_`SYSLIB1214`-`SYSLIB1218` reserved for Microsoft.Extensions.Options.SourceGeneration.* | +| __`SYSLIB1217`__ | *_`SYSLIB1214`-`SYSLIB1218` reserved for Microsoft.Extensions.Options.SourceGeneration.* | +| __`SYSLIB1218`__ | *_`SYSLIB1214`-`SYSLIB1218` reserved for Microsoft.Extensions.Options.SourceGeneration.* | ### Diagnostic Suppressions (`SYSLIBSUPPRESS****`) diff --git a/src/libraries/Common/src/System/ThrowHelper.cs b/src/libraries/Common/src/System/ThrowHelper.cs index 921394be4cd17..4257c05891e32 100644 --- a/src/libraries/Common/src/System/ThrowHelper.cs +++ b/src/libraries/Common/src/System/ThrowHelper.cs @@ -31,6 +31,46 @@ internal static void ThrowIfNull( [DoesNotReturn] #endif private static void Throw(string? paramName) => throw new ArgumentNullException(paramName); + + /// + /// Throws either an or an + /// if the specified string is or whitespace respectively. + /// + /// String to be checked for or whitespace. + /// The name of the parameter being checked. + /// The original value of . + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#if NETCOREAPP3_0_OR_GREATER + [return: NotNull] +#endif + public static string IfNullOrWhitespace( +#if NETCOREAPP3_0_OR_GREATER + [NotNull] +#endif + string? argument, + [CallerArgumentExpression(nameof(argument))] string paramName = "") + { +#if !NETCOREAPP3_1_OR_GREATER + if (argument == null) + { + throw new ArgumentNullException(paramName); + } +#endif + + if (string.IsNullOrWhiteSpace(argument)) + { + if (argument == null) + { + throw new ArgumentNullException(paramName); + } + else + { + throw new ArgumentException(paramName, "Argument is whitespace"); + } + } + + return argument; + } } } diff --git a/src/libraries/Microsoft.Extensions.Options/gen/DiagDescriptors.cs b/src/libraries/Microsoft.Extensions.Options/gen/DiagDescriptors.cs new file mode 100644 index 0000000000000..e76363fee0e94 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/DiagDescriptors.cs @@ -0,0 +1,95 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.CodeAnalysis; +using System; + +namespace Microsoft.Extensions.Options.Generators +{ + internal sealed class DiagDescriptors : DiagDescriptorsBase + { + private const string Category = "Microsoft.Extensions.Options.SourceGeneration"; + + public static DiagnosticDescriptor CantUseWithGenericTypes { get; } = Make( + id: "SYSLIB1201", + title: SR.CantUseWithGenericTypesTitle, + messageFormat: SR.CantUseWithGenericTypesMessage, + category: Category); + + public static DiagnosticDescriptor NoEligibleMember { get; } = Make( + id: "SYSLIB1202", + title: SR.NoEligibleMemberTitle, + messageFormat: SR.NoEligibleMemberMessage, + category: Category, + defaultSeverity: DiagnosticSeverity.Warning); + + public static DiagnosticDescriptor NoEligibleMembersFromValidator { get; } = Make( + id: "SYSLIB1203", + title: SR.NoEligibleMembersFromValidatorTitle, + messageFormat: SR.NoEligibleMembersFromValidatorMessage, + category: Category, + defaultSeverity: DiagnosticSeverity.Warning); + + public static DiagnosticDescriptor DoesntImplementIValidateOptions { get; } = Make( + id: "SYSLIB1204", + title: SR.DoesntImplementIValidateOptionsTitle, + messageFormat: SR.DoesntImplementIValidateOptionsMessage, + category: Category); + + public static DiagnosticDescriptor AlreadyImplementsValidateMethod { get; } = Make( + id: "SYSLIB1205", + title: SR.AlreadyImplementsValidateMethodTitle, + messageFormat: SR.AlreadyImplementsValidateMethodMessage, + category: Category); + + public static DiagnosticDescriptor MemberIsInaccessible { get; } = Make( + id: "SYSLIB1206", + title: SR.MemberIsInaccessibleTitle, + messageFormat: SR.MemberIsInaccessibleMessage, + category: Category); + + public static DiagnosticDescriptor NotEnumerableType { get; } = Make( + id: "SYSLIB1207", + title: SR.NotEnumerableTypeTitle, + messageFormat: SR.NotEnumerableTypeMessage, + category: Category); + + public static DiagnosticDescriptor ValidatorsNeedSimpleConstructor { get; } = Make( + id: "SYSLIB1208", + title: SR.ValidatorsNeedSimpleConstructorTitle, + messageFormat: SR.ValidatorsNeedSimpleConstructorMessage, + category: Category); + + public static DiagnosticDescriptor CantBeStaticClass { get; } = Make( + id: "SYSLIB1209", + title: SR.CantBeStaticClassTitle, + messageFormat: SR.CantBeStaticClassMessage, + category: Category); + + public static DiagnosticDescriptor NullValidatorType { get; } = Make( + id: "SYSLIB1210", + title: SR.NullValidatorTypeTitle, + messageFormat: SR.NullValidatorTypeMessage, + category: Category); + + public static DiagnosticDescriptor CircularTypeReferences { get; } = Make( + id: "SYSLIB1211", + title: SR.CircularTypeReferencesTitle, + messageFormat: SR.CircularTypeReferencesMessage, + category: Category); + + public static DiagnosticDescriptor PotentiallyMissingTransitiveValidation { get; } = Make( + id: "SYSLIB1212", + title: SR.PotentiallyMissingTransitiveValidationTitle, + messageFormat: SR.PotentiallyMissingTransitiveValidationMessage, + category: Category, + defaultSeverity: DiagnosticSeverity.Warning); + + public static DiagnosticDescriptor PotentiallyMissingEnumerableValidation { get; } = Make( + id: "SYSLIB1213", + title: SR.PotentiallyMissingEnumerableValidationTitle, + messageFormat: SR.PotentiallyMissingEnumerableValidationMessage, + category: Category, + defaultSeverity: DiagnosticSeverity.Warning); + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/gen/DiagDescriptorsBase.cs b/src/libraries/Microsoft.Extensions.Options/gen/DiagDescriptorsBase.cs new file mode 100644 index 0000000000000..f749e02394a67 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/DiagDescriptorsBase.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Microsoft.CodeAnalysis; + +namespace Microsoft.Extensions.Options.Generators +{ + #pragma warning disable CA1052 // Static holder types should be Static or NotInheritable + internal class DiagDescriptorsBase + #pragma warning restore CA1052 + { + protected static DiagnosticDescriptor Make( + string id, + string title, + string messageFormat, + string category, + DiagnosticSeverity defaultSeverity = DiagnosticSeverity.Error, + bool isEnabledByDefault = true) + { + return new( + id, + title, + messageFormat, + category, + defaultSeverity, + isEnabledByDefault); + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Emitter.cs b/src/libraries/Microsoft.Extensions.Options/gen/Emitter.cs new file mode 100644 index 0000000000000..91ad41a630bf6 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/Emitter.cs @@ -0,0 +1,387 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Microsoft.Extensions.Options.Generators +{ + /// + /// Emits option validation. + /// + internal sealed class Emitter : EmitterBase + { + private const string StaticValidationAttributeHolderClassName = "__Attributes"; + private const string StaticValidatorHolderClassName = "__Validators"; + private const string StaticFieldHolderClassesNamespace = "__OptionValidationStaticInstances"; + private const string StaticValidationAttributeHolderClassFQN = $"global::{StaticFieldHolderClassesNamespace}.{StaticValidationAttributeHolderClassName}"; + private const string StaticValidatorHolderClassFQN = $"global::{StaticFieldHolderClassesNamespace}.{StaticValidatorHolderClassName}"; + private const string StaticListType = "global::System.Collections.Generic.List"; + private const string StaticValidationResultType = "global::System.ComponentModel.DataAnnotations.ValidationResult"; + private const string StaticValidationAttributeType = "global::System.ComponentModel.DataAnnotations.ValidationAttribute"; + + private sealed record StaticFieldInfo(string FieldTypeFQN, int FieldOrder, string FieldName, IList InstantiationLines); + + public string Emit( + IEnumerable validatorTypes, + CancellationToken cancellationToken) + { + var staticValidationAttributesDict = new Dictionary(); + var staticValidatorsDict = new Dictionary(); + + foreach (var vt in validatorTypes.OrderBy(static lt => lt.Namespace + "." + lt.Name)) + { + cancellationToken.ThrowIfCancellationRequested(); + GenValidatorType(vt, ref staticValidationAttributesDict, ref staticValidatorsDict); + } + + GenStaticClassWithStaticReadonlyFields(staticValidationAttributesDict.Values, StaticFieldHolderClassesNamespace, StaticValidationAttributeHolderClassName); + GenStaticClassWithStaticReadonlyFields(staticValidatorsDict.Values, StaticFieldHolderClassesNamespace, StaticValidatorHolderClassName); + + return Capture(); + } + + private void GenValidatorType(ValidatorType vt, ref Dictionary staticValidationAttributesDict, ref Dictionary staticValidatorsDict) + { + if (vt.Namespace.Length > 0) + { + OutLn($"namespace {vt.Namespace}"); + OutOpenBrace(); + } + + foreach (var p in vt.ParentTypes) + { + OutLn(p); + OutOpenBrace(); + } + + if (vt.IsSynthetic) + { + OutGeneratedCodeAttribute(); + OutLn($"internal sealed partial {vt.DeclarationKeyword} {vt.Name}"); + } + else + { + OutLn($"partial {vt.DeclarationKeyword} {vt.Name}"); + } + + OutOpenBrace(); + + for (var i = 0; i < vt.ModelsToValidate.Count; i++) + { + var modelToValidate = vt.ModelsToValidate[i]; + + GenModelValidationMethod(modelToValidate, vt.IsSynthetic, ref staticValidationAttributesDict, ref staticValidatorsDict); + } + + OutCloseBrace(); + + foreach (var _ in vt.ParentTypes) + { + OutCloseBrace(); + } + + if (vt.Namespace.Length > 0) + { + OutCloseBrace(); + } + } + + private void GenStaticClassWithStaticReadonlyFields(IEnumerable staticFields, string classNamespace, string className) + { + OutLn($"namespace {classNamespace}"); + OutOpenBrace(); + + OutGeneratedCodeAttribute(); + OutLn($"internal static class {className}"); + OutOpenBrace(); + + var staticValidationAttributes = staticFields + .OrderBy(x => x.FieldOrder) + .ToArray(); + + for (var i = 0; i < staticValidationAttributes.Length; i++) + { + var attributeInstance = staticValidationAttributes[i]; + OutIndent(); + Out($"internal static readonly {attributeInstance.FieldTypeFQN} {attributeInstance.FieldName} = "); + for (var j = 0; j < attributeInstance.InstantiationLines.Count; j++) + { + var line = attributeInstance.InstantiationLines[j]; + Out(line); + if (j != attributeInstance.InstantiationLines.Count - 1) + { + OutLn(); + OutIndent(); + } + else + { + Out(';'); + } + } + + OutLn(); + + if (i != staticValidationAttributes.Length - 1) + { + OutLn(); + } + } + + OutCloseBrace(); + + OutCloseBrace(); + } + + private void GenModelSelfValidationIfNecessary(ValidatedModel modelToValidate) + { + if (modelToValidate.SelfValidates) + { + OutLn($"builder.AddResults(((global::System.ComponentModel.DataAnnotations.IValidatableObject)options).Validate(context));"); + OutLn(); + } + } + + private void GenModelValidationMethod( + ValidatedModel modelToValidate, + bool makeStatic, + ref Dictionary staticValidationAttributesDict, + ref Dictionary staticValidatorsDict) + { + OutLn($"/// "); + OutLn($"/// Validates a specific named options instance (or all when is )."); + OutLn($"/// "); + OutLn($"/// The name of the options instance being validated."); + OutLn($"/// The options instance."); + OutLn($"/// Validation result."); + OutGeneratedCodeAttribute(); + + OutLn($"public {(makeStatic ? "static " : string.Empty)}global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, {modelToValidate.Name} options)"); + OutOpenBrace(); + OutLn($"var baseName = (string.IsNullOrEmpty(name) ? \"{modelToValidate.SimpleName}\" : name) + \".\";"); + OutLn($"var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder();"); + OutLn($"var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options);"); + + int capacity = modelToValidate.MembersToValidate.Max(static vm => vm.ValidationAttributes.Count); + if (capacity > 0) + { + OutLn($"var validationResults = new {StaticListType}<{StaticValidationResultType}>();"); + OutLn($"var validationAttributes = new {StaticListType}<{StaticValidationAttributeType}>({capacity});"); + } + OutLn(); + + bool cleanListsBeforeUse = false; + foreach (var vm in modelToValidate.MembersToValidate) + { + if (vm.ValidationAttributes.Count > 0) + { + GenMemberValidation(vm, ref staticValidationAttributesDict, cleanListsBeforeUse); + cleanListsBeforeUse = true; + OutLn(); + } + + if (vm.TransValidatorType is not null) + { + GenTransitiveValidation(vm, ref staticValidatorsDict); + OutLn(); + } + + if (vm.EnumerationValidatorType is not null) + { + GenEnumerationValidation(vm, ref staticValidatorsDict); + OutLn(); + } + } + + GenModelSelfValidationIfNecessary(modelToValidate); + OutLn($"return builder.Build();"); + OutCloseBrace(); + } + + private void GenMemberValidation(ValidatedMember vm, ref Dictionary staticValidationAttributesDict, bool cleanListsBeforeUse) + { + OutLn($"context.MemberName = \"{vm.Name}\";"); + OutLn($"context.DisplayName = baseName + \"{vm.Name}\";"); + + if (cleanListsBeforeUse) + { + OutLn($"validationResults.Clear();"); + OutLn($"validationAttributes.Clear();"); + } + + foreach (var attr in vm.ValidationAttributes) + { + var staticValidationAttributeInstance = GetOrAddStaticValidationAttribute(ref staticValidationAttributesDict, attr); + OutLn($"validationAttributes.Add({StaticValidationAttributeHolderClassFQN}.{staticValidationAttributeInstance.FieldName});"); + } + + OutLn($"if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.{vm.Name}!, context, validationResults, validationAttributes))"); + OutOpenBrace(); + OutLn($"builder.AddResults(validationResults);"); + OutCloseBrace(); + } + + private StaticFieldInfo GetOrAddStaticValidationAttribute(ref Dictionary staticValidationAttributesDict, ValidationAttributeInfo attr) + { + var attrInstantiationStatementLines = new List(); + + if (attr.ConstructorArguments.Count > 0) + { + attrInstantiationStatementLines.Add($"new {attr.AttributeName}("); + + for (var i = 0; i < attr.ConstructorArguments.Count; i++) + { + if (i != attr.ConstructorArguments.Count - 1) + { + attrInstantiationStatementLines.Add($"{GetPaddingString(1)}{attr.ConstructorArguments[i]},"); + } + else + { + attrInstantiationStatementLines.Add($"{GetPaddingString(1)}{attr.ConstructorArguments[i]})"); + } + } + } + else + { + attrInstantiationStatementLines.Add($"new {attr.AttributeName}()"); + } + + if (attr.Properties.Count > 0) + { + attrInstantiationStatementLines.Add("{"); + + var propertiesOrderedByKey = attr.Properties + .OrderBy(p => p.Key) + .ToArray(); + + for (var i = 0; i < propertiesOrderedByKey.Length; i++) + { + var prop = propertiesOrderedByKey[i]; + var notLast = i != propertiesOrderedByKey.Length - 1; + attrInstantiationStatementLines.Add($"{GetPaddingString(1)}{prop.Key} = {prop.Value}{(notLast ? "," : string.Empty)}"); + } + + attrInstantiationStatementLines.Add("}"); + } + + var instantiationStatement = string.Join(Environment.NewLine, attrInstantiationStatementLines); + + if (!staticValidationAttributesDict.TryGetValue(instantiationStatement, out var staticValidationAttributeInstance)) + { + var fieldNumber = staticValidationAttributesDict.Count + 1; + staticValidationAttributeInstance = new StaticFieldInfo( + FieldTypeFQN: attr.AttributeName, + FieldOrder: fieldNumber, + FieldName: $"A{fieldNumber}", + InstantiationLines: attrInstantiationStatementLines); + + staticValidationAttributesDict.Add(instantiationStatement, staticValidationAttributeInstance); + } + + return staticValidationAttributeInstance; + } + + private void GenTransitiveValidation(ValidatedMember vm, ref Dictionary staticValidatorsDict) + { + string callSequence; + if (vm.TransValidateTypeIsSynthetic) + { + callSequence = vm.TransValidatorType!; + } + else + { + var staticValidatorInstance = GetOrAddStaticValidator(ref staticValidatorsDict, vm.TransValidatorType!); + + callSequence = $"{StaticValidatorHolderClassFQN}.{staticValidatorInstance.FieldName}"; + } + + var valueAccess = (vm.IsNullable && vm.IsValueType) ? ".Value" : string.Empty; + + if (vm.IsNullable) + { + OutLn($"if (options.{vm.Name} is not null)"); + OutOpenBrace(); + OutLn($"builder.AddResult({callSequence}.Validate(baseName + \"{vm.Name}\", options.{vm.Name}{valueAccess}));"); + OutCloseBrace(); + } + else + { + OutLn($"builder.AddResult({callSequence}.Validate(baseName + \"{vm.Name}\", options.{vm.Name}{valueAccess}));"); + } + } + + private void GenEnumerationValidation(ValidatedMember vm, ref Dictionary staticValidatorsDict) + { + var valueAccess = (vm.IsValueType && vm.IsNullable) ? ".Value" : string.Empty; + var enumeratedValueAccess = (vm.EnumeratedIsNullable && vm.EnumeratedIsValueType) ? ".Value" : string.Empty; + string callSequence; + if (vm.EnumerationValidatorTypeIsSynthetic) + { + callSequence = vm.EnumerationValidatorType!; + } + else + { + var staticValidatorInstance = GetOrAddStaticValidator(ref staticValidatorsDict, vm.EnumerationValidatorType!); + + callSequence = $"{StaticValidatorHolderClassFQN}.{staticValidatorInstance.FieldName}"; + } + + if (vm.IsNullable) + { + OutLn($"if (options.{vm.Name} is not null)"); + } + + OutOpenBrace(); + + OutLn($"var count = 0;"); + OutLn($"foreach (var o in options.{vm.Name}{valueAccess})"); + OutOpenBrace(); + + if (vm.EnumeratedIsNullable) + { + OutLn($"if (o is not null)"); + OutOpenBrace(); + OutLn($"builder.AddResult({callSequence}.Validate(baseName + $\"{vm.Name}[{{count}}]\", o{enumeratedValueAccess}));"); + OutCloseBrace(); + + if (!vm.EnumeratedMayBeNull) + { + OutLn($"else"); + OutOpenBrace(); + OutLn($"builder.AddError(baseName + $\"{vm.Name}[{{count}}] is null\");"); + OutCloseBrace(); + } + + OutLn($"count++;"); + } + else + { + OutLn($"builder.AddResult({callSequence}.Validate(baseName + $\"{vm.Name}[{{count++}}]\", o{enumeratedValueAccess}));"); + } + + OutCloseBrace(); + OutCloseBrace(); + } + + #pragma warning disable CA1822 // Mark members as static: static should come before non-static, but we want the method to be here + private StaticFieldInfo GetOrAddStaticValidator(ref Dictionary staticValidatorsDict, string validatorTypeFQN) + #pragma warning restore CA1822 + { + if (!staticValidatorsDict.TryGetValue(validatorTypeFQN, out var staticValidatorInstance)) + { + var fieldNumber = staticValidatorsDict.Count + 1; + staticValidatorInstance = new StaticFieldInfo( + FieldTypeFQN: validatorTypeFQN, + FieldOrder: fieldNumber, + FieldName: $"V{fieldNumber}", + InstantiationLines: new[] { $"new {validatorTypeFQN}()" }); + + staticValidatorsDict.Add(validatorTypeFQN, staticValidatorInstance); + } + + return staticValidatorInstance; + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/gen/EmitterBase.cs b/src/libraries/Microsoft.Extensions.Options/gen/EmitterBase.cs new file mode 100644 index 0000000000000..890c9bc598989 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/EmitterBase.cs @@ -0,0 +1,111 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.Extensions.Options.Generators +{ + internal class EmitterBase + { + public static string GeneratedCodeAttribute { get; } = $"global::System.CodeDom.Compiler.GeneratedCodeAttribute(" + + $"\"{typeof(EmitterBase).Assembly.GetName().Name}\", " + + $"\"{typeof(EmitterBase).Assembly.GetName().Version}\")"; + + public static string FilePreamble { get; } = @$" + // + #nullable enable + #pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103 + "; + + private const int DefaultStringBuilderCapacity = 1024; + private const int IndentChars = 4; + + private readonly StringBuilder _sb = new(DefaultStringBuilderCapacity); + private readonly string[] _padding = new string[16]; + private int _indent; + + public EmitterBase(bool emitPreamble = true) + { + var padding = _padding; + for (int i = 0; i < padding.Length; i++) + { + padding[i] = new string(' ', i * IndentChars); + } + + if (emitPreamble) + { + Out(FilePreamble); + } + } + + protected void OutOpenBrace() + { + OutLn("{"); + Indent(); + } + + protected void OutCloseBrace() + { + Unindent(); + OutLn("}"); + } + + protected void OutCloseBraceWithExtra(string extra) + { + Unindent(); + OutIndent(); + Out("}"); + Out(extra); + OutLn(); + } + + protected void OutIndent() + { + _ = _sb.Append(_padding[_indent]); + } + + protected string GetPaddingString(byte indent) + { + return _padding[indent]; + } + + protected void OutLn() + { + _ = _sb.AppendLine(); + } + + protected void OutLn(string line) + { + OutIndent(); + _ = _sb.AppendLine(line); + } + + protected void OutPP(string line) + { + _ = _sb.AppendLine(line); + } + + protected void OutEnumeration(IEnumerable e) + { + bool first = true; + foreach (var item in e) + { + if (!first) + { + Out(", "); + } + + Out(item); + first = false; + } + } + + protected void Out(string text) => _ = _sb.Append(text); + protected void Out(char ch) => _ = _sb.Append(ch); + protected void Indent() => _indent++; + protected void Unindent() => _indent--; + protected void OutGeneratedCodeAttribute() => OutLn($"[{GeneratedCodeAttribute}]"); + protected string Capture() => _sb.ToString(); + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Generator.cs b/src/libraries/Microsoft.Extensions.Options/gen/Generator.cs new file mode 100644 index 0000000000000..b24f43b5170d3 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/Generator.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.Extensions.Options.Generators +{ + [Generator] + public class Generator : IIncrementalGenerator + { + public void Initialize(IncrementalGeneratorInitializationContext context) + { + IncrementalValuesProvider<(TypeDeclarationSyntax TypeSyntax, SemanticModel SemanticModel)> typeDeclarations = context.SyntaxProvider + .ForAttributeWithMetadataName( + SymbolLoader.OptionsValidatorAttribute, + (node, _) => node is TypeDeclarationSyntax, + (context, _) => (TypeSyntax:context.TargetNode as TypeDeclarationSyntax, SemanticModel: context.SemanticModel)) + .Where(static m => m.TypeSyntax is not null); + + IncrementalValueProvider<(Compilation, ImmutableArray<(TypeDeclarationSyntax TypeSyntax, SemanticModel SemanticModel)>)> compilationAndTypes = + context.CompilationProvider.Combine(typeDeclarations.Collect()); + + context.RegisterSourceOutput(compilationAndTypes, static (spc, source) => HandleAnnotatedTypes(source.Item1, source.Item2, spc)); + } + + private static void HandleAnnotatedTypes(Compilation compilation, ImmutableArray<(TypeDeclarationSyntax TypeSyntax, SemanticModel SemanticModel)> types, SourceProductionContext context) + { + if (!SymbolLoader.TryLoad(compilation, out var symbolHolder)) + { + // Not eligible compilation + return; + } + + var parser = new Parser(compilation, context.ReportDiagnostic, symbolHolder!, context.CancellationToken); + + var validatorTypes = parser.GetValidatorTypes(types); + if (validatorTypes.Count > 0) + { + var emitter = new Emitter(); + var result = emitter.Emit(validatorTypes, context.CancellationToken); + + context.AddSource("Validators.g.cs", SourceText.From(result, Encoding.UTF8)); + } + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Microsoft.Extensions.Options.SourceGeneration.csproj b/src/libraries/Microsoft.Extensions.Options/gen/Microsoft.Extensions.Options.SourceGeneration.csproj new file mode 100644 index 0000000000000..12bc4a7c4d1e4 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/Microsoft.Extensions.Options.SourceGeneration.csproj @@ -0,0 +1,35 @@ + + + netstandard2.0 + false + false + true + cs + true + 4.4 + $(MicrosoftCodeAnalysisVersion_4_4) + $(DefineConstants);ROSLYN4_4_OR_GREATER + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Model/ValidatedMember.cs b/src/libraries/Microsoft.Extensions.Options/gen/Model/ValidatedMember.cs new file mode 100644 index 0000000000000..cfed013591674 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/Model/ValidatedMember.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace Microsoft.Extensions.Options.Generators +{ + internal sealed record class ValidatedMember( + string Name, + List ValidationAttributes, + string? TransValidatorType, + bool TransValidateTypeIsSynthetic, + string? EnumerationValidatorType, + bool EnumerationValidatorTypeIsSynthetic, + bool IsNullable, + bool IsValueType, + bool EnumeratedIsNullable, + bool EnumeratedIsValueType, + bool EnumeratedMayBeNull); +} diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Model/ValidatedModel.cs b/src/libraries/Microsoft.Extensions.Options/gen/Model/ValidatedModel.cs new file mode 100644 index 0000000000000..0d40d930051b1 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/Model/ValidatedModel.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace Microsoft.Extensions.Options.Generators +{ + internal sealed record class ValidatedModel( + string Name, + string SimpleName, + bool SelfValidates, + List MembersToValidate); +} diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Model/ValidationAttributeInfo.cs b/src/libraries/Microsoft.Extensions.Options/gen/Model/ValidationAttributeInfo.cs new file mode 100644 index 0000000000000..419ceca3e1d29 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/Model/ValidationAttributeInfo.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace Microsoft.Extensions.Options.Generators +{ + internal sealed record class ValidationAttributeInfo(string AttributeName) + { + public List ConstructorArguments { get; } = new(); + public Dictionary Properties { get; } = new(); + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Model/ValidatorType.cs b/src/libraries/Microsoft.Extensions.Options/gen/Model/ValidatorType.cs new file mode 100644 index 0000000000000..e104322c072ea --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/Model/ValidatorType.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace Microsoft.Extensions.Options.Generators +{ + internal sealed record class ValidatorType( + string Namespace, + string Name, + string NameWithoutGenerics, + string DeclarationKeyword, + List ParentTypes, + bool IsSynthetic, + IList ModelsToValidate); +} diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Parser.cs b/src/libraries/Microsoft.Extensions.Options/gen/Parser.cs new file mode 100644 index 0000000000000..2a9215c676071 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/Parser.cs @@ -0,0 +1,664 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.Extensions.Options.Generators +{ + /// + /// Holds an internal parser class that extracts necessary information for generating IValidateOptions. + /// + internal sealed class Parser + { + private const int NumValidationMethodArgs = 2; + + private readonly CancellationToken _cancellationToken; + private readonly Compilation _compilation; + private readonly Action _reportDiagnostic; + private readonly SymbolHolder _symbolHolder; + private readonly Dictionary _synthesizedValidators = new(SymbolEqualityComparer.Default); + private readonly HashSet _visitedModelTypes = new(SymbolEqualityComparer.Default); + + public Parser( + Compilation compilation, + Action reportDiagnostic, + SymbolHolder symbolHolder, + CancellationToken cancellationToken) + { + _compilation = compilation; + _cancellationToken = cancellationToken; + _reportDiagnostic = reportDiagnostic; + _symbolHolder = symbolHolder; + } + + public IReadOnlyList GetValidatorTypes(IEnumerable<(TypeDeclarationSyntax TypeSyntax, SemanticModel SemanticModel)> classes) + { + var results = new List(); + + foreach (var group in classes.GroupBy(x => x.TypeSyntax.SyntaxTree)) + { + SemanticModel? sm = null; + foreach (var typeDec in group) + { + TypeDeclarationSyntax syntax = typeDec.TypeSyntax; + _cancellationToken.ThrowIfCancellationRequested(); + sm ??= typeDec.SemanticModel; + + var validatorType = sm.GetDeclaredSymbol(syntax) as ITypeSymbol; + if (validatorType is not null) + { + if (validatorType.IsStatic) + { + Diag(DiagDescriptors.CantBeStaticClass, syntax.GetLocation()); + continue; + } + + _visitedModelTypes.Clear(); + + var modelTypes = GetModelTypes(validatorType); + if (modelTypes.Count == 0) + { + // validator doesn't implement IValidateOptions + Diag(DiagDescriptors.DoesntImplementIValidateOptions, syntax.GetLocation(), validatorType.Name); + continue; + } + + var modelsValidatorTypeValidates = new List(modelTypes.Count); + + foreach (var modelType in modelTypes) + { + if (modelType.Kind == SymbolKind.ErrorType) + { + // the compiler will report this error for us + continue; + } + else + { + // keep track of the models we look at, to detect loops + _ = _visitedModelTypes.Add(modelType.WithNullableAnnotation(NullableAnnotation.None)); + } + + if (AlreadyImplementsValidateMethod(validatorType, modelType)) + { + // this type already implements a validation function, we can't auto-generate a new one + Diag(DiagDescriptors.AlreadyImplementsValidateMethod, syntax.GetLocation(), validatorType.Name); + continue; + } + + var membersToValidate = GetMembersToValidate(modelType, true); + if (membersToValidate.Count == 0) + { + // this type lacks any eligible members + Diag(DiagDescriptors.NoEligibleMembersFromValidator, syntax.GetLocation(), modelType.ToString(), validatorType.ToString()); + continue; + } + + modelsValidatorTypeValidates.Add(new ValidatedModel( + GetFQN(modelType), + modelType.Name, + ModelSelfValidates(modelType), + membersToValidate)); + } + + string keyword = GetTypeKeyword(validatorType); + + // following code establishes the containment hierarchy for the generated type in terms of nested types + + var parents = new List(); + var parent = syntax.Parent as TypeDeclarationSyntax; + + while (parent is not null && IsAllowedKind(parent.Kind())) + { + parents.Add($"partial {GetTypeKeyword(parent)} {parent.Identifier}{parent.TypeParameterList} {parent.ConstraintClauses}"); + parent = parent.Parent as TypeDeclarationSyntax; + } + + parents.Reverse(); + + results.Add(new ValidatorType( + validatorType.ContainingNamespace.IsGlobalNamespace ? string.Empty : validatorType.ContainingNamespace.ToString(), + GetMinimalFQN(validatorType), + GetMinimalFQNWithoutGenerics(validatorType), + keyword, + parents, + false, + modelsValidatorTypeValidates)); + } + } + } + + results.AddRange(_synthesizedValidators.Values); + _synthesizedValidators.Clear(); + + return results; + } + + private static bool IsAllowedKind(SyntaxKind kind) => + kind == SyntaxKind.ClassDeclaration || + kind == SyntaxKind.StructDeclaration || + kind == SyntaxKind.RecordStructDeclaration || + kind == SyntaxKind.RecordDeclaration; + + private static string GetTypeKeyword(ITypeSymbol type) + { + if (type.IsReferenceType) + { + return type.IsRecord ? "record class" : "class"; + } + + return type.IsRecord ? "record struct" : "struct"; + } + + private static string GetTypeKeyword(TypeDeclarationSyntax type) => + type.Kind() switch + { + SyntaxKind.ClassDeclaration => "class", + SyntaxKind.RecordDeclaration => "record class", + SyntaxKind.RecordStructDeclaration => "record struct", + _ => type.Keyword.ValueText, + }; + + private static string GetFQN(ISymbol type) + => type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat.WithMiscellaneousOptions(SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier)); + + private static string GetMinimalFQN(ISymbol type) + => type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat.AddGenericsOptions(SymbolDisplayGenericsOptions.IncludeTypeParameters)); + + private static string GetMinimalFQNWithoutGenerics(ISymbol type) + => type.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat.WithGenericsOptions(SymbolDisplayGenericsOptions.None)); + + /// + /// Checks whether the given validator already implement the IValidationOptions>T< interface. + /// + private static bool AlreadyImplementsValidateMethod(INamespaceOrTypeSymbol validatorType, ISymbol modelType) + => validatorType + .GetMembers("Validate") + .Where(m => m.Kind == SymbolKind.Method) + .Select(m => (IMethodSymbol)m) + .Any(m => m.Parameters.Length == NumValidationMethodArgs + && m.Parameters[0].Type.SpecialType == SpecialType.System_String + && SymbolEqualityComparer.Default.Equals(m.Parameters[1].Type, modelType)); + + /// + /// Checks whether the given type contain any unbound generic type arguments. + /// + private static bool HasOpenGenerics(ITypeSymbol type, out string genericType) + { + if (type is INamedTypeSymbol mt) + { + if (mt.IsGenericType) + { + foreach (var ta in mt.TypeArguments) + { + if (ta.TypeKind == TypeKind.TypeParameter) + { + genericType = ta.Name; + return true; + } + } + } + } + else if (type is ITypeParameterSymbol) + { + genericType = type.Name; + return true; + } + else if (type is IArrayTypeSymbol ats) + { + return HasOpenGenerics(ats.ElementType, out genericType); + } + + genericType = string.Empty; + return false; + } + + private ITypeSymbol? GetEnumeratedType(ITypeSymbol type) + { + if (type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) + { + // extract the T from a Nullable + type = ((INamedTypeSymbol)type).TypeArguments[0]; + } + + foreach (var implementingInterface in type.AllInterfaces) + { + if (SymbolEqualityComparer.Default.Equals(implementingInterface.OriginalDefinition, _compilation.GetSpecialType(SpecialType.System_Collections_Generic_IEnumerable_T))) + { + return implementingInterface.TypeArguments.First(); + } + } + + return null; + } + + private List GetMembersToValidate(ITypeSymbol modelType, bool speculate) + { + // make a list of the most derived members in the model type + + if (modelType.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) + { + // extract the T from a Nullable + modelType = ((INamedTypeSymbol)modelType).TypeArguments[0]; + } + + var members = modelType.GetMembers().ToList(); + var addedMembers = new HashSet(members.Select(m => m.Name)); + var baseType = modelType.BaseType; + while (baseType is not null && baseType.SpecialType != SpecialType.System_Object) + { + var baseMembers = baseType.GetMembers().Where(m => !addedMembers.Contains(m.Name)); + members.AddRange(baseMembers); + addedMembers.UnionWith(baseMembers.Select(m => m.Name)); + baseType = baseType.BaseType; + } + + var membersToValidate = new List(); + foreach (var member in members) + { + var memberInfo = GetMemberInfo(member, speculate); + if (memberInfo is not null) + { + if (member.DeclaredAccessibility != Accessibility.Public && member.DeclaredAccessibility != Accessibility.Internal) + { + Diag(DiagDescriptors.MemberIsInaccessible, member.Locations.First(), member.Name); + continue; + } + + membersToValidate.Add(memberInfo); + } + } + + return membersToValidate; + } + + private ValidatedMember? GetMemberInfo(ISymbol member, bool speculate) + { + ITypeSymbol memberType; + switch (member) + { + case IPropertySymbol prop: + memberType = prop.Type; + break; + case IFieldSymbol field: + if (field.AssociatedSymbol is not null) + { + // a backing field for a property, don't need those + return null; + } + + memberType = field.Type; + break; + default: + // we only care about properties and fields + return null; + } + + var validationAttrs = new List(); + string? transValidatorTypeName = null; + string? enumerationValidatorTypeName = null; + var enumeratedIsNullable = false; + var enumeratedIsValueType = false; + var enumeratedMayBeNull = false; + var transValidatorIsSynthetic = false; + var enumerationValidatorIsSynthetic = false; + + foreach (var attribute in member.GetAttributes().Where(a => a.AttributeClass is not null)) + { + var attributeType = attribute.AttributeClass!; + var attrLoc = attribute.ApplicationSyntaxReference?.GetSyntax().GetLocation(); + + if (SymbolEqualityComparer.Default.Equals(attributeType, _symbolHolder.ValidateObjectMembersAttributeSymbol)) + { + if (HasOpenGenerics(memberType, out var genericType)) + { + Diag(DiagDescriptors.CantUseWithGenericTypes, attrLoc, genericType); + #pragma warning disable S1226 // Method parameters, caught exceptions and foreach variables' initial values should not be ignored + speculate = false; + #pragma warning restore S1226 // Method parameters, caught exceptions and foreach variables' initial values should not be ignored + continue; + } + + if (attribute.ConstructorArguments.Length == 1) + { + var transValidatorType = attribute.ConstructorArguments[0].Value as INamedTypeSymbol; + if (transValidatorType is not null) + { + if (CanValidate(transValidatorType, memberType)) + { + if (transValidatorType.Constructors.Where(c => !c.Parameters.Any()).Any()) + { + transValidatorTypeName = transValidatorType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + } + else + { + Diag(DiagDescriptors.ValidatorsNeedSimpleConstructor, attrLoc, transValidatorType.Name); + } + } + else + { + Diag(DiagDescriptors.DoesntImplementIValidateOptions, attrLoc, transValidatorType.Name, memberType.Name); + } + } + else + { + Diag(DiagDescriptors.NullValidatorType, attrLoc); + } + } + else if (!_visitedModelTypes.Add(memberType.WithNullableAnnotation(NullableAnnotation.None))) + { + Diag(DiagDescriptors.CircularTypeReferences, attrLoc, memberType.ToString()); + speculate = false; + continue; + } + + if (transValidatorTypeName == null) + { + transValidatorIsSynthetic = true; + transValidatorTypeName = AddSynthesizedValidator(memberType, member); + } + + // pop the stack + _ = _visitedModelTypes.Remove(memberType.WithNullableAnnotation(NullableAnnotation.None)); + } + else if (SymbolEqualityComparer.Default.Equals(attributeType, _symbolHolder.ValidateEnumeratedItemsAttributeSymbol)) + { + var enumeratedType = GetEnumeratedType(memberType); + if (enumeratedType == null) + { + Diag(DiagDescriptors.NotEnumerableType, attrLoc, memberType); + speculate = false; + continue; + } + + enumeratedIsNullable = enumeratedType.IsReferenceType || enumeratedType.NullableAnnotation == NullableAnnotation.Annotated; + enumeratedIsValueType = enumeratedType.IsValueType; + enumeratedMayBeNull = enumeratedType.NullableAnnotation == NullableAnnotation.Annotated; + + if (HasOpenGenerics(enumeratedType, out var genericType)) + { + Diag(DiagDescriptors.CantUseWithGenericTypes, attrLoc, genericType); + speculate = false; + continue; + } + + if (attribute.ConstructorArguments.Length == 1) + { + var enumerationValidatorType = attribute.ConstructorArguments[0].Value as INamedTypeSymbol; + if (enumerationValidatorType is not null) + { + if (CanValidate(enumerationValidatorType, enumeratedType)) + { + if (enumerationValidatorType.Constructors.Where(c => c.Parameters.Length == 0).Any()) + { + enumerationValidatorTypeName = enumerationValidatorType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + } + else + { + Diag(DiagDescriptors.ValidatorsNeedSimpleConstructor, attrLoc, enumerationValidatorType.Name); + } + } + else + { + Diag(DiagDescriptors.DoesntImplementIValidateOptions, attrLoc, enumerationValidatorType.Name, enumeratedType.Name); + } + } + else + { + Diag(DiagDescriptors.NullValidatorType, attrLoc); + } + } + else if (!_visitedModelTypes.Add(enumeratedType.WithNullableAnnotation(NullableAnnotation.None))) + { + Diag(DiagDescriptors.CircularTypeReferences, attrLoc, enumeratedType.ToString()); + speculate = false; + continue; + } + + if (enumerationValidatorTypeName == null) + { + enumerationValidatorIsSynthetic = true; + enumerationValidatorTypeName = AddSynthesizedValidator(enumeratedType, member); + } + + // pop the stack + _ = _visitedModelTypes.Remove(enumeratedType.WithNullableAnnotation(NullableAnnotation.None)); + } + else if (ConvertTo(attributeType, _symbolHolder.ValidationAttributeSymbol)) + { + var validationAttr = new ValidationAttributeInfo(attributeType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)); + validationAttrs.Add(validationAttr); + + foreach (var constructorArgument in attribute.ConstructorArguments) + { + validationAttr.ConstructorArguments.Add(GetArgumentExpression(constructorArgument.Type!, constructorArgument.Value)); + } + + foreach (var namedArgument in attribute.NamedArguments) + { + validationAttr.Properties.Add(namedArgument.Key, GetArgumentExpression(namedArgument.Value.Type!, namedArgument.Value.Value)); + } + } + } + + // generate a warning if the field/property seems like it should be transitively validated + if (transValidatorTypeName == null && speculate && memberType.SpecialType == SpecialType.None) + { + if (!HasOpenGenerics(memberType, out var genericType)) + { + var membersToValidate = GetMembersToValidate(memberType, false); + if (membersToValidate.Count > 0) + { + Diag(DiagDescriptors.PotentiallyMissingTransitiveValidation, member.GetLocation(), memberType.Name, member.Name); + } + } + } + + // generate a warning if the field/property seems like it should be enumerated + if (enumerationValidatorTypeName == null && speculate) + { + var enumeratedType = GetEnumeratedType(memberType); + if (enumeratedType is not null) + { + if (!HasOpenGenerics(enumeratedType, out var genericType)) + { + var membersToValidate = GetMembersToValidate(enumeratedType, false); + if (membersToValidate.Count > 0) + { + Diag(DiagDescriptors.PotentiallyMissingEnumerableValidation, member.GetLocation(), enumeratedType.Name, member.Name); + } + } + } + } + + if (validationAttrs.Count > 0 || transValidatorTypeName is not null || enumerationValidatorTypeName is not null) + { + return new( + member.Name, + validationAttrs, + transValidatorTypeName, + transValidatorIsSynthetic, + enumerationValidatorTypeName, + enumerationValidatorIsSynthetic, + memberType.IsReferenceType || memberType.NullableAnnotation == NullableAnnotation.Annotated, + memberType.IsValueType, + enumeratedIsNullable, + enumeratedIsValueType, + enumeratedMayBeNull); + } + + return null; + } + + private string? AddSynthesizedValidator(ITypeSymbol modelType, ISymbol member) + { + var mt = modelType.WithNullableAnnotation(NullableAnnotation.None); + if (mt.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T) + { + // extract the T from a Nullable + mt = ((INamedTypeSymbol)mt).TypeArguments[0]; + } + + if (_synthesizedValidators.TryGetValue(mt, out var validator)) + { + return "global::" + validator.Namespace + "." + validator.Name; + } + + var membersToValidate = GetMembersToValidate(mt, true); + if (membersToValidate.Count == 0) + { + // this type lacks any eligible members + Diag(DiagDescriptors.NoEligibleMember, member.GetLocation(), mt.ToString(), member.ToString()); + return null; + } + + var model = new ValidatedModel( + GetFQN(mt), + mt.Name, + false, + membersToValidate); + + var validatorTypeName = "__" + mt.Name + "Validator__"; + + var result = new ValidatorType( + mt.ContainingNamespace.IsGlobalNamespace ? string.Empty : mt.ContainingNamespace.ToString(), + validatorTypeName, + validatorTypeName, + "class", + new List(), + true, + new[] { model }); + + _synthesizedValidators[mt] = result; + return "global::" + (result.Namespace.Length > 0 ? result.Namespace + "." + result.Name : result.Name); + } + + private bool ConvertTo(ITypeSymbol source, ITypeSymbol dest) + { + var conversion = _compilation.ClassifyConversion(source, dest); + return conversion.IsReference && conversion.IsImplicit; + } + + private bool ModelSelfValidates(ITypeSymbol modelType) + { + foreach (var implementingInterface in modelType.AllInterfaces) + { + if (SymbolEqualityComparer.Default.Equals(implementingInterface.OriginalDefinition, _symbolHolder.IValidatableObjectSymbol)) + { + return true; + } + } + + return false; + } + + private List GetModelTypes(ITypeSymbol validatorType) + { + var result = new List(); + foreach (var implementingInterface in validatorType.AllInterfaces) + { + if (SymbolEqualityComparer.Default.Equals(implementingInterface.OriginalDefinition, _symbolHolder.ValidateOptionsSymbol)) + { + result.Add(implementingInterface.TypeArguments.First()); + } + } + + return result; + } + + private bool CanValidate(ITypeSymbol validatorType, ISymbol modelType) + { + foreach (var implementingInterface in validatorType.AllInterfaces) + { + if (SymbolEqualityComparer.Default.Equals(implementingInterface.OriginalDefinition, _symbolHolder.ValidateOptionsSymbol)) + { + var t = implementingInterface.TypeArguments.First(); + if (SymbolEqualityComparer.Default.Equals(modelType, t)) + { + return true; + } + } + } + + return false; + } + + private string GetArgumentExpression(ITypeSymbol type, object? value) + { + if (value == null) + { + return "null"; + } + + if (type.SpecialType == SpecialType.System_Boolean) + { + return (bool)value ? "true" : "false"; + } + + if (SymbolEqualityComparer.Default.Equals(type, _symbolHolder.TypeSymbol) && + value is INamedTypeSymbol sym) + { + return $"typeof({sym.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)})"; + } + + if (type.SpecialType == SpecialType.System_String) + { + return $@"""{EscapeString(value.ToString())}"""; + } + + if (type.SpecialType == SpecialType.System_Char) + { + return $@"'{EscapeString(value.ToString())}'"; + } + + return $"({type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}){Convert.ToString(value, CultureInfo.InvariantCulture)}"; + } + + private static readonly char[] _specialChars = { '\n', '\r', '"', '\\' }; + + private static string EscapeString(string s) + { + int index = s.IndexOfAny(_specialChars); + if (index < 0) + { + return s; + } + + var sb = new StringBuilder(s.Length); + _ = sb.Append(s, 0, index); + + while (index < s.Length) + { + _ = s[index] switch + { + '\n' => sb.Append("\\n"), + '\r' => sb.Append("\\r"), + '"' => sb.Append("\\\""), + '\\' => sb.Append("\\\\"), + var other => sb.Append(other), + }; + + index++; + } + + return sb.ToString(); + } + + private void Diag(DiagnosticDescriptor desc, Location? location) + { + _reportDiagnostic(Diagnostic.Create(desc, location, Array.Empty())); + } + + private void Diag(DiagnosticDescriptor desc, Location? location, params object?[]? messageArgs) + { + _reportDiagnostic(Diagnostic.Create(desc, location, messageArgs)); + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/gen/ParserUtilities.cs b/src/libraries/Microsoft.Extensions.Options/gen/ParserUtilities.cs new file mode 100644 index 0000000000000..d79ad4cccb653 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/ParserUtilities.cs @@ -0,0 +1,75 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.Extensions.Options.Generators +{ + internal static class ParserUtilities + { + internal static AttributeData? GetSymbolAttributeAnnotationOrDefault(ISymbol? attribute, ISymbol symbol) + { + if (attribute is null) + { + return null; + } + + var attrs = symbol.GetAttributes(); + foreach (var item in attrs) + { + if (SymbolEqualityComparer.Default.Equals(attribute, item.AttributeClass) && item.AttributeConstructor != null) + { + return item; + } + } + + return null; + } + + internal static bool PropertyHasModifier(ISymbol property, SyntaxKind modifierToSearch, CancellationToken token) + => property + .DeclaringSyntaxReferences + .Any(x => + x.GetSyntax(token) is PropertyDeclarationSyntax syntax && + syntax.Modifiers.Any(m => m.IsKind(modifierToSearch))); + + internal static Location? GetLocation(this ISymbol symbol) + { + if (symbol is null) + { + return null; + } + + return symbol.Locations.IsDefaultOrEmpty + ? null + : symbol.Locations[0]; + } + + internal static bool IsBaseOrIdentity(ITypeSymbol source, ITypeSymbol dest, Compilation comp) + { + var conversion = comp.ClassifyConversion(source, dest); + return conversion.IsIdentity || (conversion.IsReference && conversion.IsImplicit); + } + + internal static bool ImplementsInterface(this ITypeSymbol type, ITypeSymbol interfaceType) + { + foreach (var iface in type.AllInterfaces) + { + if (SymbolEqualityComparer.Default.Equals(interfaceType, iface)) + { + return true; + } + } + + return false; + } + + // Check if parameter has either simplified (i.e. "int?") or explicit (Nullable) nullable type declaration: + internal static bool IsNullableOfT(this ITypeSymbol type) + => type.SpecialType == SpecialType.System_Nullable_T || type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T; + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/Strings.resx b/src/libraries/Microsoft.Extensions.Options/gen/Resources/Strings.resx new file mode 100644 index 0000000000000..021f345f58cae --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/Resources/Strings.resx @@ -0,0 +1,198 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Type {0} already implements the Validate method. + + + A type already includes an implementation of the 'Validate' method. + + + [OptionsValidator] cannot be applied to static class {0}. + + + 'OptionsValidatorAttribute' can't be applied to a static class. + + + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + + + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + + + There is a circular type reference involving type {0} preventing it from being used for static validation. + + + Unsupported circular references in model types. + + + Type {0} does not implement the required IValidateOptions<{1}> interface. + + + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + + + Can't apply validation attributes to private field or property {0}. + + + Can't validate private fields or properties. + + + Type {0} has no fields or properties to validate, referenced from member {1}. + + + Type {0} has no fields or properties to validate, referenced by type {1}. + + + A type has no fields or properties to validate. + + + A member type has no fields or properties to validate. + + + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + + + Member type is not enumerable. + + + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + + + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + + + Member potentially missing enumerable validation. + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + + + Member potentially missing transitive validation. + + + Validator type {0} doesn't have a parameterless constructor. + + + Validators used for transitive or enumerable validation must have a constructor with no parameters. + + diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.cs.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.cs.xlf new file mode 100644 index 0000000000000..a27619874a770 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.cs.xlf @@ -0,0 +1,137 @@ + + + + + + Type {0} already implements the Validate method. + Type {0} already implements the Validate method. + + + + A type already includes an implementation of the 'Validate' method. + A type already includes an implementation of the 'Validate' method. + + + + [OptionsValidator] cannot be applied to static class {0}. + [OptionsValidator] cannot be applied to static class {0}. + + + + 'OptionsValidatorAttribute' can't be applied to a static class. + 'OptionsValidatorAttribute' can't be applied to a static class. + + + + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + + + + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + + + + There is a circular type reference involving type {0} preventing it from being used for static validation. + There is a circular type reference involving type {0} preventing it from being used for static validation. + + + + Unsupported circular references in model types. + Unsupported circular references in model types. + + + + Type {0} does not implement the required IValidateOptions<{1}> interface. + Type {0} does not implement the required IValidateOptions<{1}> interface. + + + + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + + + + Can't apply validation attributes to private field or property {0}. + Can't apply validation attributes to private field or property {0}. + + + + Can't validate private fields or properties. + Can't validate private fields or properties. + + + + Type {0} has no fields or properties to validate, referenced from member {1}. + Type {0} has no fields or properties to validate, referenced from member {1}. + + + + A member type has no fields or properties to validate. + A member type has no fields or properties to validate. + + + + Type {0} has no fields or properties to validate, referenced by type {1}. + Type {0} has no fields or properties to validate, referenced by type {1}. + + + + A type has no fields or properties to validate. + A type has no fields or properties to validate. + + + + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + + + + Member type is not enumerable. + Member type is not enumerable. + + + + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + + + + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + + + + Member potentially missing enumerable validation. + Member potentially missing enumerable validation. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + + + + Member potentially missing transitive validation. + Member potentially missing transitive validation. + + + + Validator type {0} doesn't have a parameterless constructor. + Validator type {0} doesn't have a parameterless constructor. + + + + Validators used for transitive or enumerable validation must have a constructor with no parameters. + Validators used for transitive or enumerable validation must have a constructor with no parameters. + + + + + \ No newline at end of file diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.de.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.de.xlf new file mode 100644 index 0000000000000..1a2543583fd4f --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.de.xlf @@ -0,0 +1,137 @@ + + + + + + Type {0} already implements the Validate method. + Type {0} already implements the Validate method. + + + + A type already includes an implementation of the 'Validate' method. + A type already includes an implementation of the 'Validate' method. + + + + [OptionsValidator] cannot be applied to static class {0}. + [OptionsValidator] cannot be applied to static class {0}. + + + + 'OptionsValidatorAttribute' can't be applied to a static class. + 'OptionsValidatorAttribute' can't be applied to a static class. + + + + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + + + + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + + + + There is a circular type reference involving type {0} preventing it from being used for static validation. + There is a circular type reference involving type {0} preventing it from being used for static validation. + + + + Unsupported circular references in model types. + Unsupported circular references in model types. + + + + Type {0} does not implement the required IValidateOptions<{1}> interface. + Type {0} does not implement the required IValidateOptions<{1}> interface. + + + + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + + + + Can't apply validation attributes to private field or property {0}. + Can't apply validation attributes to private field or property {0}. + + + + Can't validate private fields or properties. + Can't validate private fields or properties. + + + + Type {0} has no fields or properties to validate, referenced from member {1}. + Type {0} has no fields or properties to validate, referenced from member {1}. + + + + A member type has no fields or properties to validate. + A member type has no fields or properties to validate. + + + + Type {0} has no fields or properties to validate, referenced by type {1}. + Type {0} has no fields or properties to validate, referenced by type {1}. + + + + A type has no fields or properties to validate. + A type has no fields or properties to validate. + + + + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + + + + Member type is not enumerable. + Member type is not enumerable. + + + + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + + + + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + + + + Member potentially missing enumerable validation. + Member potentially missing enumerable validation. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + + + + Member potentially missing transitive validation. + Member potentially missing transitive validation. + + + + Validator type {0} doesn't have a parameterless constructor. + Validator type {0} doesn't have a parameterless constructor. + + + + Validators used for transitive or enumerable validation must have a constructor with no parameters. + Validators used for transitive or enumerable validation must have a constructor with no parameters. + + + + + \ No newline at end of file diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.es.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.es.xlf new file mode 100644 index 0000000000000..ba26cf8cb3e90 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.es.xlf @@ -0,0 +1,137 @@ + + + + + + Type {0} already implements the Validate method. + Type {0} already implements the Validate method. + + + + A type already includes an implementation of the 'Validate' method. + A type already includes an implementation of the 'Validate' method. + + + + [OptionsValidator] cannot be applied to static class {0}. + [OptionsValidator] cannot be applied to static class {0}. + + + + 'OptionsValidatorAttribute' can't be applied to a static class. + 'OptionsValidatorAttribute' can't be applied to a static class. + + + + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + + + + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + + + + There is a circular type reference involving type {0} preventing it from being used for static validation. + There is a circular type reference involving type {0} preventing it from being used for static validation. + + + + Unsupported circular references in model types. + Unsupported circular references in model types. + + + + Type {0} does not implement the required IValidateOptions<{1}> interface. + Type {0} does not implement the required IValidateOptions<{1}> interface. + + + + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + + + + Can't apply validation attributes to private field or property {0}. + Can't apply validation attributes to private field or property {0}. + + + + Can't validate private fields or properties. + Can't validate private fields or properties. + + + + Type {0} has no fields or properties to validate, referenced from member {1}. + Type {0} has no fields or properties to validate, referenced from member {1}. + + + + A member type has no fields or properties to validate. + A member type has no fields or properties to validate. + + + + Type {0} has no fields or properties to validate, referenced by type {1}. + Type {0} has no fields or properties to validate, referenced by type {1}. + + + + A type has no fields or properties to validate. + A type has no fields or properties to validate. + + + + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + + + + Member type is not enumerable. + Member type is not enumerable. + + + + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + + + + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + + + + Member potentially missing enumerable validation. + Member potentially missing enumerable validation. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + + + + Member potentially missing transitive validation. + Member potentially missing transitive validation. + + + + Validator type {0} doesn't have a parameterless constructor. + Validator type {0} doesn't have a parameterless constructor. + + + + Validators used for transitive or enumerable validation must have a constructor with no parameters. + Validators used for transitive or enumerable validation must have a constructor with no parameters. + + + + + \ No newline at end of file diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.fr.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.fr.xlf new file mode 100644 index 0000000000000..748bf1cbda2b7 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.fr.xlf @@ -0,0 +1,137 @@ + + + + + + Type {0} already implements the Validate method. + Type {0} already implements the Validate method. + + + + A type already includes an implementation of the 'Validate' method. + A type already includes an implementation of the 'Validate' method. + + + + [OptionsValidator] cannot be applied to static class {0}. + [OptionsValidator] cannot be applied to static class {0}. + + + + 'OptionsValidatorAttribute' can't be applied to a static class. + 'OptionsValidatorAttribute' can't be applied to a static class. + + + + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + + + + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + + + + There is a circular type reference involving type {0} preventing it from being used for static validation. + There is a circular type reference involving type {0} preventing it from being used for static validation. + + + + Unsupported circular references in model types. + Unsupported circular references in model types. + + + + Type {0} does not implement the required IValidateOptions<{1}> interface. + Type {0} does not implement the required IValidateOptions<{1}> interface. + + + + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + + + + Can't apply validation attributes to private field or property {0}. + Can't apply validation attributes to private field or property {0}. + + + + Can't validate private fields or properties. + Can't validate private fields or properties. + + + + Type {0} has no fields or properties to validate, referenced from member {1}. + Type {0} has no fields or properties to validate, referenced from member {1}. + + + + A member type has no fields or properties to validate. + A member type has no fields or properties to validate. + + + + Type {0} has no fields or properties to validate, referenced by type {1}. + Type {0} has no fields or properties to validate, referenced by type {1}. + + + + A type has no fields or properties to validate. + A type has no fields or properties to validate. + + + + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + + + + Member type is not enumerable. + Member type is not enumerable. + + + + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + + + + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + + + + Member potentially missing enumerable validation. + Member potentially missing enumerable validation. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + + + + Member potentially missing transitive validation. + Member potentially missing transitive validation. + + + + Validator type {0} doesn't have a parameterless constructor. + Validator type {0} doesn't have a parameterless constructor. + + + + Validators used for transitive or enumerable validation must have a constructor with no parameters. + Validators used for transitive or enumerable validation must have a constructor with no parameters. + + + + + \ No newline at end of file diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.it.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.it.xlf new file mode 100644 index 0000000000000..386b07ab9ff25 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.it.xlf @@ -0,0 +1,137 @@ + + + + + + Type {0} already implements the Validate method. + Type {0} already implements the Validate method. + + + + A type already includes an implementation of the 'Validate' method. + A type already includes an implementation of the 'Validate' method. + + + + [OptionsValidator] cannot be applied to static class {0}. + [OptionsValidator] cannot be applied to static class {0}. + + + + 'OptionsValidatorAttribute' can't be applied to a static class. + 'OptionsValidatorAttribute' can't be applied to a static class. + + + + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + + + + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + + + + There is a circular type reference involving type {0} preventing it from being used for static validation. + There is a circular type reference involving type {0} preventing it from being used for static validation. + + + + Unsupported circular references in model types. + Unsupported circular references in model types. + + + + Type {0} does not implement the required IValidateOptions<{1}> interface. + Type {0} does not implement the required IValidateOptions<{1}> interface. + + + + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + + + + Can't apply validation attributes to private field or property {0}. + Can't apply validation attributes to private field or property {0}. + + + + Can't validate private fields or properties. + Can't validate private fields or properties. + + + + Type {0} has no fields or properties to validate, referenced from member {1}. + Type {0} has no fields or properties to validate, referenced from member {1}. + + + + A member type has no fields or properties to validate. + A member type has no fields or properties to validate. + + + + Type {0} has no fields or properties to validate, referenced by type {1}. + Type {0} has no fields or properties to validate, referenced by type {1}. + + + + A type has no fields or properties to validate. + A type has no fields or properties to validate. + + + + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + + + + Member type is not enumerable. + Member type is not enumerable. + + + + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + + + + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + + + + Member potentially missing enumerable validation. + Member potentially missing enumerable validation. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + + + + Member potentially missing transitive validation. + Member potentially missing transitive validation. + + + + Validator type {0} doesn't have a parameterless constructor. + Validator type {0} doesn't have a parameterless constructor. + + + + Validators used for transitive or enumerable validation must have a constructor with no parameters. + Validators used for transitive or enumerable validation must have a constructor with no parameters. + + + + + \ No newline at end of file diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.ja.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.ja.xlf new file mode 100644 index 0000000000000..1f4ccf0f7c868 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.ja.xlf @@ -0,0 +1,137 @@ + + + + + + Type {0} already implements the Validate method. + Type {0} already implements the Validate method. + + + + A type already includes an implementation of the 'Validate' method. + A type already includes an implementation of the 'Validate' method. + + + + [OptionsValidator] cannot be applied to static class {0}. + [OptionsValidator] cannot be applied to static class {0}. + + + + 'OptionsValidatorAttribute' can't be applied to a static class. + 'OptionsValidatorAttribute' can't be applied to a static class. + + + + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + + + + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + + + + There is a circular type reference involving type {0} preventing it from being used for static validation. + There is a circular type reference involving type {0} preventing it from being used for static validation. + + + + Unsupported circular references in model types. + Unsupported circular references in model types. + + + + Type {0} does not implement the required IValidateOptions<{1}> interface. + Type {0} does not implement the required IValidateOptions<{1}> interface. + + + + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + + + + Can't apply validation attributes to private field or property {0}. + Can't apply validation attributes to private field or property {0}. + + + + Can't validate private fields or properties. + Can't validate private fields or properties. + + + + Type {0} has no fields or properties to validate, referenced from member {1}. + Type {0} has no fields or properties to validate, referenced from member {1}. + + + + A member type has no fields or properties to validate. + A member type has no fields or properties to validate. + + + + Type {0} has no fields or properties to validate, referenced by type {1}. + Type {0} has no fields or properties to validate, referenced by type {1}. + + + + A type has no fields or properties to validate. + A type has no fields or properties to validate. + + + + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + + + + Member type is not enumerable. + Member type is not enumerable. + + + + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + + + + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + + + + Member potentially missing enumerable validation. + Member potentially missing enumerable validation. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + + + + Member potentially missing transitive validation. + Member potentially missing transitive validation. + + + + Validator type {0} doesn't have a parameterless constructor. + Validator type {0} doesn't have a parameterless constructor. + + + + Validators used for transitive or enumerable validation must have a constructor with no parameters. + Validators used for transitive or enumerable validation must have a constructor with no parameters. + + + + + \ No newline at end of file diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.ko.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.ko.xlf new file mode 100644 index 0000000000000..283d22eedebe7 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.ko.xlf @@ -0,0 +1,137 @@ + + + + + + Type {0} already implements the Validate method. + Type {0} already implements the Validate method. + + + + A type already includes an implementation of the 'Validate' method. + A type already includes an implementation of the 'Validate' method. + + + + [OptionsValidator] cannot be applied to static class {0}. + [OptionsValidator] cannot be applied to static class {0}. + + + + 'OptionsValidatorAttribute' can't be applied to a static class. + 'OptionsValidatorAttribute' can't be applied to a static class. + + + + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + + + + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + + + + There is a circular type reference involving type {0} preventing it from being used for static validation. + There is a circular type reference involving type {0} preventing it from being used for static validation. + + + + Unsupported circular references in model types. + Unsupported circular references in model types. + + + + Type {0} does not implement the required IValidateOptions<{1}> interface. + Type {0} does not implement the required IValidateOptions<{1}> interface. + + + + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + + + + Can't apply validation attributes to private field or property {0}. + Can't apply validation attributes to private field or property {0}. + + + + Can't validate private fields or properties. + Can't validate private fields or properties. + + + + Type {0} has no fields or properties to validate, referenced from member {1}. + Type {0} has no fields or properties to validate, referenced from member {1}. + + + + A member type has no fields or properties to validate. + A member type has no fields or properties to validate. + + + + Type {0} has no fields or properties to validate, referenced by type {1}. + Type {0} has no fields or properties to validate, referenced by type {1}. + + + + A type has no fields or properties to validate. + A type has no fields or properties to validate. + + + + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + + + + Member type is not enumerable. + Member type is not enumerable. + + + + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + + + + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + + + + Member potentially missing enumerable validation. + Member potentially missing enumerable validation. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + + + + Member potentially missing transitive validation. + Member potentially missing transitive validation. + + + + Validator type {0} doesn't have a parameterless constructor. + Validator type {0} doesn't have a parameterless constructor. + + + + Validators used for transitive or enumerable validation must have a constructor with no parameters. + Validators used for transitive or enumerable validation must have a constructor with no parameters. + + + + + \ No newline at end of file diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.pl.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.pl.xlf new file mode 100644 index 0000000000000..cfa99a0d18363 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.pl.xlf @@ -0,0 +1,137 @@ + + + + + + Type {0} already implements the Validate method. + Type {0} already implements the Validate method. + + + + A type already includes an implementation of the 'Validate' method. + A type already includes an implementation of the 'Validate' method. + + + + [OptionsValidator] cannot be applied to static class {0}. + [OptionsValidator] cannot be applied to static class {0}. + + + + 'OptionsValidatorAttribute' can't be applied to a static class. + 'OptionsValidatorAttribute' can't be applied to a static class. + + + + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + + + + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + + + + There is a circular type reference involving type {0} preventing it from being used for static validation. + There is a circular type reference involving type {0} preventing it from being used for static validation. + + + + Unsupported circular references in model types. + Unsupported circular references in model types. + + + + Type {0} does not implement the required IValidateOptions<{1}> interface. + Type {0} does not implement the required IValidateOptions<{1}> interface. + + + + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + + + + Can't apply validation attributes to private field or property {0}. + Can't apply validation attributes to private field or property {0}. + + + + Can't validate private fields or properties. + Can't validate private fields or properties. + + + + Type {0} has no fields or properties to validate, referenced from member {1}. + Type {0} has no fields or properties to validate, referenced from member {1}. + + + + A member type has no fields or properties to validate. + A member type has no fields or properties to validate. + + + + Type {0} has no fields or properties to validate, referenced by type {1}. + Type {0} has no fields or properties to validate, referenced by type {1}. + + + + A type has no fields or properties to validate. + A type has no fields or properties to validate. + + + + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + + + + Member type is not enumerable. + Member type is not enumerable. + + + + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + + + + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + + + + Member potentially missing enumerable validation. + Member potentially missing enumerable validation. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + + + + Member potentially missing transitive validation. + Member potentially missing transitive validation. + + + + Validator type {0} doesn't have a parameterless constructor. + Validator type {0} doesn't have a parameterless constructor. + + + + Validators used for transitive or enumerable validation must have a constructor with no parameters. + Validators used for transitive or enumerable validation must have a constructor with no parameters. + + + + + \ No newline at end of file diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.pt-BR.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.pt-BR.xlf new file mode 100644 index 0000000000000..80eb423892678 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.pt-BR.xlf @@ -0,0 +1,137 @@ + + + + + + Type {0} already implements the Validate method. + Type {0} already implements the Validate method. + + + + A type already includes an implementation of the 'Validate' method. + A type already includes an implementation of the 'Validate' method. + + + + [OptionsValidator] cannot be applied to static class {0}. + [OptionsValidator] cannot be applied to static class {0}. + + + + 'OptionsValidatorAttribute' can't be applied to a static class. + 'OptionsValidatorAttribute' can't be applied to a static class. + + + + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + + + + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + + + + There is a circular type reference involving type {0} preventing it from being used for static validation. + There is a circular type reference involving type {0} preventing it from being used for static validation. + + + + Unsupported circular references in model types. + Unsupported circular references in model types. + + + + Type {0} does not implement the required IValidateOptions<{1}> interface. + Type {0} does not implement the required IValidateOptions<{1}> interface. + + + + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + + + + Can't apply validation attributes to private field or property {0}. + Can't apply validation attributes to private field or property {0}. + + + + Can't validate private fields or properties. + Can't validate private fields or properties. + + + + Type {0} has no fields or properties to validate, referenced from member {1}. + Type {0} has no fields or properties to validate, referenced from member {1}. + + + + A member type has no fields or properties to validate. + A member type has no fields or properties to validate. + + + + Type {0} has no fields or properties to validate, referenced by type {1}. + Type {0} has no fields or properties to validate, referenced by type {1}. + + + + A type has no fields or properties to validate. + A type has no fields or properties to validate. + + + + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + + + + Member type is not enumerable. + Member type is not enumerable. + + + + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + + + + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + + + + Member potentially missing enumerable validation. + Member potentially missing enumerable validation. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + + + + Member potentially missing transitive validation. + Member potentially missing transitive validation. + + + + Validator type {0} doesn't have a parameterless constructor. + Validator type {0} doesn't have a parameterless constructor. + + + + Validators used for transitive or enumerable validation must have a constructor with no parameters. + Validators used for transitive or enumerable validation must have a constructor with no parameters. + + + + + \ No newline at end of file diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.ru.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.ru.xlf new file mode 100644 index 0000000000000..7c01d660f80f0 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.ru.xlf @@ -0,0 +1,137 @@ + + + + + + Type {0} already implements the Validate method. + Type {0} already implements the Validate method. + + + + A type already includes an implementation of the 'Validate' method. + A type already includes an implementation of the 'Validate' method. + + + + [OptionsValidator] cannot be applied to static class {0}. + [OptionsValidator] cannot be applied to static class {0}. + + + + 'OptionsValidatorAttribute' can't be applied to a static class. + 'OptionsValidatorAttribute' can't be applied to a static class. + + + + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + + + + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + + + + There is a circular type reference involving type {0} preventing it from being used for static validation. + There is a circular type reference involving type {0} preventing it from being used for static validation. + + + + Unsupported circular references in model types. + Unsupported circular references in model types. + + + + Type {0} does not implement the required IValidateOptions<{1}> interface. + Type {0} does not implement the required IValidateOptions<{1}> interface. + + + + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + + + + Can't apply validation attributes to private field or property {0}. + Can't apply validation attributes to private field or property {0}. + + + + Can't validate private fields or properties. + Can't validate private fields or properties. + + + + Type {0} has no fields or properties to validate, referenced from member {1}. + Type {0} has no fields or properties to validate, referenced from member {1}. + + + + A member type has no fields or properties to validate. + A member type has no fields or properties to validate. + + + + Type {0} has no fields or properties to validate, referenced by type {1}. + Type {0} has no fields or properties to validate, referenced by type {1}. + + + + A type has no fields or properties to validate. + A type has no fields or properties to validate. + + + + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + + + + Member type is not enumerable. + Member type is not enumerable. + + + + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + + + + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + + + + Member potentially missing enumerable validation. + Member potentially missing enumerable validation. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + + + + Member potentially missing transitive validation. + Member potentially missing transitive validation. + + + + Validator type {0} doesn't have a parameterless constructor. + Validator type {0} doesn't have a parameterless constructor. + + + + Validators used for transitive or enumerable validation must have a constructor with no parameters. + Validators used for transitive or enumerable validation must have a constructor with no parameters. + + + + + \ No newline at end of file diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.tr.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.tr.xlf new file mode 100644 index 0000000000000..4c7a25edb284c --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.tr.xlf @@ -0,0 +1,137 @@ + + + + + + Type {0} already implements the Validate method. + Type {0} already implements the Validate method. + + + + A type already includes an implementation of the 'Validate' method. + A type already includes an implementation of the 'Validate' method. + + + + [OptionsValidator] cannot be applied to static class {0}. + [OptionsValidator] cannot be applied to static class {0}. + + + + 'OptionsValidatorAttribute' can't be applied to a static class. + 'OptionsValidatorAttribute' can't be applied to a static class. + + + + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + + + + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + + + + There is a circular type reference involving type {0} preventing it from being used for static validation. + There is a circular type reference involving type {0} preventing it from being used for static validation. + + + + Unsupported circular references in model types. + Unsupported circular references in model types. + + + + Type {0} does not implement the required IValidateOptions<{1}> interface. + Type {0} does not implement the required IValidateOptions<{1}> interface. + + + + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + + + + Can't apply validation attributes to private field or property {0}. + Can't apply validation attributes to private field or property {0}. + + + + Can't validate private fields or properties. + Can't validate private fields or properties. + + + + Type {0} has no fields or properties to validate, referenced from member {1}. + Type {0} has no fields or properties to validate, referenced from member {1}. + + + + A member type has no fields or properties to validate. + A member type has no fields or properties to validate. + + + + Type {0} has no fields or properties to validate, referenced by type {1}. + Type {0} has no fields or properties to validate, referenced by type {1}. + + + + A type has no fields or properties to validate. + A type has no fields or properties to validate. + + + + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + + + + Member type is not enumerable. + Member type is not enumerable. + + + + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + + + + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + + + + Member potentially missing enumerable validation. + Member potentially missing enumerable validation. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + + + + Member potentially missing transitive validation. + Member potentially missing transitive validation. + + + + Validator type {0} doesn't have a parameterless constructor. + Validator type {0} doesn't have a parameterless constructor. + + + + Validators used for transitive or enumerable validation must have a constructor with no parameters. + Validators used for transitive or enumerable validation must have a constructor with no parameters. + + + + + \ No newline at end of file diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.zh-Hans.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.zh-Hans.xlf new file mode 100644 index 0000000000000..46de6fae73a5f --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.zh-Hans.xlf @@ -0,0 +1,137 @@ + + + + + + Type {0} already implements the Validate method. + Type {0} already implements the Validate method. + + + + A type already includes an implementation of the 'Validate' method. + A type already includes an implementation of the 'Validate' method. + + + + [OptionsValidator] cannot be applied to static class {0}. + [OptionsValidator] cannot be applied to static class {0}. + + + + 'OptionsValidatorAttribute' can't be applied to a static class. + 'OptionsValidatorAttribute' can't be applied to a static class. + + + + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + + + + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + + + + There is a circular type reference involving type {0} preventing it from being used for static validation. + There is a circular type reference involving type {0} preventing it from being used for static validation. + + + + Unsupported circular references in model types. + Unsupported circular references in model types. + + + + Type {0} does not implement the required IValidateOptions<{1}> interface. + Type {0} does not implement the required IValidateOptions<{1}> interface. + + + + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + + + + Can't apply validation attributes to private field or property {0}. + Can't apply validation attributes to private field or property {0}. + + + + Can't validate private fields or properties. + Can't validate private fields or properties. + + + + Type {0} has no fields or properties to validate, referenced from member {1}. + Type {0} has no fields or properties to validate, referenced from member {1}. + + + + A member type has no fields or properties to validate. + A member type has no fields or properties to validate. + + + + Type {0} has no fields or properties to validate, referenced by type {1}. + Type {0} has no fields or properties to validate, referenced by type {1}. + + + + A type has no fields or properties to validate. + A type has no fields or properties to validate. + + + + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + + + + Member type is not enumerable. + Member type is not enumerable. + + + + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + + + + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + + + + Member potentially missing enumerable validation. + Member potentially missing enumerable validation. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + + + + Member potentially missing transitive validation. + Member potentially missing transitive validation. + + + + Validator type {0} doesn't have a parameterless constructor. + Validator type {0} doesn't have a parameterless constructor. + + + + Validators used for transitive or enumerable validation must have a constructor with no parameters. + Validators used for transitive or enumerable validation must have a constructor with no parameters. + + + + + \ No newline at end of file diff --git a/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.zh-Hant.xlf b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.zh-Hant.xlf new file mode 100644 index 0000000000000..3b7ddf599ceeb --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/Resources/xlf/Strings.zh-Hant.xlf @@ -0,0 +1,137 @@ + + + + + + Type {0} already implements the Validate method. + Type {0} already implements the Validate method. + + + + A type already includes an implementation of the 'Validate' method. + A type already includes an implementation of the 'Validate' method. + + + + [OptionsValidator] cannot be applied to static class {0}. + [OptionsValidator] cannot be applied to static class {0}. + + + + 'OptionsValidatorAttribute' can't be applied to a static class. + 'OptionsValidatorAttribute' can't be applied to a static class. + + + + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + + + + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + + + + There is a circular type reference involving type {0} preventing it from being used for static validation. + There is a circular type reference involving type {0} preventing it from being used for static validation. + + + + Unsupported circular references in model types. + Unsupported circular references in model types. + + + + Type {0} does not implement the required IValidateOptions<{1}> interface. + Type {0} does not implement the required IValidateOptions<{1}> interface. + + + + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + + + + Can't apply validation attributes to private field or property {0}. + Can't apply validation attributes to private field or property {0}. + + + + Can't validate private fields or properties. + Can't validate private fields or properties. + + + + Type {0} has no fields or properties to validate, referenced from member {1}. + Type {0} has no fields or properties to validate, referenced from member {1}. + + + + A member type has no fields or properties to validate. + A member type has no fields or properties to validate. + + + + Type {0} has no fields or properties to validate, referenced by type {1}. + Type {0} has no fields or properties to validate, referenced by type {1}. + + + + A type has no fields or properties to validate. + A type has no fields or properties to validate. + + + + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + + + + Member type is not enumerable. + Member type is not enumerable. + + + + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + + + + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + + + + Member potentially missing enumerable validation. + Member potentially missing enumerable validation. + + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + + + + Member potentially missing transitive validation. + Member potentially missing transitive validation. + + + + Validator type {0} doesn't have a parameterless constructor. + Validator type {0} doesn't have a parameterless constructor. + + + + Validators used for transitive or enumerable validation must have a constructor with no parameters. + Validators used for transitive or enumerable validation must have a constructor with no parameters. + + + + + \ No newline at end of file diff --git a/src/libraries/Microsoft.Extensions.Options/gen/SymbolHolder.cs b/src/libraries/Microsoft.Extensions.Options/gen/SymbolHolder.cs new file mode 100644 index 0000000000000..c78106e1bc4a2 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/SymbolHolder.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.CodeAnalysis; + +namespace Microsoft.Extensions.Options.Generators +{ + /// + /// Holds required symbols for the . + /// + internal sealed record class SymbolHolder( + INamedTypeSymbol OptionsValidatorSymbol, + INamedTypeSymbol ValidationAttributeSymbol, + INamedTypeSymbol DataTypeAttributeSymbol, + INamedTypeSymbol ValidateOptionsSymbol, + INamedTypeSymbol IValidatableObjectSymbol, + INamedTypeSymbol TypeSymbol, + INamedTypeSymbol? ValidateObjectMembersAttributeSymbol, + INamedTypeSymbol? ValidateEnumeratedItemsAttributeSymbol); +} diff --git a/src/libraries/Microsoft.Extensions.Options/gen/SymbolLoader.cs b/src/libraries/Microsoft.Extensions.Options/gen/SymbolLoader.cs new file mode 100644 index 0000000000000..6f805e91a0585 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/SymbolLoader.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.CodeAnalysis; + +namespace Microsoft.Extensions.Options.Generators +{ + internal static class SymbolLoader + { + public const string OptionsValidatorAttribute = "Microsoft.Extensions.Options.OptionsValidatorAttribute"; + internal const string ValidationAttribute = "System.ComponentModel.DataAnnotations.ValidationAttribute"; + internal const string DataTypeAttribute = "System.ComponentModel.DataAnnotations.DataTypeAttribute"; + internal const string IValidatableObjectType = "System.ComponentModel.DataAnnotations.IValidatableObject"; + internal const string IValidateOptionsType = "Microsoft.Extensions.Options.IValidateOptions`1"; + internal const string TypeOfType = "System.Type"; + internal const string ValidateObjectMembersAttribute = "Microsoft.Extensions.Options.ValidateObjectMembersAttribute"; + internal const string ValidateEnumeratedItemsAttribute = "Microsoft.Extensions.Options.ValidateEnumeratedItemsAttribute"; + + public static bool TryLoad(Compilation compilation, out SymbolHolder? symbolHolder) + { + INamedTypeSymbol? GetSymbol(string metadataName, bool optional = false) + { + var symbol = compilation.GetTypeByMetadataName(metadataName); + if (symbol == null && !optional) + { + return null; + } + + return symbol; + } + + // required + var optionsValidatorSymbol = GetSymbol(OptionsValidatorAttribute); + var validationAttributeSymbol = GetSymbol(ValidationAttribute); + var dataTypeAttributeSymbol = GetSymbol(DataTypeAttribute); + var ivalidatableObjectSymbol = GetSymbol(IValidatableObjectType); + var validateOptionsSymbol = GetSymbol(IValidateOptionsType); + var typeSymbol = GetSymbol(TypeOfType); + + #pragma warning disable S1067 // Expressions should not be too complex + if (optionsValidatorSymbol == null || + validationAttributeSymbol == null || + dataTypeAttributeSymbol == null || + ivalidatableObjectSymbol == null || + validateOptionsSymbol == null || + typeSymbol == null) + { + symbolHolder = default; + return false; + } + #pragma warning restore S1067 // Expressions should not be too complex + + symbolHolder = new( + optionsValidatorSymbol, + validationAttributeSymbol, + dataTypeAttributeSymbol, + validateOptionsSymbol, + ivalidatableObjectSymbol, + typeSymbol, + + // optional + GetSymbol(ValidateObjectMembersAttribute, optional: true), + GetSymbol(ValidateEnumeratedItemsAttribute, optional: true)); + + return true; + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/gen/TypeDeclarationSyntaxReceiver.cs b/src/libraries/Microsoft.Extensions.Options/gen/TypeDeclarationSyntaxReceiver.cs new file mode 100644 index 0000000000000..f007c53cd5fc1 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/gen/TypeDeclarationSyntaxReceiver.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.Extensions.Options.Generators +{ + /// + /// Class/struct/record declaration syntax receiver for generators. + /// + internal sealed class TypeDeclarationSyntaxReceiver : ISyntaxReceiver + { + internal static ISyntaxReceiver Create() => new TypeDeclarationSyntaxReceiver(); + + /// + /// Gets class/struct/record declaration syntax holders after visiting nodes. + /// + public ICollection TypeDeclarations { get; } = new List(); + + /// + public void OnVisitSyntaxNode(SyntaxNode syntaxNode) + { + if (syntaxNode is ClassDeclarationSyntax classSyntax) + { + TypeDeclarations.Add(classSyntax); + } + else if (syntaxNode is StructDeclarationSyntax structSyntax) + { + TypeDeclarations.Add(structSyntax); + } + else if (syntaxNode is RecordDeclarationSyntax recordSyntax) + { + TypeDeclarations.Add(recordSyntax); + } + else if (syntaxNode is InterfaceDeclarationSyntax interfaceSyntax) + { + TypeDeclarations.Add(interfaceSyntax); + } + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/ref/Microsoft.Extensions.Options.cs b/src/libraries/Microsoft.Extensions.Options/ref/Microsoft.Extensions.Options.cs index 6ae6095e9ad3e..d81036040688b 100644 --- a/src/libraries/Microsoft.Extensions.Options/ref/Microsoft.Extensions.Options.cs +++ b/src/libraries/Microsoft.Extensions.Options/ref/Microsoft.Extensions.Options.cs @@ -215,6 +215,10 @@ public OptionsValidationException(string optionsName, System.Type optionsType, S public string OptionsName { get { throw null; } } public System.Type OptionsType { get { throw null; } } } + [System.AttributeUsage(System.AttributeTargets.Class | System.AttributeTargets.Struct)] + public sealed class OptionsValidatorAttribute : System.Attribute + { + } public partial class OptionsWrapper<[System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] TOptions> : Microsoft.Extensions.Options.IOptions where TOptions : class { public OptionsWrapper(TOptions options) { } @@ -282,6 +286,20 @@ public PostConfigureOptions(string? name, TDep1 dependency1, TDep2 dependency2, public virtual void PostConfigure(string? name, TOptions options) { } public void PostConfigure(TOptions options) { } } + [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field)] + public sealed class ValidateEnumeratedItemsAttribute : System.Attribute + { + public ValidateEnumeratedItemsAttribute() {} + public ValidateEnumeratedItemsAttribute(System.Type validator) { throw null; } + public System.Type? Validator { get {throw null; } } + } + [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Field)] + public sealed class ValidateObjectMembersAttribute : System.Attribute + { + public ValidateObjectMembersAttribute() {} + public ValidateObjectMembersAttribute(System.Type validator) { throw null; } + public System.Type? Validator { get { throw null; } } + } public partial class ValidateOptionsResult { public static readonly Microsoft.Extensions.Options.ValidateOptionsResult Skip; diff --git a/src/libraries/Microsoft.Extensions.Options/src/Microsoft.Extensions.Options.csproj b/src/libraries/Microsoft.Extensions.Options/src/Microsoft.Extensions.Options.csproj index 202e9435ce7f6..adfda8a3d1213 100644 --- a/src/libraries/Microsoft.Extensions.Options/src/Microsoft.Extensions.Options.csproj +++ b/src/libraries/Microsoft.Extensions.Options/src/Microsoft.Extensions.Options.csproj @@ -30,4 +30,10 @@ + + + + diff --git a/src/libraries/Microsoft.Extensions.Options/src/OptionsValidatorAttribute.cs b/src/libraries/Microsoft.Extensions.Options/src/OptionsValidatorAttribute.cs new file mode 100644 index 0000000000000..39e6aa3c5b71e --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/src/OptionsValidatorAttribute.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics; + +namespace Microsoft.Extensions.Options +{ + /// + /// Triggers the automatic generation of the implementation of at compile time. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] + public sealed class OptionsValidatorAttribute : Attribute + { + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/src/ValidateEnumeratedItemsAttribute.cs b/src/libraries/Microsoft.Extensions.Options/src/ValidateEnumeratedItemsAttribute.cs new file mode 100644 index 0000000000000..7f3630207e13a --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/src/ValidateEnumeratedItemsAttribute.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.Options; + +namespace Microsoft.Extensions.Options +{ + /// + /// Marks a field or property to be enumerated, and each enumerated object to be validated. + /// + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public sealed class ValidateEnumeratedItemsAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Using this constructor for a field/property tells the code generator to + /// generate validation for the individual members of the enumerable's type. + /// + public ValidateEnumeratedItemsAttribute() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// A type that implements for the enumerable's type. + /// + /// Using this constructor for a field/property tells the code generator to use the given type to validate + /// the object held by the enumerable. + /// + public ValidateEnumeratedItemsAttribute(Type validator) + { + Validator = validator; + } + + /// + /// Gets the type to use to validate the enumerable's objects. + /// + public Type? Validator { get; } + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/src/ValidateObjectMembersAttribute.cs b/src/libraries/Microsoft.Extensions.Options/src/ValidateObjectMembersAttribute.cs new file mode 100644 index 0000000000000..69a36cad65ede --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/src/ValidateObjectMembersAttribute.cs @@ -0,0 +1,44 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using Microsoft.Extensions.Options; + +namespace Microsoft.Extensions.Options +{ + /// + /// Marks a field or property to be validated transitively. + /// + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public sealed class ValidateObjectMembersAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Using this constructor for a field/property tells the code generator to + /// generate validation for the individual members of the field/property's type. + /// + public ValidateObjectMembersAttribute() + { + } + + /// + /// Initializes a new instance of the class. + /// + /// A type that implements for the field/property's type. + /// + /// Using this constructor for a field/property tells the code generator to use the given type to validate + /// the object held by the field/property. + /// + public ValidateObjectMembersAttribute(Type validator) + { + Validator = validator; + } + + /// + /// Gets the type to use to validate a field or property. + /// + public Type? Validator { get; } + } +} 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 new file mode 100644 index 0000000000000..ad094f991803f --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Main.cs @@ -0,0 +1,1246 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options.Generators; +using SourceGenerators.Tests; +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Gen.OptionsValidation.Unit.Test; + +public class EmitterTests +{ + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task TestEmitterWithCustomValidator() + { + string source = """ + using System; + using System.ComponentModel.DataAnnotations; + using Microsoft.Extensions.Options; + + #nullable enable + + namespace HelloWorld + { + public class MyOptions + { + [Required] + public string Val1 { get; set; } = string.Empty; + + [Range(1, 3)] + public int Val2 { get; set; } + } + + [OptionsValidator] + public partial struct MyOptionsValidator : IValidateOptions + { + } + } + """; + + string generatedSource = """ + + // + #nullable enable + #pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103 + namespace HelloWorld +{ + partial struct 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::HelloWorld.MyOptions options) + { + var baseName = (string.IsNullOrEmpty(name) ? "MyOptions" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val1"; + context.DisplayName = baseName + "Val1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + context.MemberName = "Val2"; + context.DisplayName = baseName + "Val2"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val2!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace __OptionValidationStaticInstances +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal static class __Attributes + { + internal static readonly global::System.ComponentModel.DataAnnotations.RequiredAttribute A1 = new global::System.ComponentModel.DataAnnotations.RequiredAttribute(); + + internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A2 = new global::System.ComponentModel.DataAnnotations.RangeAttribute( + (int)1, + (int)3); + } +} +namespace __OptionValidationStaticInstances +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal static class __Validators + { + } +} + +"""; + + var (diagnostics, generatedSources) = await RoslynTestUtils.RunGenerator( + new Generator(), + new[] + { + Assembly.GetAssembly(typeof(RequiredAttribute))!, + Assembly.GetAssembly(typeof(OptionsValidatorAttribute))!, + Assembly.GetAssembly(typeof(IValidateOptions))!, + }, + new List { source }) + .ConfigureAwait(false); + + Assert.Empty(diagnostics); + _ = Assert.Single(generatedSources); + + Assert.Equal(generatedSource.Replace("\r\n", "\n"), generatedSources[0].SourceText.ToString().Replace("\r\n", "\n")); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task PotentiallyMissingAttributes() + { + var (diagnostics, _) = await RunGenerator(@" + public class FirstModel + { + [Required] + public SecondModel? P1 { get; set; } + + [Required] + public System.Collections.Generic.IList? P2 { get; set; } + } + + public class SecondModel + { + [Required] + public string? P3; + } + + [OptionsValidator] + public partial class FirstValidator : IValidateOptions + { + } + "); + + Assert.Equal(2, diagnostics.Count); + Assert.Equal(DiagDescriptors.PotentiallyMissingTransitiveValidation.Id, diagnostics[0].Id); + Assert.Equal(DiagDescriptors.PotentiallyMissingEnumerableValidation.Id, diagnostics[1].Id); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task CircularTypeReferences() + { + var (diagnostics, _) = await RunGenerator(@" + public class FirstModel + { + [Required] + [ValidateObjectMembers] + public FirstModel? P1 { get; set; } + } + + [OptionsValidator] + public partial class FirstValidator : IValidateOptions + { + } + "); + + _ = Assert.Single(diagnostics); + Assert.Equal(DiagDescriptors.CircularTypeReferences.Id, diagnostics[0].Id); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task InvalidValidatorInterface() + { + var (diagnostics, _) = await RunGenerator(@" + public class FirstModel + { + [Required] + public string? P1; + } + + public class SecondModel + { + [Required] + public string? P2; + } + + [OptionsValidator] + public partial class FirstValidator : IValidateOptions + { + } + + [OptionsValidator] + public partial class SecondValidator + { + } + "); + + _ = Assert.Single(diagnostics); + Assert.Equal(DiagDescriptors.DoesntImplementIValidateOptions.Id, diagnostics[0].Id); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task NotValidator() + { + var (diagnostics, _) = await RunGenerator(@" + public class FirstModel + { + [ValidateObjectMembers(typeof(SecondValidator)] + public SecondModel? P1; + } + + public class SecondModel + { + [Required] + public string? P2; + } + + [OptionsValidator] + public partial class FirstValidator : IValidateOptions + { + } + + public partial class SecondValidator + { + } + "); + + _ = Assert.Single(diagnostics); + Assert.Equal(DiagDescriptors.DoesntImplementIValidateOptions.Id, diagnostics[0].Id); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task ValidatorAlreadyImplementValidateFunction() + { + var (diagnostics, _) = await RunGenerator(@" + public class FirstModel + { + [Required] + public string? P1; + + [ValidateObjectMembers(typeof(SecondValidator)] + public SecondModel? P2; + } + + public class SecondModel + { + [Required] + public string? P3; + } + + [OptionsValidator] + public partial class FirstValidator : IValidateOptions + { + } + + [OptionsValidator] + public partial class SecondValidator : IValidateOptions + { + public ValidateOptionsResult Validate(string name, SecondModel options) + { + throw new System.NotSupportedException(); + } + } + "); + + _ = Assert.Single(diagnostics); + Assert.Equal(DiagDescriptors.AlreadyImplementsValidateMethod.Id, diagnostics[0].Id); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task NullValidator() + { + var (diagnostics, _) = await RunGenerator(@" + public class FirstModel + { + [ValidateObjectMembers(null!)] + public SecondModel? P1; + } + + public class SecondModel + { + [Required] + public string? P2; + } + + [OptionsValidator] + public partial class FirstValidator : IValidateOptions + { + } + + [OptionsValidator] + public partial class SecondValidator : IValidateOptions + { + } + "); + + _ = Assert.Single(diagnostics); + Assert.Equal(DiagDescriptors.NullValidatorType.Id, diagnostics[0].Id); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task NoSimpleValidatorConstructor() + { + var (diagnostics, _) = await RunGenerator(@" + public class FirstModel + { + [Required] + public string? P1; + + [ValidateObjectMembers(typeof(SecondValidator)] + public SecondModel? P2; + } + + public class SecondModel + { + [Required] + public string? P3; + } + + [OptionsValidator] + public partial class FirstValidator : IValidateOptions + { + } + + [OptionsValidator] + public partial class SecondValidator : IValidateOptions + { + public SecondValidator(int _) + { + } + } + "); + + _ = Assert.Single(diagnostics); + Assert.Equal(DiagDescriptors.ValidatorsNeedSimpleConstructor.Id, diagnostics[0].Id); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task NoStaticValidator() + { + var (diagnostics, _) = await RunGenerator(@" + public class FirstModel + { + [Required] + public string P1; + } + + [OptionsValidator] + public static partial class FirstValidator : IValidateOptions + { + } + "); + + _ = Assert.Single(diagnostics); + Assert.Equal(DiagDescriptors.CantBeStaticClass.Id, diagnostics[0].Id); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task BogusModelType() + { + var (diagnostics, _) = await RunGenerator(@" + [OptionsValidator] + public partial class FirstValidator : IValidateOptions + { + } + "); + + // the generator doesn't produce any errors here, since the C# compiler will take care of it + Assert.Empty(diagnostics); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task CantValidateOpenGenericMembers() + { + var (diagnostics, _) = await RunGenerator(@" + public class FirstModel + { + [Required] + [ValidateObjectMembers] + public T? P1; + + [ValidateObjectMembers] + [Required] + public T[]? P2; + + [ValidateObjectMembers] + [Required] + public System.Collections.Generics.IList P3 = null!; + } + + [OptionsValidator] + public partial class FirstValidator : IValidateOptions> + { + } + "); + + Assert.Equal(3, diagnostics.Count); + Assert.Equal(DiagDescriptors.CantUseWithGenericTypes.Id, diagnostics[0].Id); + Assert.Equal(DiagDescriptors.CantUseWithGenericTypes.Id, diagnostics[1].Id); + Assert.Equal(DiagDescriptors.CantUseWithGenericTypes.Id, diagnostics[2].Id); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task ClosedGenerics() + { + var (diagnostics, _) = await RunGenerator(@" + public class FirstModel + { + [Required] + [ValidateObjectMembers] + public T? P1; + + [ValidateObjectMembers] + [Required] + public T[]? P2; + + [ValidateObjectMembers] + [Required] + public int[]? P3; + + [ValidateObjectMembers] + [Required] + public System.Collections.Generics.IList? P4; + } + + [OptionsValidator] + public partial class FirstValidator : IValidateOptions> + { + } + "); + + Assert.Equal(4, diagnostics.Count); + Assert.Equal(DiagDescriptors.NoEligibleMember.Id, diagnostics[0].Id); + Assert.Equal(DiagDescriptors.NoEligibleMember.Id, diagnostics[1].Id); + Assert.Equal(DiagDescriptors.NoEligibleMember.Id, diagnostics[2].Id); + Assert.Equal(DiagDescriptors.NoEligibleMember.Id, diagnostics[3].Id); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task NoEligibleMembers() + { + var (diagnostics, _) = await RunGenerator(@" + public class FirstModel + { + [Required] + [ValidateObjectMembers] + public SecondModel? P1; + } + + public class SecondModel + { + public string P2; + } + + [OptionsValidator] + public partial class FirstValidator : IValidateOptions + { + } + + [OptionsValidator] + public partial class SecondValidator : IValidateOptions + { + } + "); + + Assert.Equal(2, diagnostics.Count); + Assert.Equal(DiagDescriptors.NoEligibleMember.Id, diagnostics[0].Id); + Assert.Equal(DiagDescriptors.NoEligibleMembersFromValidator.Id, diagnostics[1].Id); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task AlreadyImplemented() + { + var (diagnostics, _) = await RunGenerator(@" + public class FirstModel + { + [Required] + public string One { get; set; } = string.Empty; + } + + [OptionsValidator] + public partial class FirstValidator : IValidateOptions + { + public void Validate(string name, FirstModel fm) + { + } + } + "); + + _ = Assert.Single(diagnostics); + Assert.Equal(DiagDescriptors.AlreadyImplementsValidateMethod.Id, diagnostics[0].Id); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task ShouldNotProduceInfoWhenTheClassHasABaseClass() + { + var (diagnostics, _) = await RunGenerator(@" + public class Parent + { + [Required] + public string parentString { get; set; } + } + + public class Child : Parent + { + [Required] + public string childString { get; set; } + } + + [OptionsValidator] + public partial class Validator : IValidateOptions + { + } + "); + + Assert.Empty(diagnostics); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task ShouldNotProduceInfoWhenTransitiveClassHasABaseClass() + { + var (diagnostics, _) = await RunGenerator(@" + public class Parent + { + [Required] + public string parentString { get; set; } + } + + public class Child : Parent + { + [Required] + public string childString { get; set; } + } + + public class MyOptions + { + [ValidateObjectMembers] + public Child childVal { get; set; } + } + + [OptionsValidator] + public partial class Validator : IValidateOptions + { + } + "); + + Assert.Empty(diagnostics); + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [InlineData("bool")] + [InlineData("int")] + [InlineData("double")] + [InlineData("string")] + [InlineData("System.String")] + [InlineData("System.DateTime")] + public async Task ShouldProduceWarn_WhenTransitiveAttrMisused(string memberClass) + { + var (diagnostics, _) = await RunGenerator(@$" + public class InnerModel + {{ + [Required] + public string childString {{ get; set; }} + }} + + public class MyOptions + {{ + [Required] + public string simpleVal {{ get; set; }} + + [ValidateObjectMembers] + public {memberClass} complexVal {{ get; set; }} + }} + + [OptionsValidator] + public partial class Validator : IValidateOptions + {{ + }} + "); + + Assert.Single(diagnostics); + Assert.Equal(DiagDescriptors.NoEligibleMember.Id, diagnostics[0].Id); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task ShouldProduceWarningWhenTheClassHasNoEligibleMembers() + { + var (diagnostics, _) = await RunGenerator(@" + public class Child + { + private string AccountName { get; set; } + public object Weight; + } + + [OptionsValidator] + public partial class Validator : IValidateOptions + { + } + "); + + Assert.Single(diagnostics); + Assert.Equal(DiagDescriptors.NoEligibleMembersFromValidator.Id, diagnostics[0].Id); + } + + [ConditionalTheory(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + [InlineData("private")] + [InlineData("protected")] + public async Task ShouldProduceWarningWhenTheClassMembersAreInaccessible(string accessModifier) + { + var (diagnostics, _) = await RunGenerator($@" + public class Model + {{ + [Required] + public string? PublicVal {{ get; set; }} + + [Required] + {accessModifier} string? Val {{ get; set; }} + }} + + [OptionsValidator] + public partial class Validator : IValidateOptions + {{ + }} + "); + + Assert.Single(diagnostics); + Assert.Equal("SYSLIB1206", diagnostics[0].Id); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task ShouldNotProduceErrorWhenMultipleValidationAnnotationsExist() + { + var (diagnostics, _) = await RunGenerator(@" + public class IValidateOptionsTestFile + { + [MinLength(5)] + [MaxLength(15)] + public string Val9 { get; set; } + } + + [OptionsValidator] + public partial class Validator : IValidateOptions + { + } + "); + + Assert.Empty(diagnostics); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task ShouldNotProduceErrorWhenDataTypeAttributesAreUsed() + { + var (diagnostics, _) = await RunGenerator(@" + public class IValidateOptionsTestFile + { + [CreditCard] + public string Val3 = """"; + + [EmailAddress] + public string Val6 { get; set; } + + [EnumDataType(typeof(string))] + public string Val7 { get; set; } + + [FileExtensions] + public string Val8 { get; set; } + + [Phone] + public string Val10 { get; set; } + + [Url] + public string Val11 { get; set; } + } + + [OptionsValidator] + public partial class Validator : IValidateOptions + { + } + "); + + Assert.Empty(diagnostics); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task ShouldNotProduceErrorWhenConstVariableIsUsedAsAttributeArgument() + { + var (diagnostics, _) = await RunGenerator(@" + public class IValidateOptionsTestFile + { + private const int q = 5; + [Range(q, 10)] + public string Val11 { get; set; } + } + + [OptionsValidator] + public partial class Validator : IValidateOptions + { + } + "); + + Assert.Empty(diagnostics); + } + + // Testing on all existing & eligible annotations extending ValidationAttribute that aren't used above + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task ShouldNotProduceAnyMessagesWhenExistingValidationsArePlaced() + { + var (diagnostics, _) = await RunGenerator(@" + public class IValidateOptionsTestFile + { + [Required] + public string Val { get; set; } + + [Compare(""val"")] + public string Val2 { get; set; } + + [DataType(DataType.Password)] + public string _val5 = """"; + + [Range(5.1, 10.11)] + public string Val12 { get; set; } + + [Range(typeof(MemberDeclarationSyntax), ""1/2/2004"", ""3/4/2004"")] + public string Val14 { get; set; } + + [RegularExpression("""")] + public string Val15 { get; set; } + + [StringLength(5)] + public string Val16 { get; set; } + + [CustomValidation(typeof(MemberDeclarationSyntax), ""CustomMethod"")] + public string Val17 { get; set; } + } + + [OptionsValidator] + public partial class Validator : IValidateOptions + { + } + "); + + Assert.Empty(diagnostics); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task ShouldNotProduceErrorWhenPropertiesAreUsedAsAttributeArgument() + { + var (diagnostics, _) = await RunGenerator(@" + public class IValidateOptionsTestFile + { + private const int q = 5; + [Range(q, 10, ErrorMessage = ""ErrorMessage"")] + public string Val11 { get; set; } + } + + [OptionsValidator] + public partial class Validator : IValidateOptions + { + } + "); + + Assert.Empty(diagnostics); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task ShouldSkipWhenOptionsValidatorAttributeDoesNotExist() + { + var (diagnostics, _) = await RunGenerator(@" + public class IValidateOptionsTestFile + { + private const int q = 5; + [Range(q, 10, ErrorMessage = ""ErrorMessage"")] + public string Val11 { get; set; } + } + + [OptionsValidator] + public partial class Validator : IValidateOptions + { + } + ", includeOptionValidatorReferences: false); + + Assert.Empty(diagnostics); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task ShouldSkipAtrributeWhenAttributeSymbolCannotBeFound() + { + var (diagnostics, _) = await RunGenerator(@" + public class IValidateOptionsTestFile + { + [RandomTest] + public string Val11 { get; set; } + + [Range(1, 10, ErrorMessage = ""ErrorMessage"")] + public string Val12 { get; set; } + } + + [OptionsValidator] + public partial class Validator : IValidateOptions + { + } + "); + + Assert.Empty(diagnostics); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task ShouldSkipAtrributeWhenAttributeSymbolIsNotBasedOnValidationAttribute() + { + var (diagnostics, _) = await RunGenerator(@" + public class IValidateOptionsTestFile + { + [FilterUIHint(""MultiForeignKey"")] + public string Val11 { get; set; } + + [Range(1, 10, ErrorMessage = ""ErrorMessage"")] + public string Val12 { get; set; } + } + + [OptionsValidator] + public partial class Validator : IValidateOptions + { + } + "); + + Assert.Empty(diagnostics); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task ShouldAcceptAtrributeWhenAttributeIsInDifferentNamespace() + { + var (diagnostics, _) = await RunGenerator(@" + namespace Test { + public class IValidateOptionsTestFile + { + [Test] + public string Val11 { get; set; } + } + + [AttributeUsage(AttributeTargets.Class)] + public sealed class TestAttribute : ValidationAttribute + { + } + + [OptionsValidator] + public partial class Validator : IValidateOptions + { + } + } + ", inNamespace: false); + + Assert.Empty(diagnostics); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task ShouldHandleAtrributePropertiesOtherThanString() + { + var (diagnostics, _) = await RunGenerator(@" + namespace Test { + public class IValidateOptionsTestFile + { + [Test(num = 5)] + public string Val11 { get; set; } + + [Required] + public string Val12 { get; set; } + } + + [OptionsValidator] + public partial class Validator : IValidateOptions + { + } + } + + namespace System.ComponentModel.DataAnnotations { + [AttributeUsage(AttributeTargets.Class)] + public sealed class TestAttribute : ValidationAttribute + { + public int num { get; set; } + public TestAttribute() { + } + } + } + ", inNamespace: false); + + Assert.Empty(diagnostics); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task ShouldStoreFloatValuesCorrectly() + { + var backupCulture = CultureInfo.CurrentCulture; + CultureInfo.CurrentCulture = new CultureInfo("ru-RU", false); + try + { + var (diagMessages, generatedResults) = await RunGenerator(@" + public class Model + { + [Range(-0.1, 1.3)] + public string Val { get; set; } + } + + [OptionsValidator] + public partial class Validator : IValidateOptions + { + } + "); + + Assert.Empty(diagMessages); + Assert.Single(generatedResults); + Assert.DoesNotContain("0,1", generatedResults[0].SourceText.ToString()); + Assert.DoesNotContain("1,3", generatedResults[0].SourceText.ToString()); + } + finally + { + CultureInfo.CurrentCulture = backupCulture; + } + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task MultiModelValidatorGeneratesOnlyOnePartialTypeBlock() + { + var (diagnostics, sources) = await RunGenerator(@" + public class FirstModel + { + [Required] + public string P1 { get; set; } + } + + public class SecondModel + { + [Required] + public string P2 { get; set; } + } + + public class ThirdModel + { + [Required] + public string P3 { get; set; } + } + + [OptionsValidator] + public partial class MultiValidator : IValidateOptions, IValidateOptions, IValidateOptions + { + } + "); + + var typeDeclarations = sources[0].SyntaxTree + .GetRoot() + .DescendantNodes() + .OfType() + .ToArray(); + + var multiValidatorTypeDeclarations = typeDeclarations + .Where(x => x.Identifier.ValueText == "MultiValidator") + .ToArray(); + + Assert.Single(multiValidatorTypeDeclarations); + + var validateMethodDeclarations = multiValidatorTypeDeclarations[0] + .DescendantNodes() + .OfType() + .Where(x => x.Identifier.ValueText == "Validate") + .ToArray(); + + Assert.Equal(3, validateMethodDeclarations.Length); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task CircularTypeReferencesInEnumeration() + { + var (diagnostics, _) = await RunGenerator(@" + public class FirstModel + { + [Required] + [ValidateEnumeratedItems] + public FirstModel[]? P1 { get; set; } + } + + [OptionsValidator] + public partial class FirstValidator : IValidateOptions + { + } + "); + + _ = Assert.Single(diagnostics); + Assert.Equal(DiagDescriptors.CircularTypeReferences.Id, diagnostics[0].Id); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task NotValidatorInEnumeration() + { + var (diagnostics, _) = await RunGenerator(@" + public class FirstModel + { + [ValidateEnumeratedItems(typeof(SecondValidator)] + public SecondModel[]? P1; + } + + public class SecondModel + { + [Required] + public string? P2; + } + + [OptionsValidator] + public partial class FirstValidator : IValidateOptions + { + } + + public partial class SecondValidator + { + } + "); + + _ = Assert.Single(diagnostics); + Assert.Equal(DiagDescriptors.DoesntImplementIValidateOptions.Id, diagnostics[0].Id); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task NullValidatorInEnumeration() + { + var (diagnostics, _) = await RunGenerator(@" + public class FirstModel + { + [ValidateEnumeratedItems(null!)] + public SecondModel[]? P1; + } + + public class SecondModel + { + [Required] + public string? P2; + } + + [OptionsValidator] + public partial class FirstValidator : IValidateOptions + { + } + + [OptionsValidator] + public partial class SecondValidator : IValidateOptions + { + } + "); + + _ = Assert.Single(diagnostics); + Assert.Equal(DiagDescriptors.NullValidatorType.Id, diagnostics[0].Id); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task NoSimpleValidatorConstructorInEnumeration() + { + var (diagnostics, _) = await RunGenerator(@" + public class FirstModel + { + [Required] + public string? P1; + + [ValidateEnumeratedItems(typeof(SecondValidator)] + public SecondModel[]? P2; + } + + public class SecondModel + { + [Required] + public string? P3; + } + + [OptionsValidator] + public partial class FirstValidator : IValidateOptions + { + } + + [OptionsValidator] + public partial class SecondValidator : IValidateOptions + { + public SecondValidator(int _) + { + } + } + "); + + _ = Assert.Single(diagnostics); + Assert.Equal(DiagDescriptors.ValidatorsNeedSimpleConstructor.Id, diagnostics[0].Id); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task CantValidateOpenGenericMembersInEnumeration() + { + var (diagnostics, _) = await RunGenerator(@" + public class FirstModel + { + [Required] + [ValidateEnumeratedItems] + public T[]? P1; + + [ValidateEnumeratedItems] + [Required] + public T[]? P2; + + [ValidateEnumeratedItems] + [Required] + public System.Collections.Generic.IList P3 = null!; + } + + [OptionsValidator] + public partial class FirstValidator : IValidateOptions> + { + } + "); + + Assert.Equal(3, diagnostics.Count); + Assert.Equal(DiagDescriptors.CantUseWithGenericTypes.Id, diagnostics[0].Id); + Assert.Equal(DiagDescriptors.CantUseWithGenericTypes.Id, diagnostics[1].Id); + Assert.Equal(DiagDescriptors.CantUseWithGenericTypes.Id, diagnostics[2].Id); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task ClosedGenericsInEnumeration() + { + var (diagnostics, _) = await RunGenerator(@" + public class FirstModel + { + [ValidateEnumeratedItems] + [Required] + public T[]? P1; + + [ValidateEnumeratedItems] + [Required] + public int[]? P2; + + [ValidateEnumeratedItems] + [Required] + public System.Collections.Generic.IList? P3; + } + + [OptionsValidator] + public partial class FirstValidator : IValidateOptions> + { + } + "); + + Assert.Equal(3, diagnostics.Count); + Assert.Equal(DiagDescriptors.NoEligibleMember.Id, diagnostics[0].Id); + Assert.Equal(DiagDescriptors.NoEligibleMember.Id, diagnostics[1].Id); + Assert.Equal(DiagDescriptors.NoEligibleMember.Id, diagnostics[2].Id); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task NotEnumerable() + { + var (diagnostics, _) = await RunGenerator(@" + public class FirstModel + { + [Required] + [ValidateEnumeratedItems] + public int P1; + } + + [OptionsValidator] + public partial class FirstValidator : IValidateOptions + { + } + "); + + Assert.Equal(1, diagnostics.Count); + Assert.Equal(DiagDescriptors.NotEnumerableType.Id, diagnostics[0].Id); + } + + private static async Task<(IReadOnlyList diagnostics, ImmutableArray generatedSources)> RunGenerator( + string code, + bool wrap = true, + bool inNamespace = true, + bool includeOptionValidatorReferences = true, + bool includeSystemReferences = true, + bool includeOptionsReferences = true, + bool includeTransitiveReferences = true) + { + var text = code; + if (wrap) + { + var nspaceStart = "namespace Test {"; + var nspaceEnd = "}"; + if (!inNamespace) + { + nspaceStart = ""; + nspaceEnd = ""; + } + + text = $@" + {nspaceStart} + using System.ComponentModel.DataAnnotations; + using Microsoft.Extensions.Options.Validation; + using Microsoft.Shared.Data.Validation; + using Microsoft.Extensions.Options; + using Microsoft.CodeAnalysis.CSharp.Syntax; + {code} + {nspaceEnd} + "; + } + + var assemblies = new List { Assembly.GetAssembly(typeof(MemberDeclarationSyntax))! }; + + if (includeOptionValidatorReferences) + { + assemblies.Add(Assembly.GetAssembly(typeof(OptionsValidatorAttribute))!); + } + + if (includeSystemReferences) + { + assemblies.Add(Assembly.GetAssembly(typeof(RequiredAttribute))!); + } + + if (includeOptionsReferences) + { + assemblies.Add(Assembly.GetAssembly(typeof(IValidateOptions))!); + } + + if (includeTransitiveReferences) + { + assemblies.Add(Assembly.GetAssembly(typeof(Microsoft.Extensions.Options.ValidateObjectMembersAttribute))!); + } + + var result = await RoslynTestUtils.RunGenerator(new Generator(), assemblies.ToArray(), new[] { text }) + .ConfigureAwait(false); + + return result; + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Microsoft.Extensions.Options.SourceGeneration.Unit.Tests.csproj b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Microsoft.Extensions.Options.SourceGeneration.Unit.Tests.csproj new file mode 100644 index 0000000000000..c18245aa1b5f4 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Microsoft.Extensions.Options.SourceGeneration.Unit.Tests.csproj @@ -0,0 +1,37 @@ + + + $(NetCoreAppCurrent);$(NetFrameworkMinimum) + $(MicrosoftCodeAnalysisVersion_4_4) + true + + true + + $(DefineConstants);ROSLYN4_0_OR_GREATER;ROSLYN4_4_OR_GREATER;ROSLYN_4_0_OR_GREATER + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Resources/Strings.resx b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Resources/Strings.resx new file mode 100644 index 0000000000000..1673d4621545b --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGeneration.Unit.Tests/Resources/Strings.resx @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ErrorMessageResourceName + + + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + + + Member potentially missing enumerable validation. + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + + + There is a circular type reference involving type {0} preventing it from being used for static validation. + + + Unsupported circular references in model types. + + + Can't apply validation attributes to private field or property {0}. + + + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + + + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + + + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + + + A member type has no fields or properties to validate. + + + A type has no fields or properties to validate. + + + Member type is not enumerable. + + + Validator type {0} doesn't have a parameterless constructor. + + + Type {0} does not implement the required IValidateOptions<{1}> interface. + + + Validators used for transitive or enumerable validation must have a constructor with no parameters. + + + Type {0} has no fields or properties to validate, referenced by type {1}. + + + Type {0} has no fields or properties to validate, referenced from member {1}. + + + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + + + Can't validate private fields or properties. + + + [OptionsValidator] cannot be applied to static class {0}. + + + A type already includes an implementation of the 'Validate' method. + + + Type {0} already implements the Validate method. + + + 'OptionsValidatorAttribute' can't be applied to a static class. + + + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + + + Member potentially missing transitive validation. + + \ No newline at end of file diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetCoreApp/Validators.g.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetCoreApp/Validators.g.cs new file mode 100644 index 0000000000000..a78ca02c4b6ca --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetCoreApp/Validators.g.cs @@ -0,0 +1,2080 @@ + + // + #nullable enable + #pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] +internal sealed partial class __ThirdModelNoNamespaceValidator__ +{ + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::ThirdModelNoNamespace options) + { + var baseName = (string.IsNullOrEmpty(name) ? "ThirdModelNoNamespace" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P5"; + context.DisplayName = baseName + "P5"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } +} +partial class FirstValidatorNoNamespace +{ + /// + /// 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::FirstModelNoNamespace options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModelNoNamespace" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P1"; + context.DisplayName = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + if (options.P2 is not null) + { + builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V1.Validate(baseName + "P2", options.P2)); + } + + if (options.P3 is not null) + { + builder.AddResult(global::__ThirdModelNoNamespaceValidator__.Validate(baseName + "P3", options.P3)); + } + + return builder.Build(); + } +} +partial class SecondValidatorNoNamespace +{ + /// + /// 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::SecondModelNoNamespace options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModelNoNamespace" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P4"; + context.DisplayName = baseName + "P4"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } +} +namespace CustomAttr +{ + partial class FirstValidator + { + /// + /// 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::CustomAttr.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A3); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + context.MemberName = "P2"; + context.DisplayName = baseName + "P2"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A4); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P2!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace Enumeration +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __SecondModelValidator__ + { + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Enumeration.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P6"; + context.DisplayName = baseName + "P6"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P6!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace Enumeration +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __ThirdModelValidator__ + { + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Enumeration.ThirdModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "ThirdModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Value"; + context.DisplayName = baseName + "Value"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A5); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Value!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace Enumeration +{ + partial struct FirstValidator + { + /// + /// 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::Enumeration.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options); + + if (options.P1 is not null) + { + var count = 0; + foreach (var o in options.P1) + { + if (o is not null) + { + builder.AddResult(global::Enumeration.__SecondModelValidator__.Validate(baseName + $"P1[{count}]", o)); + } + else + { + builder.AddError(baseName + $"P1[{count}] is null"); + } + count++; + } + } + + if (options.P2 is not null) + { + var count = 0; + foreach (var o in options.P2) + { + if (o is not null) + { + builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V2.Validate(baseName + $"P2[{count}]", o)); + } + else + { + builder.AddError(baseName + $"P2[{count}] is null"); + } + count++; + } + } + + if (options.P3 is not null) + { + var count = 0; + foreach (var o in options.P3) + { + if (o is not null) + { + builder.AddResult(global::Enumeration.__SecondModelValidator__.Validate(baseName + $"P3[{count}]", o)); + } + count++; + } + } + + if (options.P4 is not null) + { + var count = 0; + foreach (var o in options.P4) + { + builder.AddResult(global::Enumeration.__ThirdModelValidator__.Validate(baseName + $"P4[{count++}]", o)); + } + } + + if (options.P5 is not null) + { + var count = 0; + foreach (var o in options.P5) + { + if (o is not null) + { + builder.AddResult(global::Enumeration.__ThirdModelValidator__.Validate(baseName + $"P5[{count}]", o.Value)); + } + count++; + } + } + + if (options.P51 is not null) + { + var count = 0; + foreach (var o in options.P51) + { + if (o is not null) + { + builder.AddResult(global::Enumeration.__ThirdModelValidator__.Validate(baseName + $"P51[{count}]", o.Value)); + } + count++; + } + } + + if (options.P6 is not null) + { + var count = 0; + foreach (var o in options.P6.Value) + { + if (o is not null) + { + builder.AddResult(global::Enumeration.__SecondModelValidator__.Validate(baseName + $"P6[{count}]", o)); + } + else + { + builder.AddError(baseName + $"P6[{count}] is null"); + } + count++; + } + } + + { + var count = 0; + foreach (var o in options.P7) + { + if (o is not null) + { + builder.AddResult(global::Enumeration.__SecondModelValidator__.Validate(baseName + $"P7[{count}]", o)); + } + else + { + builder.AddError(baseName + $"P7[{count}] is null"); + } + count++; + } + } + + if (options.P8 is not null) + { + var count = 0; + foreach (var o in options.P8.Value) + { + if (o is not null) + { + builder.AddResult(global::Enumeration.__SecondModelValidator__.Validate(baseName + $"P8[{count}]", o)); + } + else + { + builder.AddError(baseName + $"P8[{count}] is null"); + } + count++; + } + } + + return builder.Build(); + } + } +} +namespace Enumeration +{ + partial struct SecondValidator + { + /// + /// 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::Enumeration.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P6"; + context.DisplayName = baseName + "P6"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P6!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace Fields +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __ThirdModelValidator__ + { + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Fields.ThirdModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "ThirdModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P5"; + context.DisplayName = baseName + "P5"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace Fields +{ + partial struct FirstValidator + { + /// + /// 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::Fields.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P1"; + context.DisplayName = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + if (options.P2 is not null) + { + builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V3.Validate(baseName + "P2", options.P2)); + } + + builder.AddResult(global::Fields.__ThirdModelValidator__.Validate(baseName + "P3", options.P3)); + + return builder.Build(); + } + } +} +namespace Fields +{ + partial struct SecondValidator + { + /// + /// 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::Fields.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P4"; + context.DisplayName = baseName + "P4"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace FileScopedNamespace +{ + partial struct FirstValidator + { + /// + /// 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::FileScopedNamespace.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P1"; + context.DisplayName = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace FunnyStrings +{ + partial struct FirstValidator + { + /// + /// 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::FunnyStrings.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A6); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace Generics +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __SecondModelValidator__ + { + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Generics.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P4"; + context.DisplayName = baseName + "P4"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace Generics +{ + partial class FirstValidator + { + /// + /// 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::Generics.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P1"; + context.DisplayName = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + if (options.P3 is not null) + { + builder.AddResult(global::Generics.__SecondModelValidator__.Validate(baseName + "P3", options.P3)); + } + + return builder.Build(); + } + } +} +namespace MultiModelValidator +{ + partial struct MultiValidator + { + /// + /// 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::MultiModelValidator.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P1"; + context.DisplayName = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + if (options.P2 is not null) + { + builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V4.Validate(baseName + "P2", options.P2)); + } + + return builder.Build(); + } + /// + /// 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::MultiModelValidator.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P3"; + context.DisplayName = baseName + "P3"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P3!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace Nested +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __ThirdModelValidator__ + { + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Nested.Container1.ThirdModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "ThirdModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P6"; + context.DisplayName = baseName + "P6"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P6!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace Nested +{ + partial record struct Container7 + { + partial record struct FifthValidator + { + /// + /// 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::Nested.Container1.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P5"; + context.DisplayName = baseName + "P5"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } + } +} +namespace Nested +{ + partial class Container2 + { + partial class Container3 + { + partial struct FirstValidator + { + /// + /// 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::Nested.Container1.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P1"; + context.DisplayName = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + if (options.P2 is not null) + { + builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V5.Validate(baseName + "P2", options.P2)); + } + + builder.AddResult(global::Nested.__ThirdModelValidator__.Validate(baseName + "P3", options.P3)); + + if (options.P4 is not null) + { + builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V6.Validate(baseName + "P4", options.P4)); + } + + return builder.Build(); + } + } + } + } +} +namespace Nested +{ + partial struct Container6 + { + partial struct FourthValidator + { + /// + /// 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::Nested.Container1.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P5"; + context.DisplayName = baseName + "P5"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } + } +} +namespace Nested +{ + partial class Container2 + { + partial class Container3 + { + partial struct SecondValidator + { + /// + /// 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::Nested.Container1.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P5"; + context.DisplayName = baseName + "P5"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } + } + } +} +namespace Nested +{ + partial record class Container4 + { + partial record class Container5 + { + partial struct ThirdValidator + { + /// + /// 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::Nested.Container1.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P5"; + context.DisplayName = baseName + "P5"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } + } + } +} +namespace RandomMembers +{ + partial class FirstValidator + { + /// + /// 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::RandomMembers.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P1"; + context.DisplayName = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace RecordTypes +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __ThirdModelValidator__ + { + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RecordTypes.ThirdModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "ThirdModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P6"; + context.DisplayName = baseName + "P6"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P6!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace RecordTypes +{ + partial record struct FirstValidator + { + /// + /// 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::RecordTypes.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P1"; + context.DisplayName = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + if (options.P2 is not null) + { + builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V7.Validate(baseName + "P2", options.P2)); + } + + if (options.P3 is not null) + { + builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V8.Validate(baseName + "P3", options.P3)); + } + + builder.AddResult(global::RecordTypes.__ThirdModelValidator__.Validate(baseName + "P4", options.P4)); + + return builder.Build(); + } + } +} +namespace RecordTypes +{ + partial record struct SecondValidator + { + /// + /// 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::RecordTypes.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P5"; + context.DisplayName = baseName + "P5"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace RecordTypes +{ + partial record class ThirdValidator + { + /// + /// 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::RecordTypes.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P5"; + context.DisplayName = baseName + "P5"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace RepeatedTypes +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __SecondModelValidator__ + { + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RepeatedTypes.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "P4"; + context.DisplayName = baseName + "P4"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + if (options.P4 is not null) + { + builder.AddResult(global::RepeatedTypes.__ThirdModelValidator__.Validate(baseName + "P4", options.P4)); + } + + return builder.Build(); + } + } +} +namespace RepeatedTypes +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __ThirdModelValidator__ + { + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RepeatedTypes.ThirdModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "ThirdModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P5"; + context.DisplayName = baseName + "P5"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace RepeatedTypes +{ + partial class FirstValidator + { + /// + /// 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::RepeatedTypes.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + if (options.P1 is not null) + { + builder.AddResult(global::RepeatedTypes.__SecondModelValidator__.Validate(baseName + "P1", options.P1)); + } + + context.MemberName = "P2"; + context.DisplayName = baseName + "P2"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P2!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + if (options.P2 is not null) + { + builder.AddResult(global::RepeatedTypes.__SecondModelValidator__.Validate(baseName + "P2", options.P2)); + } + + context.MemberName = "P3"; + context.DisplayName = baseName + "P3"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P3!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + if (options.P3 is not null) + { + builder.AddResult(global::RepeatedTypes.__ThirdModelValidator__.Validate(baseName + "P3", options.P3)); + } + + return builder.Build(); + } + } +} +namespace SelfValidation +{ + partial struct FirstValidator + { + /// + /// 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::SelfValidation.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + builder.AddResults(((global::System.ComponentModel.DataAnnotations.IValidatableObject)options).Validate(context)); + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __RangeAttributeModelDoubleValidator__ + { + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.RangeAttributeModelDouble options) + { + var baseName = (string.IsNullOrEmpty(name) ? "RangeAttributeModelDouble" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val"; + context.DisplayName = baseName + "Val"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A7); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __RequiredAttributeModelValidator__ + { + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.RequiredAttributeModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "RequiredAttributeModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val"; + context.DisplayName = baseName + "Val"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __TypeWithoutOptionsValidatorValidator__ + { + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.TypeWithoutOptionsValidator options) + { + var baseName = (string.IsNullOrEmpty(name) ? "TypeWithoutOptionsValidator" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val1"; + context.DisplayName = baseName + "Val1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + context.MemberName = "Val2"; + context.DisplayName = baseName + "Val2"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A8); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val2!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + if (options.YetAnotherComplexVal is not null) + { + builder.AddResult(global::TestClasses.OptionsValidation.__RangeAttributeModelDoubleValidator__.Validate(baseName + "YetAnotherComplexVal", options.YetAnotherComplexVal)); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class AttributePropertyModelValidator + { + /// + /// 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::TestClasses.OptionsValidation.AttributePropertyModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "AttributePropertyModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val1"; + context.DisplayName = baseName + "Val1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A9); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + context.MemberName = "Val2"; + context.DisplayName = baseName + "Val2"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A10); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val2!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class ComplexModelValidator + { + /// + /// 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::TestClasses.OptionsValidation.ComplexModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "ComplexModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options); + + if (options.ComplexVal is not null) + { + builder.AddResult(global::TestClasses.OptionsValidation.__RequiredAttributeModelValidator__.Validate(baseName + "ComplexVal", options.ComplexVal)); + } + + if (options.ValWithoutOptionsValidator is not null) + { + builder.AddResult(global::TestClasses.OptionsValidation.__TypeWithoutOptionsValidatorValidator__.Validate(baseName + "ValWithoutOptionsValidator", options.ValWithoutOptionsValidator)); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class CustomTypeCustomValidationAttributeModelValidator + { + /// + /// 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::TestClasses.OptionsValidation.CustomTypeCustomValidationAttributeModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "CustomTypeCustomValidationAttributeModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val"; + context.DisplayName = baseName + "Val"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A11); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class CustomValidationAttributeModelValidator + { + /// + /// 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::TestClasses.OptionsValidation.CustomValidationAttributeModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "CustomValidationAttributeModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val"; + context.DisplayName = baseName + "Val"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A12); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class DataTypeAttributeModelValidator + { + /// + /// 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::TestClasses.OptionsValidation.DataTypeAttributeModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "DataTypeAttributeModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val"; + context.DisplayName = baseName + "Val"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A13); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class DerivedModelValidator + { + /// + /// 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::TestClasses.OptionsValidation.DerivedModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "DerivedModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "DerivedVal"; + context.DisplayName = baseName + "DerivedVal"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.DerivedVal!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + context.MemberName = "VirtualValWithAttr"; + context.DisplayName = baseName + "VirtualValWithAttr"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.VirtualValWithAttr!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + context.MemberName = "Val"; + context.DisplayName = baseName + "Val"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class EmailAttributeModelValidator + { + /// + /// 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::TestClasses.OptionsValidation.EmailAttributeModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "EmailAttributeModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val"; + context.DisplayName = baseName + "Val"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A14); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class LeafModelValidator + { + /// + /// 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::TestClasses.OptionsValidation.LeafModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "LeafModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "VirtualValWithoutAttr"; + context.DisplayName = baseName + "VirtualValWithoutAttr"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.VirtualValWithoutAttr!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + context.MemberName = "DerivedVal"; + context.DisplayName = baseName + "DerivedVal"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.DerivedVal!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + context.MemberName = "Val"; + context.DisplayName = baseName + "Val"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class MultipleAttributeModelValidator + { + /// + /// 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::TestClasses.OptionsValidation.MultipleAttributeModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "MultipleAttributeModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "Val1"; + context.DisplayName = baseName + "Val1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A15); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + context.MemberName = "Val2"; + context.DisplayName = baseName + "Val2"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A16); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val2!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + context.MemberName = "Val3"; + context.DisplayName = baseName + "Val3"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A17); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val3!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + context.MemberName = "Val4"; + context.DisplayName = baseName + "Val4"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A18); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val4!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class RangeAttributeModelDateValidator + { + /// + /// 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::TestClasses.OptionsValidation.RangeAttributeModelDate options) + { + var baseName = (string.IsNullOrEmpty(name) ? "RangeAttributeModelDate" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val"; + context.DisplayName = baseName + "Val"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A19); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class RangeAttributeModelDoubleValidator + { + /// + /// 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::TestClasses.OptionsValidation.RangeAttributeModelDouble options) + { + var baseName = (string.IsNullOrEmpty(name) ? "RangeAttributeModelDouble" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val"; + context.DisplayName = baseName + "Val"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A7); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class RangeAttributeModelIntValidator + { + /// + /// 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::TestClasses.OptionsValidation.RangeAttributeModelInt options) + { + var baseName = (string.IsNullOrEmpty(name) ? "RangeAttributeModelInt" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val"; + context.DisplayName = baseName + "Val"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A16); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class RegularExpressionAttributeModelValidator + { + /// + /// 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::TestClasses.OptionsValidation.RegularExpressionAttributeModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "RegularExpressionAttributeModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val"; + context.DisplayName = baseName + "Val"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A20); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class RequiredAttributeModelValidator + { + /// + /// 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::TestClasses.OptionsValidation.RequiredAttributeModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "RequiredAttributeModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val"; + context.DisplayName = baseName + "Val"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace ValueTypes +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __SecondModelValidator__ + { + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::ValueTypes.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P4"; + context.DisplayName = baseName + "P4"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace ValueTypes +{ + partial struct FirstValidator + { + /// + /// 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::ValueTypes.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P1"; + context.DisplayName = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + if (options.P2 is not null) + { + builder.AddResult(global::ValueTypes.__SecondModelValidator__.Validate(baseName + "P2", options.P2.Value)); + } + + builder.AddResult(global::ValueTypes.__SecondModelValidator__.Validate(baseName + "P3", options.P3)); + + if (options.P4 is not null) + { + builder.AddResult(global::ValueTypes.__SecondModelValidator__.Validate(baseName + "P4", options.P4.Value)); + } + + return builder.Build(); + } + } +} +namespace __OptionValidationStaticInstances +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal static class __Attributes + { + internal static readonly global::System.ComponentModel.DataAnnotations.RequiredAttribute A1 = new global::System.ComponentModel.DataAnnotations.RequiredAttribute(); + + internal static readonly global::System.ComponentModel.DataAnnotations.MinLengthAttribute A2 = new global::System.ComponentModel.DataAnnotations.MinLengthAttribute( + (int)5); + + internal static readonly global::CustomAttr.CustomAttribute A3 = new global::CustomAttr.CustomAttribute( + 'A', + true, + null); + + internal static readonly global::CustomAttr.CustomAttribute A4 = new global::CustomAttr.CustomAttribute( + 'A', + false, + "X"); + + internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A5 = new global::System.ComponentModel.DataAnnotations.RangeAttribute( + (int)0, + (int)10); + + internal static readonly global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute A6 = new global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute( + "\"\r\n\\\\"); + + internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A7 = new global::System.ComponentModel.DataAnnotations.RangeAttribute( + (double)0.5, + (double)0.9); + + internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A8 = new global::System.ComponentModel.DataAnnotations.RangeAttribute( + typeof(global::System.DateTime), + "1/2/2004", + "3/4/2004"); + + internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A9 = new global::System.ComponentModel.DataAnnotations.RangeAttribute( + (int)1, + (int)3) + { + ErrorMessage = "ErrorMessage" + }; + + internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A10 = new global::System.ComponentModel.DataAnnotations.RangeAttribute( + (int)1, + (int)3) + { + ErrorMessageResourceName = "ErrorMessageResourceName", + ErrorMessageResourceType = typeof(global::System.SR) + }; + + internal static readonly global::System.ComponentModel.DataAnnotations.CustomValidationAttribute A11 = new global::System.ComponentModel.DataAnnotations.CustomValidationAttribute( + typeof(global::TestClasses.OptionsValidation.CustomTypeCustomValidationTest), + "TestMethod"); + + internal static readonly global::System.ComponentModel.DataAnnotations.CustomValidationAttribute A12 = new global::System.ComponentModel.DataAnnotations.CustomValidationAttribute( + typeof(global::TestClasses.OptionsValidation.CustomValidationTest), + "TestMethod"); + + internal static readonly global::System.ComponentModel.DataAnnotations.DataTypeAttribute A13 = new global::System.ComponentModel.DataAnnotations.DataTypeAttribute( + (global::System.ComponentModel.DataAnnotations.DataType)7); + + internal static readonly global::System.ComponentModel.DataAnnotations.EmailAddressAttribute A14 = new global::System.ComponentModel.DataAnnotations.EmailAddressAttribute(); + + internal static readonly global::System.ComponentModel.DataAnnotations.DataTypeAttribute A15 = new global::System.ComponentModel.DataAnnotations.DataTypeAttribute( + (global::System.ComponentModel.DataAnnotations.DataType)11); + + internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A16 = new global::System.ComponentModel.DataAnnotations.RangeAttribute( + (int)1, + (int)3); + + internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A17 = new global::System.ComponentModel.DataAnnotations.RangeAttribute( + (int)3, + (int)5); + + internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A18 = new global::System.ComponentModel.DataAnnotations.RangeAttribute( + (int)5, + (int)9); + + internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A19 = new global::System.ComponentModel.DataAnnotations.RangeAttribute( + typeof(global::System.DateTime), + "1/2/2004", + "3/4/2004") + { + ParseLimitsInInvariantCulture = true + }; + + internal static readonly global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute A20 = new global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute( + "\\s"); + } +} +namespace __OptionValidationStaticInstances +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal static class __Validators + { + internal static readonly global::SecondValidatorNoNamespace V1 = new global::SecondValidatorNoNamespace(); + + internal static readonly global::Enumeration.SecondValidator V2 = new global::Enumeration.SecondValidator(); + + internal static readonly global::Fields.SecondValidator V3 = new global::Fields.SecondValidator(); + + internal static readonly global::MultiModelValidator.MultiValidator V4 = new global::MultiModelValidator.MultiValidator(); + + internal static readonly global::Nested.Container2.Container3.SecondValidator V5 = new global::Nested.Container2.Container3.SecondValidator(); + + internal static readonly global::Nested.Container4.Container5.ThirdValidator V6 = new global::Nested.Container4.Container5.ThirdValidator(); + + internal static readonly global::RecordTypes.SecondValidator V7 = new global::RecordTypes.SecondValidator(); + + internal static readonly global::RecordTypes.ThirdValidator V8 = new global::RecordTypes.ThirdValidator(); + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetFX/Validators.g.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetFX/Validators.g.cs new file mode 100644 index 0000000000000..daa65114c056a --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Baselines/NetFX/Validators.g.cs @@ -0,0 +1,2072 @@ + + // + #nullable enable + #pragma warning disable CS1591 // Compensate for https://github.com/dotnet/roslyn/issues/54103 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] +internal sealed partial class __ThirdModelNoNamespaceValidator__ +{ + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::ThirdModelNoNamespace options) + { + var baseName = (string.IsNullOrEmpty(name) ? "ThirdModelNoNamespace" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P5"; + context.DisplayName = baseName + "P5"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } +} +partial class FirstValidatorNoNamespace +{ + /// + /// 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::FirstModelNoNamespace options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModelNoNamespace" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P1"; + context.DisplayName = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + if (options.P2 is not null) + { + builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V1.Validate(baseName + "P2", options.P2)); + } + + if (options.P3 is not null) + { + builder.AddResult(global::__ThirdModelNoNamespaceValidator__.Validate(baseName + "P3", options.P3)); + } + + return builder.Build(); + } +} +partial class SecondValidatorNoNamespace +{ + /// + /// 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::SecondModelNoNamespace options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModelNoNamespace" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P4"; + context.DisplayName = baseName + "P4"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } +} +namespace CustomAttr +{ + partial class FirstValidator + { + /// + /// 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::CustomAttr.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A3); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + context.MemberName = "P2"; + context.DisplayName = baseName + "P2"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A4); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P2!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace Enumeration +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __SecondModelValidator__ + { + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Enumeration.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P6"; + context.DisplayName = baseName + "P6"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P6!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace Enumeration +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __ThirdModelValidator__ + { + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Enumeration.ThirdModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "ThirdModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Value"; + context.DisplayName = baseName + "Value"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A5); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Value!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace Enumeration +{ + partial struct FirstValidator + { + /// + /// 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::Enumeration.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options); + + if (options.P1 is not null) + { + var count = 0; + foreach (var o in options.P1) + { + if (o is not null) + { + builder.AddResult(global::Enumeration.__SecondModelValidator__.Validate(baseName + $"P1[{count}]", o)); + } + else + { + builder.AddError(baseName + $"P1[{count}] is null"); + } + count++; + } + } + + if (options.P2 is not null) + { + var count = 0; + foreach (var o in options.P2) + { + if (o is not null) + { + builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V2.Validate(baseName + $"P2[{count}]", o)); + } + else + { + builder.AddError(baseName + $"P2[{count}] is null"); + } + count++; + } + } + + if (options.P3 is not null) + { + var count = 0; + foreach (var o in options.P3) + { + if (o is not null) + { + builder.AddResult(global::Enumeration.__SecondModelValidator__.Validate(baseName + $"P3[{count}]", o)); + } + count++; + } + } + + if (options.P4 is not null) + { + var count = 0; + foreach (var o in options.P4) + { + builder.AddResult(global::Enumeration.__ThirdModelValidator__.Validate(baseName + $"P4[{count++}]", o)); + } + } + + if (options.P5 is not null) + { + var count = 0; + foreach (var o in options.P5) + { + if (o is not null) + { + builder.AddResult(global::Enumeration.__ThirdModelValidator__.Validate(baseName + $"P5[{count}]", o.Value)); + } + count++; + } + } + + if (options.P51 is not null) + { + var count = 0; + foreach (var o in options.P51) + { + if (o is not null) + { + builder.AddResult(global::Enumeration.__ThirdModelValidator__.Validate(baseName + $"P51[{count}]", o.Value)); + } + count++; + } + } + + if (options.P6 is not null) + { + var count = 0; + foreach (var o in options.P6.Value) + { + if (o is not null) + { + builder.AddResult(global::Enumeration.__SecondModelValidator__.Validate(baseName + $"P6[{count}]", o)); + } + else + { + builder.AddError(baseName + $"P6[{count}] is null"); + } + count++; + } + } + + { + var count = 0; + foreach (var o in options.P7) + { + if (o is not null) + { + builder.AddResult(global::Enumeration.__SecondModelValidator__.Validate(baseName + $"P7[{count}]", o)); + } + else + { + builder.AddError(baseName + $"P7[{count}] is null"); + } + count++; + } + } + + if (options.P8 is not null) + { + var count = 0; + foreach (var o in options.P8.Value) + { + if (o is not null) + { + builder.AddResult(global::Enumeration.__SecondModelValidator__.Validate(baseName + $"P8[{count}]", o)); + } + else + { + builder.AddError(baseName + $"P8[{count}] is null"); + } + count++; + } + } + + return builder.Build(); + } + } +} +namespace Enumeration +{ + partial struct SecondValidator + { + /// + /// 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::Enumeration.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P6"; + context.DisplayName = baseName + "P6"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P6!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace Fields +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __ThirdModelValidator__ + { + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Fields.ThirdModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "ThirdModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P5"; + context.DisplayName = baseName + "P5"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace Fields +{ + partial struct FirstValidator + { + /// + /// 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::Fields.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P1"; + context.DisplayName = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + if (options.P2 is not null) + { + builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V3.Validate(baseName + "P2", options.P2)); + } + + builder.AddResult(global::Fields.__ThirdModelValidator__.Validate(baseName + "P3", options.P3)); + + return builder.Build(); + } + } +} +namespace Fields +{ + partial struct SecondValidator + { + /// + /// 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::Fields.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P4"; + context.DisplayName = baseName + "P4"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace FileScopedNamespace +{ + partial struct FirstValidator + { + /// + /// 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::FileScopedNamespace.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P1"; + context.DisplayName = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace FunnyStrings +{ + partial struct FirstValidator + { + /// + /// 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::FunnyStrings.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A6); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace Generics +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __SecondModelValidator__ + { + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Generics.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P4"; + context.DisplayName = baseName + "P4"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace Generics +{ + partial class FirstValidator + { + /// + /// 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::Generics.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P1"; + context.DisplayName = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + if (options.P3 is not null) + { + builder.AddResult(global::Generics.__SecondModelValidator__.Validate(baseName + "P3", options.P3)); + } + + return builder.Build(); + } + } +} +namespace MultiModelValidator +{ + partial struct MultiValidator + { + /// + /// 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::MultiModelValidator.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P1"; + context.DisplayName = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + if (options.P2 is not null) + { + builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V4.Validate(baseName + "P2", options.P2)); + } + + return builder.Build(); + } + /// + /// 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::MultiModelValidator.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P3"; + context.DisplayName = baseName + "P3"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P3!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace Nested +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __ThirdModelValidator__ + { + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::Nested.Container1.ThirdModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "ThirdModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P6"; + context.DisplayName = baseName + "P6"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P6!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace Nested +{ + partial record struct Container7 + { + partial record struct FifthValidator + { + /// + /// 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::Nested.Container1.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P5"; + context.DisplayName = baseName + "P5"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } + } +} +namespace Nested +{ + partial class Container2 + { + partial class Container3 + { + partial struct FirstValidator + { + /// + /// 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::Nested.Container1.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P1"; + context.DisplayName = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + if (options.P2 is not null) + { + builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V5.Validate(baseName + "P2", options.P2)); + } + + builder.AddResult(global::Nested.__ThirdModelValidator__.Validate(baseName + "P3", options.P3)); + + if (options.P4 is not null) + { + builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V6.Validate(baseName + "P4", options.P4)); + } + + return builder.Build(); + } + } + } + } +} +namespace Nested +{ + partial struct Container6 + { + partial struct FourthValidator + { + /// + /// 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::Nested.Container1.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P5"; + context.DisplayName = baseName + "P5"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } + } +} +namespace Nested +{ + partial class Container2 + { + partial class Container3 + { + partial struct SecondValidator + { + /// + /// 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::Nested.Container1.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P5"; + context.DisplayName = baseName + "P5"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } + } + } +} +namespace Nested +{ + partial record class Container4 + { + partial record class Container5 + { + partial struct ThirdValidator + { + /// + /// 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::Nested.Container1.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P5"; + context.DisplayName = baseName + "P5"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } + } + } +} +namespace RandomMembers +{ + partial class FirstValidator + { + /// + /// 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::RandomMembers.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P1"; + context.DisplayName = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace RecordTypes +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __ThirdModelValidator__ + { + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RecordTypes.ThirdModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "ThirdModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P6"; + context.DisplayName = baseName + "P6"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P6!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace RecordTypes +{ + partial record struct FirstValidator + { + /// + /// 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::RecordTypes.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P1"; + context.DisplayName = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + if (options.P2 is not null) + { + builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V7.Validate(baseName + "P2", options.P2)); + } + + if (options.P3 is not null) + { + builder.AddResult(global::__OptionValidationStaticInstances.__Validators.V8.Validate(baseName + "P3", options.P3)); + } + + builder.AddResult(global::RecordTypes.__ThirdModelValidator__.Validate(baseName + "P4", options.P4)); + + return builder.Build(); + } + } +} +namespace RecordTypes +{ + partial record struct SecondValidator + { + /// + /// 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::RecordTypes.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P5"; + context.DisplayName = baseName + "P5"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace RecordTypes +{ + partial record class ThirdValidator + { + /// + /// 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::RecordTypes.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P5"; + context.DisplayName = baseName + "P5"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace RepeatedTypes +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __SecondModelValidator__ + { + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RepeatedTypes.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "P4"; + context.DisplayName = baseName + "P4"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + if (options.P4 is not null) + { + builder.AddResult(global::RepeatedTypes.__ThirdModelValidator__.Validate(baseName + "P4", options.P4)); + } + + return builder.Build(); + } + } +} +namespace RepeatedTypes +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __ThirdModelValidator__ + { + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::RepeatedTypes.ThirdModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "ThirdModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P5"; + context.DisplayName = baseName + "P5"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P5!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace RepeatedTypes +{ + partial class FirstValidator + { + /// + /// 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::RepeatedTypes.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + if (options.P1 is not null) + { + builder.AddResult(global::RepeatedTypes.__SecondModelValidator__.Validate(baseName + "P1", options.P1)); + } + + context.MemberName = "P2"; + context.DisplayName = baseName + "P2"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P2!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + if (options.P2 is not null) + { + builder.AddResult(global::RepeatedTypes.__SecondModelValidator__.Validate(baseName + "P2", options.P2)); + } + + context.MemberName = "P3"; + context.DisplayName = baseName + "P3"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P3!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + if (options.P3 is not null) + { + builder.AddResult(global::RepeatedTypes.__ThirdModelValidator__.Validate(baseName + "P3", options.P3)); + } + + return builder.Build(); + } + } +} +namespace SelfValidation +{ + partial struct FirstValidator + { + /// + /// 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::SelfValidation.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + builder.AddResults(((global::System.ComponentModel.DataAnnotations.IValidatableObject)options).Validate(context)); + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __RangeAttributeModelDoubleValidator__ + { + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.RangeAttributeModelDouble options) + { + var baseName = (string.IsNullOrEmpty(name) ? "RangeAttributeModelDouble" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val"; + context.DisplayName = baseName + "Val"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A7); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __RequiredAttributeModelValidator__ + { + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.RequiredAttributeModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "RequiredAttributeModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val"; + context.DisplayName = baseName + "Val"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __TypeWithoutOptionsValidatorValidator__ + { + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::TestClasses.OptionsValidation.TypeWithoutOptionsValidator options) + { + var baseName = (string.IsNullOrEmpty(name) ? "TypeWithoutOptionsValidator" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val1"; + context.DisplayName = baseName + "Val1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + context.MemberName = "Val2"; + context.DisplayName = baseName + "Val2"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A8); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val2!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + if (options.YetAnotherComplexVal is not null) + { + builder.AddResult(global::TestClasses.OptionsValidation.__RangeAttributeModelDoubleValidator__.Validate(baseName + "YetAnotherComplexVal", options.YetAnotherComplexVal)); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class AttributePropertyModelValidator + { + /// + /// 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::TestClasses.OptionsValidation.AttributePropertyModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "AttributePropertyModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val1"; + context.DisplayName = baseName + "Val1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A9); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + context.MemberName = "Val2"; + context.DisplayName = baseName + "Val2"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A10); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val2!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class ComplexModelValidator + { + /// + /// 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::TestClasses.OptionsValidation.ComplexModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "ComplexModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + var context = new global::System.ComponentModel.DataAnnotations.ValidationContext(options); + + if (options.ComplexVal is not null) + { + builder.AddResult(global::TestClasses.OptionsValidation.__RequiredAttributeModelValidator__.Validate(baseName + "ComplexVal", options.ComplexVal)); + } + + if (options.ValWithoutOptionsValidator is not null) + { + builder.AddResult(global::TestClasses.OptionsValidation.__TypeWithoutOptionsValidatorValidator__.Validate(baseName + "ValWithoutOptionsValidator", options.ValWithoutOptionsValidator)); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class CustomTypeCustomValidationAttributeModelValidator + { + /// + /// 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::TestClasses.OptionsValidation.CustomTypeCustomValidationAttributeModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "CustomTypeCustomValidationAttributeModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val"; + context.DisplayName = baseName + "Val"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A11); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class CustomValidationAttributeModelValidator + { + /// + /// 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::TestClasses.OptionsValidation.CustomValidationAttributeModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "CustomValidationAttributeModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val"; + context.DisplayName = baseName + "Val"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A12); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class DataTypeAttributeModelValidator + { + /// + /// 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::TestClasses.OptionsValidation.DataTypeAttributeModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "DataTypeAttributeModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val"; + context.DisplayName = baseName + "Val"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A13); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class DerivedModelValidator + { + /// + /// 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::TestClasses.OptionsValidation.DerivedModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "DerivedModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "DerivedVal"; + context.DisplayName = baseName + "DerivedVal"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.DerivedVal!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + context.MemberName = "VirtualValWithAttr"; + context.DisplayName = baseName + "VirtualValWithAttr"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.VirtualValWithAttr!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + context.MemberName = "Val"; + context.DisplayName = baseName + "Val"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class EmailAttributeModelValidator + { + /// + /// 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::TestClasses.OptionsValidation.EmailAttributeModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "EmailAttributeModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val"; + context.DisplayName = baseName + "Val"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A14); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class LeafModelValidator + { + /// + /// 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::TestClasses.OptionsValidation.LeafModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "LeafModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "VirtualValWithoutAttr"; + context.DisplayName = baseName + "VirtualValWithoutAttr"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.VirtualValWithoutAttr!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + context.MemberName = "DerivedVal"; + context.DisplayName = baseName + "DerivedVal"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.DerivedVal!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + context.MemberName = "Val"; + context.DisplayName = baseName + "Val"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class MultipleAttributeModelValidator + { + /// + /// 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::TestClasses.OptionsValidation.MultipleAttributeModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "MultipleAttributeModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "Val1"; + context.DisplayName = baseName + "Val1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A15); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + context.MemberName = "Val2"; + context.DisplayName = baseName + "Val2"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A16); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val2!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + context.MemberName = "Val3"; + context.DisplayName = baseName + "Val3"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A17); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val3!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + context.MemberName = "Val4"; + context.DisplayName = baseName + "Val4"; + validationResults.Clear(); + validationAttributes.Clear(); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A18); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val4!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class RangeAttributeModelDateValidator + { + /// + /// 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::TestClasses.OptionsValidation.RangeAttributeModelDate options) + { + var baseName = (string.IsNullOrEmpty(name) ? "RangeAttributeModelDate" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val"; + context.DisplayName = baseName + "Val"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A8); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class RangeAttributeModelDoubleValidator + { + /// + /// 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::TestClasses.OptionsValidation.RangeAttributeModelDouble options) + { + var baseName = (string.IsNullOrEmpty(name) ? "RangeAttributeModelDouble" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val"; + context.DisplayName = baseName + "Val"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A7); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class RangeAttributeModelIntValidator + { + /// + /// 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::TestClasses.OptionsValidation.RangeAttributeModelInt options) + { + var baseName = (string.IsNullOrEmpty(name) ? "RangeAttributeModelInt" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val"; + context.DisplayName = baseName + "Val"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A16); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class RegularExpressionAttributeModelValidator + { + /// + /// 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::TestClasses.OptionsValidation.RegularExpressionAttributeModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "RegularExpressionAttributeModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val"; + context.DisplayName = baseName + "Val"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A19); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace TestClasses.OptionsValidation +{ + partial class RequiredAttributeModelValidator + { + /// + /// 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::TestClasses.OptionsValidation.RequiredAttributeModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "RequiredAttributeModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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 = "Val"; + context.DisplayName = baseName + "Val"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.Val!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace ValueTypes +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal sealed partial class __SecondModelValidator__ + { + /// + /// 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 static global::Microsoft.Extensions.Options.ValidateOptionsResult Validate(string? name, global::ValueTypes.SecondModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "SecondModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P4"; + context.DisplayName = baseName + "P4"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P4!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + return builder.Build(); + } + } +} +namespace ValueTypes +{ + partial struct FirstValidator + { + /// + /// 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::ValueTypes.FirstModel options) + { + var baseName = (string.IsNullOrEmpty(name) ? "FirstModel" : name) + "."; + var builder = new global::Microsoft.Extensions.Options.ValidateOptionsResultBuilder(); + 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(2); + + context.MemberName = "P1"; + context.DisplayName = baseName + "P1"; + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A1); + validationAttributes.Add(global::__OptionValidationStaticInstances.__Attributes.A2); + if (!global::System.ComponentModel.DataAnnotations.Validator.TryValidateValue(options.P1!, context, validationResults, validationAttributes)) + { + builder.AddResults(validationResults); + } + + if (options.P2 is not null) + { + builder.AddResult(global::ValueTypes.__SecondModelValidator__.Validate(baseName + "P2", options.P2.Value)); + } + + builder.AddResult(global::ValueTypes.__SecondModelValidator__.Validate(baseName + "P3", options.P3)); + + if (options.P4 is not null) + { + builder.AddResult(global::ValueTypes.__SecondModelValidator__.Validate(baseName + "P4", options.P4.Value)); + } + + return builder.Build(); + } + } +} +namespace __OptionValidationStaticInstances +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal static class __Attributes + { + internal static readonly global::System.ComponentModel.DataAnnotations.RequiredAttribute A1 = new global::System.ComponentModel.DataAnnotations.RequiredAttribute(); + + internal static readonly global::System.ComponentModel.DataAnnotations.MinLengthAttribute A2 = new global::System.ComponentModel.DataAnnotations.MinLengthAttribute( + (int)5); + + internal static readonly global::CustomAttr.CustomAttribute A3 = new global::CustomAttr.CustomAttribute( + 'A', + true, + null); + + internal static readonly global::CustomAttr.CustomAttribute A4 = new global::CustomAttr.CustomAttribute( + 'A', + false, + "X"); + + internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A5 = new global::System.ComponentModel.DataAnnotations.RangeAttribute( + (int)0, + (int)10); + + internal static readonly global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute A6 = new global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute( + "\"\r\n\\\\"); + + internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A7 = new global::System.ComponentModel.DataAnnotations.RangeAttribute( + (double)0.5, + (double)0.9); + + internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A8 = new global::System.ComponentModel.DataAnnotations.RangeAttribute( + typeof(global::System.DateTime), + "1/2/2004", + "3/4/2004"); + + internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A9 = new global::System.ComponentModel.DataAnnotations.RangeAttribute( + (int)1, + (int)3) + { + ErrorMessage = "ErrorMessage" + }; + + internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A10 = new global::System.ComponentModel.DataAnnotations.RangeAttribute( + (int)1, + (int)3) + { + ErrorMessageResourceName = "ErrorMessageResourceName", + ErrorMessageResourceType = typeof(global::System.SR) + }; + + internal static readonly global::System.ComponentModel.DataAnnotations.CustomValidationAttribute A11 = new global::System.ComponentModel.DataAnnotations.CustomValidationAttribute( + typeof(global::TestClasses.OptionsValidation.CustomTypeCustomValidationTest), + "TestMethod"); + + internal static readonly global::System.ComponentModel.DataAnnotations.CustomValidationAttribute A12 = new global::System.ComponentModel.DataAnnotations.CustomValidationAttribute( + typeof(global::TestClasses.OptionsValidation.CustomValidationTest), + "TestMethod"); + + internal static readonly global::System.ComponentModel.DataAnnotations.DataTypeAttribute A13 = new global::System.ComponentModel.DataAnnotations.DataTypeAttribute( + (global::System.ComponentModel.DataAnnotations.DataType)7); + + internal static readonly global::System.ComponentModel.DataAnnotations.EmailAddressAttribute A14 = new global::System.ComponentModel.DataAnnotations.EmailAddressAttribute(); + + internal static readonly global::System.ComponentModel.DataAnnotations.DataTypeAttribute A15 = new global::System.ComponentModel.DataAnnotations.DataTypeAttribute( + (global::System.ComponentModel.DataAnnotations.DataType)11); + + internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A16 = new global::System.ComponentModel.DataAnnotations.RangeAttribute( + (int)1, + (int)3); + + internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A17 = new global::System.ComponentModel.DataAnnotations.RangeAttribute( + (int)3, + (int)5); + + internal static readonly global::System.ComponentModel.DataAnnotations.RangeAttribute A18 = new global::System.ComponentModel.DataAnnotations.RangeAttribute( + (int)5, + (int)9); + + internal static readonly global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute A19 = new global::System.ComponentModel.DataAnnotations.RegularExpressionAttribute( + "\\s"); + } +} +namespace __OptionValidationStaticInstances +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Options.SourceGeneration", "42.42.42.42")] + internal static class __Validators + { + internal static readonly global::SecondValidatorNoNamespace V1 = new global::SecondValidatorNoNamespace(); + + internal static readonly global::Enumeration.SecondValidator V2 = new global::Enumeration.SecondValidator(); + + internal static readonly global::Fields.SecondValidator V3 = new global::Fields.SecondValidator(); + + internal static readonly global::MultiModelValidator.MultiValidator V4 = new global::MultiModelValidator.MultiValidator(); + + internal static readonly global::Nested.Container2.Container3.SecondValidator V5 = new global::Nested.Container2.Container3.SecondValidator(); + + internal static readonly global::Nested.Container4.Container5.ThirdValidator V6 = new global::Nested.Container4.Container5.ThirdValidator(); + + internal static readonly global::RecordTypes.SecondValidator V7 = new global::RecordTypes.SecondValidator(); + + internal static readonly global::RecordTypes.ThirdValidator V8 = new global::RecordTypes.ThirdValidator(); + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/EmitterTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/EmitterTests.cs new file mode 100644 index 0000000000000..fe3e007e0464d --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/EmitterTests.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.IO; +using System.Reflection; +using System.Threading.Tasks; +using SourceGenerators.Tests; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Options.Generators; +using Microsoft.Shared.Data.Validation; +using Xunit; + +namespace Microsoft.Gen.OptionsValidation.Test; + +public class EmitterTests +{ + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotBrowser))] + public async Task TestEmitter() + { + var sources = new List(); +#pragma warning disable RS1035 // To allow using the File IO APIs inside the analyzer test + foreach (var file in Directory.GetFiles("TestClasses")) + { +#if NETCOREAPP3_1_OR_GREATER + sources.Add("#define NETCOREAPP3_1_OR_GREATER\n" + File.ReadAllText(file)); +#else + sources.Add(File.ReadAllText(file)); +#endif + } + + var (d, r) = await RoslynTestUtils.RunGenerator( + new Generator(), + new[] + { + Assembly.GetAssembly(typeof(RequiredAttribute))!, + Assembly.GetAssembly(typeof(TimeSpanAttribute))!, + Assembly.GetAssembly(typeof(OptionsValidatorAttribute))!, + Assembly.GetAssembly(typeof(IValidateOptions))!, + }, + sources) + .ConfigureAwait(false); + + Assert.Empty(d); + _ = Assert.Single(r); + +#if NETCOREAPP3_1_OR_GREATER + string baseline = File.ReadAllText(@"Baselines/NetCoreApp/Validators.g.cs"); +#else + string baseline = File.ReadAllText(@"Baselines/NetFX/Validators.g.cs"); +#endif + + string result = r[0].SourceText.ToString(); + Assert.Equal(baseline, result); +#pragma warning restore RS1035 + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Extensions/EmptyReadOnlyList.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Extensions/EmptyReadOnlyList.cs new file mode 100644 index 0000000000000..cf0d931284d4b --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Extensions/EmptyReadOnlyList.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections; +using System.Collections.Generic; + +#pragma warning disable CA1716 +namespace Microsoft.Shared.Collections; +#pragma warning restore CA1716 + +#if !SHARED_PROJECT +[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] +#endif + +[System.Diagnostics.CodeAnalysis.SuppressMessage("Design", "CA1001:Types that own disposable fields should be disposable", Justification = "Static field, lifetime matches the process")] +internal sealed class EmptyReadOnlyList : IReadOnlyList, ICollection +{ + public static readonly EmptyReadOnlyList Instance = new(); + private readonly Enumerator _enumerator = new(); + + public IEnumerator GetEnumerator() => _enumerator; + IEnumerator IEnumerable.GetEnumerator() => _enumerator; + public int Count => 0; + public T this[int index] => throw new ArgumentOutOfRangeException(nameof(index)); + + void ICollection.CopyTo(T[] array, int arrayIndex) + { + // nop + } + + bool ICollection.Contains(T item) => false; + bool ICollection.IsReadOnly => true; + void ICollection.Add(T item) => throw new NotSupportedException(); + bool ICollection.Remove(T item) => false; + + void ICollection.Clear() + { + // nop + } + + internal sealed class Enumerator : IEnumerator + { + public void Dispose() + { + // nop + } + + public void Reset() + { + // nop + } + + public bool MoveNext() => false; + public T Current => throw new InvalidOperationException(); + object IEnumerator.Current => throw new InvalidOperationException(); + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Extensions/EmptyReadonlyDictionary.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Extensions/EmptyReadonlyDictionary.cs new file mode 100644 index 0000000000000..99ef907611280 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Extensions/EmptyReadonlyDictionary.cs @@ -0,0 +1,63 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections; +using System.Collections.Generic; + +#pragma warning disable CA1716 +namespace Microsoft.Shared.Collections; +#pragma warning restore CA1716 + +#if !SHARED_PROJECT +[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] +#endif + +internal sealed class EmptyReadOnlyDictionary : IReadOnlyDictionary, IDictionary + where TKey : notnull +{ + public static readonly EmptyReadOnlyDictionary Instance = new(); + + public int Count => 0; + public TValue this[TKey key] => throw new KeyNotFoundException(); + public bool ContainsKey(TKey key) => false; + public IEnumerable Keys => EmptyReadOnlyList.Instance; + public IEnumerable Values => EmptyReadOnlyList.Instance; + + public IEnumerator> GetEnumerator() => EmptyReadOnlyList>.Instance.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + ICollection IDictionary.Keys => Array.Empty(); + ICollection IDictionary.Values => Array.Empty(); + bool ICollection>.IsReadOnly => true; + TValue IDictionary.this[TKey key] + { + get => throw new KeyNotFoundException(); + set => throw new NotSupportedException(); + } + + public bool TryGetValue(TKey key, out TValue value) + { +#pragma warning disable CS8601 // The recommended implementation: https://docs.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2.trygetvalue + value = default; +#pragma warning restore + + return false; + } + + void ICollection>.Clear() + { + // nop + } + + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + { + // nop + } + + void IDictionary.Add(TKey key, TValue value) => throw new NotSupportedException(); + bool IDictionary.Remove(TKey key) => false; + void ICollection>.Add(KeyValuePair item) => throw new NotSupportedException(); + bool ICollection>.Contains(KeyValuePair item) => false; + bool ICollection>.Remove(KeyValuePair item) => false; +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Extensions/TimeSpanAttribute.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Extensions/TimeSpanAttribute.cs new file mode 100644 index 0000000000000..e1e7639fc6af0 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Extensions/TimeSpanAttribute.cs @@ -0,0 +1,160 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +// using Microsoft.Shared.Diagnostics; + +#pragma warning disable CA1716 +namespace Microsoft.Shared.Data.Validation; +#pragma warning restore CA1716 + +/// +/// Provides boundary validation for . +/// +#if !SHARED_PROJECT +[ExcludeFromCodeCoverage] +#endif + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] +[SuppressMessage("Design", "CA1019:Define accessors for attribute arguments", Justification = "Indirectly we are.")] +internal sealed class TimeSpanAttribute : ValidationAttribute +{ + /// + /// Gets the lower bound for time span. + /// + public TimeSpan Minimum => _minMs.HasValue ? TimeSpan.FromMilliseconds((double)_minMs) : TimeSpan.Parse(_min!, CultureInfo.InvariantCulture); + + /// + /// Gets the upper bound for time span. + /// + public TimeSpan? Maximum + { + get + { + if (_maxMs.HasValue) + { + return TimeSpan.FromMilliseconds((double)_maxMs); + } + else + { + return _max == null ? null : TimeSpan.Parse(_max, CultureInfo.InvariantCulture); + } + } + } + + /// + /// Gets or sets a value indicating whether the time span validation should exclude the minimum and maximum values. + /// + /// + /// By default the property is set to false. + /// + public bool Exclusive { get; set; } + + private readonly int? _minMs; + private readonly int? _maxMs; + private readonly string? _min; + private readonly string? _max; + + /// + /// Initializes a new instance of the class. + /// + /// Minimum in milliseconds. + public TimeSpanAttribute(int minMs) + { + _minMs = minMs; + _maxMs = null; + } + + /// + /// Initializes a new instance of the class. + /// + /// Minimum in milliseconds. + /// Maximum in milliseconds. + public TimeSpanAttribute(int minMs, int maxMs) + { + _minMs = minMs; + _maxMs = maxMs; + } + + /// + /// Initializes a new instance of the class. + /// + /// Minimum represented as time span string. + public TimeSpanAttribute(string min) + { + _ = ThrowHelper.IfNullOrWhitespace(min); + + _min = min; + _max = null; + } + + /// + /// Initializes a new instance of the class. + /// + /// Minimum represented as time span string. + /// Maximum represented as time span string. + public TimeSpanAttribute(string min, string max) + { + _ = ThrowHelper.IfNullOrWhitespace(min); + _ = ThrowHelper.IfNullOrWhitespace(max); + + _min = min; + _max = max; + } + + /// + /// Validates that a given value represents an in-range TimeSpan value. + /// + /// The value to validate. + /// Additional context for this validation. + /// A value indicating success or failure. + protected override ValidationResult IsValid(object? value, ValidationContext? validationContext) + { + var min = Minimum; + var max = Maximum; + + if (min >= max) + { + throw new InvalidOperationException($"{nameof(TimeSpanAttribute)} requires that the minimum value be less than the maximum value (see field {validationContext.GetDisplayName()})"); + } + + if (value == null) + { + // use the [Required] attribute to force presence + return ValidationResult.Success!; + } + + if (value is TimeSpan ts) + { + if (Exclusive && ts <= min) + { + return new ValidationResult($"The field {validationContext.GetDisplayName()} must be > to {min}.", validationContext.GetMemberName()); + } + + if (ts < min) + { + return new ValidationResult($"The field {validationContext.GetDisplayName()} must be >= to {min}.", validationContext.GetMemberName()); + } + + if (max.HasValue) + { + if (Exclusive && ts >= max.Value) + { + return new ValidationResult($"The field {validationContext.GetDisplayName()} must be < to {max}.", validationContext.GetMemberName()); + } + + if (ts > max.Value) + { + return new ValidationResult($"The field {validationContext.GetDisplayName()} must be <= to {max}.", validationContext.GetMemberName()); + } + } + + return ValidationResult.Success!; + } + + throw new InvalidOperationException($"{nameof(TimeSpanAttribute)} can only be used with fields of type TimeSpan (see field {validationContext.GetDisplayName()})"); + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Extensions/ValidationContextExtensions.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Extensions/ValidationContextExtensions.cs new file mode 100644 index 0000000000000..45d3365b2005d --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Extensions/ValidationContextExtensions.cs @@ -0,0 +1,29 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel.DataAnnotations; + +#pragma warning disable CA1716 +namespace Microsoft.Shared.Data.Validation; +#pragma warning restore CA1716 + +#if !SHARED_PROJECT +[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] +#endif + +internal static class ValidationContextExtensions +{ + public static string[]? GetMemberName(this ValidationContext? validationContext) + { +#pragma warning disable S1168 // Empty arrays and collections should be returned instead of null + return validationContext?.MemberName is { } memberName + ? new[] { memberName } + : null; +#pragma warning restore S1168 // Empty arrays and collections should be returned instead of null + } + + public static string GetDisplayName(this ValidationContext? validationContext) + { + return validationContext?.DisplayName ?? string.Empty; + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/CustomAttrTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/CustomAttrTests.cs new file mode 100644 index 0000000000000..9770924b5b12d --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/CustomAttrTests.cs @@ -0,0 +1,39 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using CustomAttr; +using Microsoft.Extensions.Options; +using Xunit; + +namespace Microsoft.Gen.OptionsValidation.Test; + +public class CustomAttrTests +{ + [Fact] + public void Invalid() + { + var firstModel = new FirstModel + { + P1 = 'a', + P2 = 'x', + }; + + var validator = new FirstValidator(); + var vr = validator.Validate("CustomAttr", firstModel); + + Utils.VerifyValidateOptionsResult(vr, 2, "P1", "P2"); + } + + [Fact] + public void Valid() + { + var firstModel = new FirstModel + { + P1 = 'A', + P2 = 'A', + }; + + var validator = new FirstValidator(); + Assert.Equal(ValidateOptionsResult.Success, validator.Validate("CustomAttr", firstModel)); + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/EnumerationTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/EnumerationTests.cs new file mode 100644 index 0000000000000..d2f9bddca2843 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/EnumerationTests.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Enumeration; +using Microsoft.Extensions.Options; +using Xunit; + +namespace Microsoft.Gen.OptionsValidation.Test; + +public class EnumerationTests +{ + [Fact] + public void Invalid() + { + var secondModelC = new SecondModel + { + P6 = "1234", + }; + + var secondModelB = new SecondModel + { + P6 = "12345", + }; + + var secondModel = new SecondModel + { + P6 = "1234", + }; + + ThirdModel? thirdModel = new ThirdModel + { + Value = 11 + }; + + var firstModel = new FirstModel + { + P1 = new[] { secondModel }, + P2 = new[] { secondModel, secondModelB, secondModelC }, + P51 = new[] { thirdModel } + }; + + var validator = default(FirstValidator); + var vr = validator.Validate("Enumeration", firstModel); + + Utils.VerifyValidateOptionsResult(vr, 4, "P1[0].P6", "P2[0].P6", "P2[2].P6", "P51[0].Value"); + } + + [Fact] + public void NullElement() + { + var firstModel = new FirstModel + { + P1 = new[] { (SecondModel)null! }, + }; + + var validator = default(FirstValidator); + var vr = validator.Validate("Enumeration", firstModel); + + Utils.VerifyValidateOptionsResult(vr, 1, "P1[0]"); + } + + [Fact] + public void Valid() + { + var secondModel = new SecondModel + { + P6 = "12345", + }; + + var thirdModelA = new ThirdModel + { + Value = 2 + }; + + var thirdModelB = new ThirdModel + { + Value = 9 + }; + + var firstModel = new FirstModel + { + P1 = new[] { secondModel }, + P2 = new[] { secondModel }, + P3 = new[] { (SecondModel?)null }, + P4 = new[] { thirdModelA, thirdModelB }, + P5 = new ThirdModel?[] { thirdModelA, default }, + P51 = new ThirdModel?[] { thirdModelB, default } + }; + + var validator = default(FirstValidator); + Assert.Equal(ValidateOptionsResult.Success, validator.Validate("Enumeration", firstModel)); + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/FieldTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/FieldTests.cs new file mode 100644 index 0000000000000..3656d4f359e5f --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/FieldTests.cs @@ -0,0 +1,62 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Fields; +using Microsoft.Extensions.Options; +using Xunit; + +namespace Microsoft.Gen.OptionsValidation.Test; + +public class FieldTests +{ + [Fact] + public void Invalid() + { + var thirdModel = new ThirdModel + { + P5 = "1234", + }; + + var secondModel = new SecondModel + { + P4 = "1234", + }; + + var firstModel = new FirstModel + { + P1 = "1234", + P2 = secondModel, + P3 = thirdModel, + }; + + var validator = default(FirstValidator); + var vr = validator.Validate("Fields", firstModel); + + Utils.VerifyValidateOptionsResult(vr, 3, "P1", "P2.P4", "P3.P5"); + } + + [Fact] + public void Valid() + { + var thirdModel = new ThirdModel + { + P5 = "12345", + P6 = 1 + }; + + var secondModel = new SecondModel + { + P4 = "12345", + }; + + var firstModel = new FirstModel + { + P1 = "12345", + P2 = secondModel, + P3 = thirdModel, + }; + + var validator = default(FirstValidator); + Assert.Equal(ValidateOptionsResult.Success, validator.Validate("Fields", firstModel)); + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/FunnyStringsTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/FunnyStringsTests.cs new file mode 100644 index 0000000000000..29b62a5c4b4b9 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/FunnyStringsTests.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using FunnyStrings; +using Microsoft.Extensions.Options; +using Xunit; + +namespace Microsoft.Gen.OptionsValidation.Test; + +public class FunnyStringsTests +{ + [Fact] + public void Invalid() + { + var firstModel = new FirstModel + { + P1 = "XXX", + }; + + var validator = default(FirstValidator); + var vr = validator.Validate("FunnyStrings", firstModel); + + Utils.VerifyValidateOptionsResult(vr, 1, "P1"); + } + + [Fact] + public void Valid() + { + var firstModel = new FirstModel + { + P1 = "\"\r\n\\", + }; + + var validator = default(FirstValidator); + Assert.Equal(ValidateOptionsResult.Success, validator.Validate("FunnyStrings", firstModel)); + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/GenericsTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/GenericsTests.cs new file mode 100644 index 0000000000000..838fe5296897b --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/GenericsTests.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Generics; +using Microsoft.Extensions.Options; +using Xunit; + +namespace Microsoft.Gen.OptionsValidation.Test; + +public class GenericsTests +{ + [Fact] + public void Invalid() + { + var secondModel = new SecondModel + { + P4 = "1234", + }; + + var firstModel = new FirstModel + { + P1 = "1234", + P3 = secondModel, + }; + + var validator = new FirstValidator(); + var vr = validator.Validate("Generics", firstModel); + + Utils.VerifyValidateOptionsResult(vr, 2, "P1", "P3.P4"); + } + + [Fact] + public void Valid() + { + var secondModel = new SecondModel + { + P4 = "12345", + }; + + var firstModel = new FirstModel + { + P1 = "12345", + P3 = secondModel, + }; + + var validator = new FirstValidator(); + Assert.Equal(ValidateOptionsResult.Success, validator.Validate("Generics", firstModel)); + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/MultiModelValidatorTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/MultiModelValidatorTests.cs new file mode 100644 index 0000000000000..fff83ced91845 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/MultiModelValidatorTests.cs @@ -0,0 +1,49 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Extensions.Options; +using MultiModelValidator; +using Xunit; + +namespace Microsoft.Gen.OptionsValidation.Test; + +public class MultiModelValidatorTests +{ + [Fact] + public void Invalid() + { + var secondModel = new SecondModel + { + P3 = "1234", + }; + + var firstModel = new FirstModel + { + P1 = "1234", + P2 = secondModel, + }; + + var validator = default(MultiValidator); + var vr = validator.Validate("MultiModelValidator", firstModel); + + Utils.VerifyValidateOptionsResult(vr, 2, "P1", "P2.P3"); + } + + [Fact] + public void Valid() + { + var secondModel = new SecondModel + { + P3 = "12345", + }; + + var firstModel = new FirstModel + { + P1 = "12345", + P2 = secondModel, + }; + + var validator = default(MultiValidator); + Assert.Equal(ValidateOptionsResult.Success, validator.Validate("MultiModelValidator", firstModel)); + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/NestedTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/NestedTests.cs new file mode 100644 index 0000000000000..8a4b04361a32a --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/NestedTests.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if ROSLYN_4_0_OR_GREATER + +using Microsoft.Extensions.Options; +using Nested; +using Xunit; + +namespace Microsoft.Gen.OptionsValidation.Test; + +public class NestedTests +{ + [Fact] + public void Invalid() + { + var thirdModel = new Container1.ThirdModel + { + P6 = "1234", + }; + + var secondModel = new Container1.SecondModel + { + P5 = "1234", + }; + + var firstModel = new Container1.FirstModel + { + P1 = "1234", + P2 = secondModel, + P3 = thirdModel, + P4 = secondModel, + }; + + var validator = default(Container2.Container3.FirstValidator); + var vr = validator.Validate("Nested", firstModel); + + Utils.VerifyValidateOptionsResult(vr, 4, "P1", "P2.P5", "P3.P6", "P4.P5"); + } + + [Fact] + public void Valid() + { + var thirdModel = new Container1.ThirdModel + { + P6 = "12345", + }; + + var secondModel = new Container1.SecondModel + { + P5 = "12345", + }; + + var firstModel = new Container1.FirstModel + { + P1 = "12345", + P2 = secondModel, + P3 = thirdModel, + P4 = secondModel, + }; + + var validator = default(Container2.Container3.FirstValidator); + Assert.Equal(ValidateOptionsResult.Success, validator.Validate("Nested", firstModel)); + } +} + +#endif diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/NoNamespaceTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/NoNamespaceTests.cs new file mode 100644 index 0000000000000..5530b63953180 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/NoNamespaceTests.cs @@ -0,0 +1,60 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Extensions.Options; +using Xunit; + +namespace Microsoft.Gen.OptionsValidation.Test; + +public class NoNamespaceTests +{ + [Fact] + public void Invalid() + { + var thirdModel = new ThirdModelNoNamespace + { + P5 = "1234", + }; + + var secondModel = new SecondModelNoNamespace + { + P4 = "1234", + }; + + var firstModel = new FirstModelNoNamespace + { + P1 = "1234", + P2 = secondModel, + P3 = thirdModel, + }; + + var validator = new FirstValidatorNoNamespace(); + var vr = validator.Validate("NoNamespace", firstModel); + + Utils.VerifyValidateOptionsResult(vr, 3, "P1", "P2.P4", "P3.P5"); + } + + [Fact] + public void Valid() + { + var thirdModel = new ThirdModelNoNamespace + { + P5 = "12345", + }; + + var secondModel = new SecondModelNoNamespace + { + P4 = "12345", + }; + + var firstModel = new FirstModelNoNamespace + { + P1 = "12345", + P2 = secondModel, + P3 = thirdModel, + }; + + var validator = new FirstValidatorNoNamespace(); + Assert.Equal(ValidateOptionsResult.Success, validator.Validate("NoNamespace", firstModel)); + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/OptionsValidationTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/OptionsValidationTests.cs new file mode 100644 index 0000000000000..49941010f1ace --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/OptionsValidationTests.cs @@ -0,0 +1,442 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using Microsoft.Extensions.Options; +using TestClasses.OptionsValidation; +using Xunit; + +namespace Microsoft.Gen.OptionsValidation.Test; + +public class OptionsValidationTests +{ + [Fact] + public void RequiredAttributeValid() + { + var validModel = new RequiredAttributeModel + { + Val = "val" + }; + + var modelValidator = new RequiredAttributeModelValidator(); + var result = modelValidator.Validate(nameof(validModel), validModel); + + Assert.Equal(ValidateOptionsResult.Success, result); + } + + [Fact] + public void RequiredAttributeInvalid() + { + var validModel = new RequiredAttributeModel + { + Val = null + }; + + var modelValidator = new RequiredAttributeModelValidator(); + Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(validModel), validModel), 1); + } + + [Fact] + public void RegularExpressionAttributeValid() + { + var validModel = new RegularExpressionAttributeModel + { + Val = " " + }; + + var modelValidator = new RegularExpressionAttributeModelValidator(); + var result = modelValidator.Validate(nameof(validModel), validModel); + + Assert.Equal(ValidateOptionsResult.Success, result); + } + + [Fact] + public void RegularExpressionAttributeInvalid() + { + var validModel = new RegularExpressionAttributeModel + { + Val = "Not Space" + }; + + var modelValidator = new RegularExpressionAttributeModelValidator(); + Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(validModel), validModel), 1); + } + + [Fact] + public void EmailAttributeValid() + { + var validModel = new EmailAttributeModel + { + Val = "abc@xyz.com" + }; + + var modelValidator = new EmailAttributeModelValidator(); + var result = modelValidator.Validate(nameof(validModel), validModel); + + Assert.Equal(ValidateOptionsResult.Success, result); + } + + [Fact] + public void EmailAttributeInvalid() + { + var validModel = new EmailAttributeModel + { + Val = "Not Email Address" + }; + + var modelValidator = new EmailAttributeModelValidator(); + Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(validModel), validModel), 1); + } + + [Fact] + public void CustomValidationAttributeValid() + { + var validModel = new CustomValidationAttributeModel + { + Val = "Pass" + }; + + var modelValidator = new CustomValidationAttributeModelValidator(); + var result = modelValidator.Validate(nameof(validModel), validModel); + + Assert.Equal(ValidateOptionsResult.Success, result); + } + + [Fact] + public void CustomValidationAttributeInvalid() + { + var validModel = new CustomValidationAttributeModel + { + Val = "NOT PASS" + }; + + var modelValidator = new CustomValidationAttributeModelValidator(); + Assert.Throws(() => modelValidator.Validate(nameof(validModel), validModel)); + } + + [Fact] + public void DataTypeAttributeValid() + { + var validModel = new DataTypeAttributeModel + { + Val = "ABC" + }; + + var modelValidator = new DataTypeAttributeModelValidator(); + var result = modelValidator.Validate(nameof(validModel), validModel); + + Assert.Equal(ValidateOptionsResult.Success, result); + } + + [Fact] + public void RangeAttributeModelIntValid() + { + var validModel = new RangeAttributeModelInt + { + Val = 1 + }; + + var modelValidator = new RangeAttributeModelIntValidator(); + var result = modelValidator.Validate(nameof(validModel), validModel); + + Assert.Equal(ValidateOptionsResult.Success, result); + } + + [Fact] + public void RangeAttributeModelIntInvalid() + { + var validModel = new RangeAttributeModelInt + { + Val = 0 + }; + + var modelValidator = new RangeAttributeModelIntValidator(); + Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(validModel), validModel), 1); + } + + [Fact] + public void RangeAttributeModelDoubleValid() + { + var validModel = new RangeAttributeModelDouble + { + Val = 0.6 + }; + + var modelValidator = new RangeAttributeModelDoubleValidator(); + var result = modelValidator.Validate(nameof(validModel), validModel); + + Assert.Equal(ValidateOptionsResult.Success, result); + } + + [Fact] + public void RangeAttributeModelDoubleInvalid() + { + var validModel = new RangeAttributeModelDouble + { + Val = 0.1 + }; + + var modelValidator = new RangeAttributeModelDoubleValidator(); + Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(validModel), validModel), 1); + } + + [Fact] + public void RangeAttributeModelDateValid() + { +#if NETCOREAPP3_1_OR_GREATER + // Setting non-invariant culture to check that + // attribute's "ParseLimitsInInvariantCulture" property + // was set up correctly in the validator: + CultureInfo.CurrentCulture = new CultureInfo("cs"); +#else + // Setting invariant culture to avoid DateTime parsing discrepancies: + CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; +#endif + var validModel = new RangeAttributeModelDate + { + Val = new DateTime(day: 3, month: 1, year: 2004) + }; + + var modelValidator = new RangeAttributeModelDateValidator(); + var result = modelValidator.Validate(nameof(validModel), validModel); + + Assert.Equal(ValidateOptionsResult.Success, result); + } + + [Fact] + public void RangeAttributeModelDateInvalid() + { + var validModel = new RangeAttributeModelDate + { + Val = new DateTime(day: 1, month: 1, year: 2004) + }; + + var modelValidator = new RangeAttributeModelDateValidator(); + Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(validModel), validModel), 1); + } + + [Fact] + public void MultipleAttributeModelValid() + { + var validModel = new MultipleAttributeModel + { + Val1 = "abc", + Val2 = 2, + Val3 = 4, + Val4 = 6 + }; + + var modelValidator = new MultipleAttributeModelValidator(); + var result = modelValidator.Validate(nameof(validModel), validModel); + + Assert.Equal(ValidateOptionsResult.Success, result); + } + + [Theory] + [InlineData("", 2, 4, 7)] + [InlineData(null, 2, 4, 7)] + [InlineData("abc", 0, 4, 9)] + [InlineData("abc", 2, 8, 8)] + [InlineData("abc", 2, 4, 10)] + public void MultipleAttributeModelInvalid(string val1, int val2, int val3, int val4) + { + var validModel = new MultipleAttributeModel + { + Val1 = val1, + Val2 = val2, + Val3 = val3, + Val4 = val4 + }; + + var modelValidator = new MultipleAttributeModelValidator(); + Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(validModel), validModel), 1); + } + + [Fact] + public void CustomTypeCustomValidationAttributeModelValid() + { + var validModel = new CustomTypeCustomValidationAttributeModel + { + Val = new CustomType { Val1 = "Pass", Val2 = "Pass" } + }; + + var modelValidator = new CustomTypeCustomValidationAttributeModelValidator(); + var result = modelValidator.Validate(nameof(validModel), validModel); + + Assert.Equal(ValidateOptionsResult.Success, result); + } + + [Fact] + public void CustomTypeCustomValidationAttributeModelInvalid() + { + var validModel = new CustomTypeCustomValidationAttributeModel + { + Val = new CustomType { Val1 = "Pass", Val2 = "Not Pass" } + }; + + var modelValidator = new CustomTypeCustomValidationAttributeModelValidator(); + Assert.Throws(() => modelValidator.Validate(nameof(validModel), validModel)); + } + + [Fact] + public void DerivedModelIsValid() + { + var validModel = new DerivedModel + { + Val = 1, + DerivedVal = "Valid", + VirtualValWithAttr = 1, + VirtualValWithoutAttr = null + }; + + ((RequiredAttributeModel)validModel).Val = "Valid hidden member from base class"; + + var validator = new DerivedModelValidator(); + var result = validator.Validate(nameof(validModel), validModel); + Assert.Equal(ValidateOptionsResult.Success, result); + } + + [Theory] + [InlineData(0, "", 1, null, "Valid hidden member from base class")] + [InlineData(null, "Valid", 1, null, "Valid hidden member from base class")] + [InlineData(1, "Valid", null, null, "Valid hidden member from base class")] + public void DerivedModelIsInvalid(int? val, string? derivedVal, int? virtValAttr, int? virtVal, string? hiddenValBaseClass) + { + var invalidModel = new DerivedModel + { + Val = val, + DerivedVal = derivedVal, + VirtualValWithAttr = virtValAttr, + VirtualValWithoutAttr = virtVal + }; + + ((RequiredAttributeModel)invalidModel).Val = hiddenValBaseClass; + + var validator = new DerivedModelValidator(); + Utils.VerifyValidateOptionsResult(validator.Validate(nameof(invalidModel), invalidModel), 1); + } + + [Fact] + public void LeafModelIsValid() + { + var validModel = new LeafModel + { + Val = 1, + DerivedVal = "Valid", + VirtualValWithAttr = null, + VirtualValWithoutAttr = 1 + }; + + ((RequiredAttributeModel)validModel).Val = "Valid hidden member from base class"; + + var validator = new LeafModelValidator(); + var result = validator.Validate(nameof(validModel), validModel); + Assert.Equal(ValidateOptionsResult.Success, result); + } + + [Fact] + public void ComplexModelValid() + { + var validModel = new ComplexModel + { + ComplexVal = new RequiredAttributeModel { Val = "Valid" } + }; + + var modelValidator = new ComplexModelValidator(); + var result = modelValidator.Validate(nameof(validModel), validModel); + Assert.Equal(ValidateOptionsResult.Success, result); + + validModel = new ComplexModel + { + ValWithoutOptionsValidator = new TypeWithoutOptionsValidator + { + Val1 = "Valid", + Val2 = new DateTime(day: 3, month: 1, year: 2004) + } + }; + + // Setting invariant culture to avoid DateTime parsing discrepancies: + CultureInfo.CurrentCulture = CultureInfo.InvariantCulture; + result = modelValidator.Validate(nameof(validModel), validModel); + Assert.Equal(ValidateOptionsResult.Success, result); + + validModel = new ComplexModel + { + ValWithoutOptionsValidator = new TypeWithoutOptionsValidator + { + Val1 = "A", + Val2 = new DateTime(day: 2, month: 2, year: 2004), + YetAnotherComplexVal = new RangeAttributeModelDouble { Val = 0.7 } + } + }; + + result = modelValidator.Validate(nameof(validModel), validModel); + Assert.Equal(ValidateOptionsResult.Success, result); + } + + [Fact] + public void ComplexModelInvalid() + { + var invalidModel = new ComplexModel + { + ComplexVal = new RequiredAttributeModel { Val = null } + }; + + var modelValidator = new ComplexModelValidator(); + Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(invalidModel), invalidModel), 1); + + invalidModel = new ComplexModel + { + ValWithoutOptionsValidator = new TypeWithoutOptionsValidator { Val1 = "Valid", Val2 = new DateTime(2003, 3, 3) } + }; + + Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(invalidModel), invalidModel), 1); + + invalidModel = new ComplexModel + { + ValWithoutOptionsValidator = new TypeWithoutOptionsValidator { Val1 = string.Empty, Val2 = new DateTime(2004, 3, 3) } + }; + + Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(invalidModel), invalidModel), 1); + + invalidModel = new ComplexModel + { + ValWithoutOptionsValidator = new TypeWithoutOptionsValidator + { + Val1 = "A", + Val2 = new DateTime(2004, 2, 2), + YetAnotherComplexVal = new RangeAttributeModelDouble { Val = 0.4999 } + } + }; + + Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(invalidModel), invalidModel), 1); + } + + [Fact] + public void AttributePropertyModelTestOnErrorMessage() + { + var validModel = new AttributePropertyModel + { + Val1 = 5, + Val2 = 1 + }; + + var modelValidator = new AttributePropertyModelValidator(); + Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(validModel), validModel), 1); + } + + [Fact] + public void AttributePropertyModelTestOnErrorMessageResource() + { + var validModel = new AttributePropertyModel + { + Val1 = 1, + Val2 = 5 + }; + + var modelValidator = new AttributePropertyModelValidator(); + Utils.VerifyValidateOptionsResult(modelValidator.Validate(nameof(validModel), validModel), 1); + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/RandomMembersTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/RandomMembersTests.cs new file mode 100644 index 0000000000000..1c87892b27952 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/RandomMembersTests.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Extensions.Options; +using RandomMembers; +using Xunit; + +namespace Microsoft.Gen.OptionsValidation.Test; + +public class RandomMembersTests +{ + [Fact] + public void Invalid() + { + var firstModel = new FirstModel + { + P1 = "1234", + }; + + var validator = new FirstValidator(); + var vr = validator.Validate("RandomMembers", firstModel); + + Utils.VerifyValidateOptionsResult(vr, 1, "P1"); + } + + [Fact] + public void Valid() + { + var firstModel = new FirstModel + { + P1 = "12345", + }; + + var validator = new FirstValidator(); + Assert.Equal(ValidateOptionsResult.Success, validator.Validate("RandomMembers", firstModel)); + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/RecordTypesTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/RecordTypesTests.cs new file mode 100644 index 0000000000000..bd0d872c8b686 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/RecordTypesTests.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if ROSLYN_4_0_OR_GREATER + +using Microsoft.Extensions.Options; +using RecordTypes; +using Xunit; + +namespace Microsoft.Gen.OptionsValidation.Test; + +public class RecordTypesTests +{ + [Fact] + public void Invalid() + { + var thirdModel = new ThirdModel + { + P6 = "1234", + }; + + var secondModel = new SecondModel + { + P5 = "1234", + }; + + var firstModel = new FirstModel + { + P1 = "1234", + P2 = secondModel, + P3 = secondModel, + P4 = thirdModel, + }; + + var validator = default(FirstValidator); + var vr = validator.Validate("RecordTypes", firstModel); + + Utils.VerifyValidateOptionsResult(vr, 4, "P1", "P2.P5", "P3.P5", "P4.P6"); + } + + [Fact] + public void Valid() + { + var thirdModel = new ThirdModel + { + P6 = "12345", + }; + + var secondModel = new SecondModel + { + P5 = "12345", + }; + + var firstModel = new FirstModel + { + P1 = "12345", + P2 = secondModel, + P3 = secondModel, + P4 = thirdModel, + }; + + var validator = default(FirstValidator); + Assert.Equal(ValidateOptionsResult.Success, validator.Validate("RecordTypes", firstModel)); + } +} + +#endif diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/RepeatedTypesTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/RepeatedTypesTests.cs new file mode 100644 index 0000000000000..b45fcbf9ced02 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/RepeatedTypesTests.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Extensions.Options; +using RepeatedTypes; +using Xunit; + +namespace Microsoft.Gen.OptionsValidation.Test; + +public class RepeatedTypesTests +{ + [Fact] + public void Invalid() + { + var thirdModel = new ThirdModel + { + P5 = "1234", + }; + + var secondModel = new SecondModel + { + P4 = thirdModel, + }; + + var firstModel = new FirstModel + { + P1 = secondModel, + P2 = secondModel, + P3 = thirdModel, + }; + + var validator = new FirstValidator(); + var vr = validator.Validate("RepeatedTypes", firstModel); + + Utils.VerifyValidateOptionsResult(vr, 3, "P1.P4.P5", "P2.P4.P5", "P3.P5"); + } + + [Fact] + public void Valid() + { + var thirdModel = new ThirdModel + { + P5 = "12345", + }; + + var secondModel = new SecondModel + { + P4 = thirdModel, + }; + + var firstModel = new FirstModel + { + P1 = secondModel, + P2 = secondModel, + P3 = thirdModel, + }; + + var validator = new FirstValidator(); + Assert.Equal(ValidateOptionsResult.Success, validator.Validate("RepeatedTypes", firstModel)); + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/SelfValidationTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/SelfValidationTests.cs new file mode 100644 index 0000000000000..0a511333f0357 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/SelfValidationTests.cs @@ -0,0 +1,37 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Extensions.Options; +using SelfValidation; +using Xunit; + +namespace Microsoft.Gen.OptionsValidation.Test; + +public class SelfValidationTests +{ + [Fact] + public void Invalid() + { + var firstModel = new FirstModel + { + P1 = "1234", + }; + + var validator = default(FirstValidator); + var vr = validator.Validate("SelfValidation", firstModel); + + Utils.VerifyValidateOptionsResult(vr, 1, "P1"); + } + + [Fact] + public void Valid() + { + var firstModel = new FirstModel + { + P1 = "12345", + }; + + var validator = default(FirstValidator); + Assert.Equal(ValidateOptionsResult.Success, validator.Validate("SelfValidation", firstModel)); + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/Utils.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/Utils.cs new file mode 100644 index 0000000000000..7412374f18a56 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/Utils.cs @@ -0,0 +1,31 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if NETCOREAPP3_1_OR_GREATER +using System.Linq; +#endif +using Microsoft.Extensions.Options; +using Xunit; + +namespace Microsoft.Gen.OptionsValidation.Test; + +internal static class Utils +{ + public static void VerifyValidateOptionsResult(ValidateOptionsResult vr, int expectedErrorCount, params string[] expectedErrorSubstrings) + { + Assert.NotNull(vr); + +#if NETCOREAPP3_1_OR_GREATER + var failures = vr.Failures!.ToArray(); +#else + var failures = vr.FailureMessage!.Split(';'); +#endif + + Assert.Equal(expectedErrorCount, failures.Length); + + for (int i = 0; i < expectedErrorSubstrings.Length; i++) + { + Assert.Contains(expectedErrorSubstrings[i], failures[i]); + } + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/ValueTypesTests.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/ValueTypesTests.cs new file mode 100644 index 0000000000000..543e8eb882b20 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Generated/ValueTypesTests.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Extensions.Options; +using ValueTypes; +using Xunit; + +namespace Microsoft.Gen.OptionsValidation.Test; + +public class ValueTypesTests +{ + [Fact] + public void Invalid() + { + var secondModel = new SecondModel + { + P4 = "1234", + }; + + var firstModel = new FirstModel + { + P1 = "1234", + P3 = secondModel, + P2 = secondModel, + P4 = default, + }; + + var validator = default(FirstValidator); + var vr = validator.Validate("ValueTypes", firstModel); + + Utils.VerifyValidateOptionsResult(vr, 3, "P1", "P2.P4", "P3.P4"); + } + + [Fact] + public void Valid() + { + var secondModel = new SecondModel + { + P4 = "12345", + }; + + var firstModel = new FirstModel + { + P1 = "12345", + P3 = secondModel, + P2 = secondModel, + P4 = default, + }; + + var validator = default(FirstValidator); + Assert.Equal(ValidateOptionsResult.Success, validator.Validate("ValueTypes", firstModel)); + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Microsoft.Extensions.Options.SourceGeneration.Tests.csproj b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Microsoft.Extensions.Options.SourceGeneration.Tests.csproj new file mode 100644 index 0000000000000..54f7dfdade3c8 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Microsoft.Extensions.Options.SourceGeneration.Tests.csproj @@ -0,0 +1,55 @@ + + + $(NetCoreAppCurrent);$(NetFrameworkMinimum) + $(MicrosoftCodeAnalysisVersion_4_4) + true + + true + + $(DefineConstants);ROSLYN4_0_OR_GREATER;ROSLYN4_4_OR_GREATER;ROSLYN_4_0_OR_GREATER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + PreserveNewest + + + + PreserveNewest + + + + + + + + + + + diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Resources/Strings.resx b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Resources/Strings.resx new file mode 100644 index 0000000000000..1673d4621545b --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/Resources/Strings.resx @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ErrorMessageResourceName + + + Can't use 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' on fields or properties with open generic types. + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateEnumeratedItems] which could be an oversight. + + + Member potentially missing enumerable validation. + + + Type {0} has validation annotations, but member {1} doesn't specify [ValidateObjectMembers] which could be an oversight. + + + There is a circular type reference involving type {0} preventing it from being used for static validation. + + + Unsupported circular references in model types. + + + Can't apply validation attributes to private field or property {0}. + + + [ValidateEnumeratedItems] cannot be used on members of type {0} as it doesn't implement IEnumerable<T>. + + + Null validator type specified for the 'ValidateObjectMembersAttribute' or 'ValidateEnumeratedItemsAttribute' attributes. + + + Can't use [ValidateObjectMembers] or [ValidateEnumeratedItems] on fields or properties with open generic type {0}. + + + A member type has no fields or properties to validate. + + + A type has no fields or properties to validate. + + + Member type is not enumerable. + + + Validator type {0} doesn't have a parameterless constructor. + + + Type {0} does not implement the required IValidateOptions<{1}> interface. + + + Validators used for transitive or enumerable validation must have a constructor with no parameters. + + + Type {0} has no fields or properties to validate, referenced by type {1}. + + + Type {0} has no fields or properties to validate, referenced from member {1}. + + + A type annotated with 'OptionsValidatorAttribute' doesn't implement the necessary interface. + + + Can't validate private fields or properties. + + + [OptionsValidator] cannot be applied to static class {0}. + + + A type already includes an implementation of the 'Validate' method. + + + Type {0} already implements the Validate method. + + + 'OptionsValidatorAttribute' can't be applied to a static class. + + + Null validator type specified in [ValidateObjectMembers] or [ValidateEnumeratedItems] attributes. + + + Member potentially missing transitive validation. + + \ No newline at end of file diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/CustomAttr.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/CustomAttr.cs new file mode 100644 index 0000000000000..aa75c2e5ee8ef --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/CustomAttr.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.Options; + +namespace CustomAttr +{ +#pragma warning disable SA1649 +#pragma warning disable SA1402 +#pragma warning disable CA1019 +#pragma warning disable IDE0052 + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, AllowMultiple = false)] + public sealed class CustomAttribute : ValidationAttribute + { + private readonly char _ch; + private readonly bool _caseSensitive; + private readonly string? _extra; + + public CustomAttribute(char ch, bool caseSensitive, string? extra) + { + _ch = ch; + _caseSensitive = caseSensitive; + _extra = extra; + } + + protected override ValidationResult IsValid(object? value, ValidationContext? validationContext) + { + if (value == null) + { + return ValidationResult.Success!; + } + + if (_caseSensitive) + { + if ((char)value != _ch) + { + return new ValidationResult($"{validationContext?.MemberName} didn't match"); + } + } + else + { + if (char.ToUpperInvariant((char)value) != char.ToUpperInvariant(_ch)) + { + return new ValidationResult($"{validationContext?.MemberName} didn't match"); + } + } + + return ValidationResult.Success!; + } + } + + public class FirstModel + { + [Custom('A', true, null)] + public char P1 { get; set; } + + [Custom('A', false, "X")] + public char P2 { get; set; } + } + + [OptionsValidator] + public partial class FirstValidator : IValidateOptions + { + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Enumeration.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Enumeration.cs new file mode 100644 index 0000000000000..549e5fbb82e8f --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Enumeration.cs @@ -0,0 +1,93 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.Options; + +namespace Enumeration +{ +#pragma warning disable SA1649 +#pragma warning disable SA1402 + + public class FirstModel + { + [ValidateEnumeratedItems] + public IList? P1; + + [ValidateEnumeratedItems(typeof(SecondValidator))] + public IList? P2; + + [ValidateEnumeratedItems] + public IList? P3; + + [ValidateEnumeratedItems] + public IList? P4; + + [ValidateEnumeratedItems] + public IList? P5; + + [ValidateEnumeratedItems] + [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1125:Use shorthand for nullable types", Justification = "Testing System>Nullable")] + public IList>? P51; + + [ValidateEnumeratedItems] + public SynteticEnumerable? P6; + + [ValidateEnumeratedItems] + public SynteticEnumerable P7; + + [ValidateEnumeratedItems] + [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1125:Use shorthand for nullable types", Justification = "Testing System>Nullable")] + public Nullable P8; + } + + public class SecondModel + { + [Required] + [MinLength(5)] + public string P6 = string.Empty; + } + + public struct ThirdModel + { + [Range(0, 10)] + public int Value; + } + + public struct SynteticEnumerable : IEnumerable + { + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public IEnumerator GetEnumerator() => new InternalEnumerator(); + + private class InternalEnumerator : IEnumerator + { + public SecondModel Current => throw new NotSupportedException(); + + object IEnumerator.Current => Current; + + public void Dispose() + { + // Nothing to dispose... + } + + public bool MoveNext() => false; + + public void Reset() => throw new NotSupportedException(); + } + } + + [OptionsValidator] + public partial struct FirstValidator : IValidateOptions + { + } + + [OptionsValidator] + public partial struct SecondValidator : IValidateOptions + { + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Fields.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Fields.cs new file mode 100644 index 0000000000000..0a6ab1470e321 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Fields.cs @@ -0,0 +1,71 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.Options; + +namespace Fields +{ +#pragma warning disable SA1649 +#pragma warning disable SA1402 +#pragma warning disable S1186 +#pragma warning disable CA1822 + + public class FirstModel + { + [Required] + [MinLength(5)] + public string P1 = string.Empty; + + [Microsoft.Extensions.Options.ValidateObjectMembers(typeof(SecondValidator))] + public SecondModel? P2; + + [Microsoft.Extensions.Options.ValidateObjectMembers] + public ThirdModel P3; + } + + public class SecondModel + { + [Required] + [MinLength(5)] + public string P4 = string.Empty; + } + + public struct ThirdModel + { + [Required] + [MinLength(5)] + public string P5 = string.Empty; + + public int P6 = default; + + public ThirdModel(object _) + { + } + } + + [OptionsValidator] + public partial struct FirstValidator : IValidateOptions + { + public void Validate() + { + } + + public void Validate(int _) + { + } + + public void Validate(string? _) + { + } + + public void Validate(string? _0, object _1) + { + } + } + + [OptionsValidator] + public partial struct SecondValidator : IValidateOptions + { + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/FileScopedNamespace.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/FileScopedNamespace.cs new file mode 100644 index 0000000000000..cf76ea3497f1e --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/FileScopedNamespace.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.Options; + +namespace FileScopedNamespace; + +#pragma warning disable SA1649 // File name should match first type name + +public class FirstModel +{ + [Required] + [MinLength(5)] + public string P1 = string.Empty; +} + +[OptionsValidator] +public partial struct FirstValidator : IValidateOptions +{ +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/FunnyStrings.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/FunnyStrings.cs new file mode 100644 index 0000000000000..c1c874ab301f0 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/FunnyStrings.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.Options; + +namespace FunnyStrings +{ +#pragma warning disable SA1649 +#pragma warning disable SA1402 + + public class FirstModel + { + [RegularExpression("\"\r\n\\\\")] + public string P1 { get; set; } = string.Empty; + } + + [OptionsValidator] + public partial struct FirstValidator : IValidateOptions + { + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Generics.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Generics.cs new file mode 100644 index 0000000000000..51a59a73972e9 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Generics.cs @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.Options; + +namespace Generics +{ +#pragma warning disable SA1649 +#pragma warning disable SA1402 + + public class FirstModel + { + [Required] + [MinLength(5)] + public string P1 { get; set; } = string.Empty; + + public T? P2 { get; set; } + + [Microsoft.Extensions.Options.ValidateObjectMembers] + public SecondModel? P3 { get; set; } + } + + public class SecondModel + { + [Required] + [MinLength(5)] + public string P4 { get; set; } = string.Empty; + } + + [OptionsValidator] + public partial class FirstValidator : IValidateOptions> + { + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Models.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Models.cs new file mode 100644 index 0000000000000..31993d093759a --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Models.cs @@ -0,0 +1,251 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.Options; +using Microsoft.Gen.OptionsValidation.Test; + +#pragma warning disable SA1649 +#pragma warning disable SA1402 + +namespace TestClasses.OptionsValidation +{ + // ValidationAttribute without parameter + public class RequiredAttributeModel + { + [Required] + public string? Val { get; set; } + } + + // ValidationAttribute with string parameter + public class RegularExpressionAttributeModel + { + [RegularExpression("\\s")] + public string Val { get; set; } = string.Empty; + } + + // DataTypeAttribute + public class EmailAttributeModel + { + [EmailAddress] + public string Val { get; set; } = string.Empty; + } + + // ValidationAttribute with System.Type parameter + public class CustomValidationAttributeModel + { + [CustomValidation(typeof(CustomValidationTest), "TestMethod")] + public string Val { get; set; } = string.Empty; + } + +#pragma warning disable SA1204 // Static elements should appear before instance elements + public static class CustomValidationTest +#pragma warning restore SA1204 // Static elements should appear before instance elements + { + public static ValidationResult? TestMethod(string val, ValidationContext _) + { + if (val.Equals("Pass", StringComparison.Ordinal)) + { + return ValidationResult.Success; + } + + throw new ValidationException(); + } + } + + // ValidationAttribute with DataType parameter + public class DataTypeAttributeModel + { + [DataType(DataType.Text)] + public string Val { get; set; } = string.Empty; + } + + // ValidationAttribute with type, double, int parameters + public class RangeAttributeModelInt + { + [Range(1, 3)] + public int Val { get; set; } + } + + public class RangeAttributeModelDouble + { + [Range(0.5, 0.9)] + public double Val { get; set; } + } + + public class RangeAttributeModelDate + { +#if NETCOREAPP3_1_OR_GREATER + [Range(typeof(DateTime), "1/2/2004", "3/4/2004", ParseLimitsInInvariantCulture = true)] +#else + [Range(typeof(DateTime), "1/2/2004", "3/4/2004")] +#endif + public DateTime Val { get; set; } + } + + public class MultipleAttributeModel + { + [Required] + [DataType(DataType.Password)] + public string Val1 { get; set; } = string.Empty; + + [Range(1, 3)] + public int Val2 { get; set; } + + [Range(3, 5)] + public int Val3 { get; set; } + + [Range(5, 9)] + public int Val4 { get; set; } + } + + public class CustomTypeCustomValidationAttributeModel + { + [CustomValidation(typeof(CustomTypeCustomValidationTest), "TestMethod")] + public CustomType? Val { get; set; } + } + + public class CustomType + { + public string Val1 { get; set; } = string.Empty; + public string Val2 { get; set; } = string.Empty; + } + +#pragma warning disable SA1204 // Static elements should appear before instance elements + public static class CustomTypeCustomValidationTest +#pragma warning restore SA1204 // Static elements should appear before instance elements + { + public static ValidationResult? TestMethod(CustomType val, ValidationContext _) + { + if (val.Val1.Equals("Pass", StringComparison.Ordinal) && val.Val2.Equals("Pass", StringComparison.Ordinal)) + { + return ValidationResult.Success; + } + + throw new ValidationException(); + } + } + + public class AttributePropertyModel + { + [Range(1, 3, ErrorMessage = "ErrorMessage")] + public int Val1 { get; set; } + + [Range(1, 3, ErrorMessageResourceType = typeof(SR), ErrorMessageResourceName = "ErrorMessageResourceName")] + public int Val2 { get; set; } + } + + public class TypeWithoutOptionsValidator + { + [Required] + public string? Val1 { get; set; } + + [Range(typeof(DateTime), "1/2/2004", "3/4/2004")] + public DateTime Val2 { get; set; } + + [Microsoft.Extensions.Options.ValidateObjectMembers] + public RangeAttributeModelDouble? YetAnotherComplexVal { get; set; } + } + + public class DerivedModel : RequiredAttributeModel + { + [Required] + public string? DerivedVal { get; set; } + + [Required] + internal virtual int? VirtualValWithAttr { get; set; } + + public virtual int? VirtualValWithoutAttr { get; set; } + + [Required] + public new int? Val { get; set; } + } + + public class LeafModel : DerivedModel + { + internal override int? VirtualValWithAttr { get; set; } + + [Required] + public override int? VirtualValWithoutAttr { get; set; } + } + + public class ComplexModel + { + [Microsoft.Extensions.Options.ValidateObjectMembers] + public RequiredAttributeModel? ComplexVal { get; set; } + + [Microsoft.Extensions.Options.ValidateObjectMembers] + public TypeWithoutOptionsValidator? ValWithoutOptionsValidator { get; set; } + } + + [OptionsValidator] + public partial class RequiredAttributeModelValidator : IValidateOptions + { + } + + [OptionsValidator] + public partial class RegularExpressionAttributeModelValidator : IValidateOptions + { + } + + [OptionsValidator] + public partial class EmailAttributeModelValidator : IValidateOptions + { + } + + [OptionsValidator] + public partial class CustomValidationAttributeModelValidator : IValidateOptions + { + } + + [OptionsValidator] + public partial class DataTypeAttributeModelValidator : IValidateOptions + { + } + + [OptionsValidator] + public partial class RangeAttributeModelIntValidator : IValidateOptions + { + } + + [OptionsValidator] + public partial class RangeAttributeModelDoubleValidator : IValidateOptions + { + } + + [OptionsValidator] + public partial class RangeAttributeModelDateValidator : IValidateOptions + { + } + + [OptionsValidator] + public partial class MultipleAttributeModelValidator : IValidateOptions + { + } + + [OptionsValidator] + public partial class CustomTypeCustomValidationAttributeModelValidator : IValidateOptions + { + } + + [OptionsValidator] + public partial class AttributePropertyModelValidator : IValidateOptions + { + } + + [OptionsValidator] + public partial class DerivedModelValidator : IValidateOptions + { + } + + [OptionsValidator] + public partial class LeafModelValidator : IValidateOptions + { + } + + [OptionsValidator] + internal sealed partial class ComplexModelValidator : IValidateOptions + { + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/MultiModelValidator.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/MultiModelValidator.cs new file mode 100644 index 0000000000000..2ca6c78076631 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/MultiModelValidator.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.Options; + +namespace MultiModelValidator +{ +#pragma warning disable SA1649 +#pragma warning disable SA1402 + + public class FirstModel + { + [Required] + [MinLength(5)] + public string P1 = string.Empty; + + [Microsoft.Extensions.Options.ValidateObjectMembers(typeof(MultiValidator))] + public SecondModel? P2; + } + + public class SecondModel + { + [Required] + [MinLength(5)] + public string P3 = string.Empty; + } + + [OptionsValidator] + public partial struct MultiValidator : IValidateOptions, IValidateOptions + { + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Nested.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Nested.cs new file mode 100644 index 0000000000000..262f2fe283926 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/Nested.cs @@ -0,0 +1,106 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// #if ROSLYN_4_0_OR_GREATER +// #if ROSLYN4_0_OR_GREATER + +using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.Options; + +namespace Nested +{ +#pragma warning disable SA1649 +#pragma warning disable SA1402 + + public static class Container1 + { + public class FirstModel + { + [Required] + [MinLength(5)] + public string P1 { get; set; } = string.Empty; + + [Microsoft.Extensions.Options.ValidateObjectMembers(typeof(Container2.Container3.SecondValidator))] + public SecondModel? P2 { get; set; } + + [Microsoft.Extensions.Options.ValidateObjectMembers] + public ThirdModel P3 { get; set; } + + [Microsoft.Extensions.Options.ValidateObjectMembers(typeof(Container4.Container5.ThirdValidator))] + public SecondModel? P4 { get; set; } + } + + public class SecondModel + { + [Required] + [MinLength(5)] + public string P5 { get; set; } = string.Empty; + } + + public struct ThirdModel + { + public ThirdModel(int _) + { + } + + [Required] + [MinLength(5)] + public string P6 { get; set; } = string.Empty; + } + } + + public static partial class Container2 + { + public partial class Container3 + { + public Container3(int _) + { + // nothing to do + } + + [OptionsValidator] + public partial struct FirstValidator : IValidateOptions + { + } + + [OptionsValidator] + public partial struct SecondValidator : IValidateOptions + { + } + } + } + + public partial record class Container4 + { + public partial record class Container5 + { + public Container5(int _) + { + // nothing to do + } + + [OptionsValidator] + public partial struct ThirdValidator : IValidateOptions + { + } + } + } + + public partial struct Container6 + { + [OptionsValidator] + public partial struct FourthValidator : IValidateOptions + { + } + } + + public partial record struct Container7 + { + [OptionsValidator] + public partial record struct FifthValidator : IValidateOptions + { + } + } +} + +// #endif diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/NoNamespace.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/NoNamespace.cs new file mode 100644 index 0000000000000..1eadac9c45092 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/NoNamespace.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.Options; + +#pragma warning disable SA1649 +#pragma warning disable SA1402 + +public class FirstModelNoNamespace +{ + [Required] + [MinLength(5)] + public string P1 { get; set; } = string.Empty; + + [Microsoft.Extensions.Options.ValidateObjectMembers(typeof(SecondValidatorNoNamespace))] + public SecondModelNoNamespace? P2 { get; set; } + + [Microsoft.Extensions.Options.ValidateObjectMembers] + public ThirdModelNoNamespace? P3 { get; set; } +} + +public class SecondModelNoNamespace +{ + [Required] + [MinLength(5)] + public string P4 { get; set; } = string.Empty; +} + +public class ThirdModelNoNamespace +{ + [Required] + [MinLength(5)] + public string P5 { get; set; } = string.Empty; +} + +[OptionsValidator] +public partial class FirstValidatorNoNamespace : IValidateOptions +{ +} + +[OptionsValidator] +public partial class SecondValidatorNoNamespace : IValidateOptions +{ +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/RandomMembers.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/RandomMembers.cs new file mode 100644 index 0000000000000..b3c5331990a13 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/RandomMembers.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.Options; + +namespace RandomMembers +{ +#pragma warning disable SA1649 +#pragma warning disable SA1402 +#pragma warning disable CA1822 + + public class FirstModel + { + [Required] + [MinLength(5)] + public string? P1 { get; set; } + + public void Foo() + { + throw new NotSupportedException(); + } + + public class Nested + { + } + } + + [OptionsValidator] + public partial class FirstValidator : IValidateOptions + { + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/RecordTypes.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/RecordTypes.cs new file mode 100644 index 0000000000000..4d964ceeb4a38 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/RecordTypes.cs @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// #if ROSLYN_4_0_OR_GREATER + +using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.Options; + +namespace RecordTypes +{ +#pragma warning disable SA1649 + + public record class FirstModel + { + [Required] + [MinLength(5)] + public string P1 { get; set; } = string.Empty; + + [Microsoft.Extensions.Options.ValidateObjectMembers(typeof(SecondValidator))] + public SecondModel? P2 { get; set; } + + [Microsoft.Extensions.Options.ValidateObjectMembers(typeof(ThirdValidator))] + public SecondModel P3 { get; set; } = new SecondModel(); + + [Microsoft.Extensions.Options.ValidateObjectMembers] + public ThirdModel P4 { get; set; } + } + + public record class SecondModel + { + [Required] + [MinLength(5)] + public string P5 { get; set; } = string.Empty; + } + + public record struct ThirdModel + { + [Required] + [MinLength(5)] + public string P6 { get; set; } = string.Empty; + + public ThirdModel(int _) + { + } + + public ThirdModel(object _) + { + } + } + + [OptionsValidator] + public partial record struct FirstValidator : IValidateOptions + { + } + + [OptionsValidator] + public partial record struct SecondValidator : IValidateOptions + { + } + + [OptionsValidator] + public partial record class ThirdValidator : IValidateOptions + { + } +} + +// #endif diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/RepeatedTypes.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/RepeatedTypes.cs new file mode 100644 index 0000000000000..db295a448aa57 --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/RepeatedTypes.cs @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.Options; + +namespace RepeatedTypes +{ +#pragma warning disable SA1649 +#pragma warning disable SA1402 +#pragma warning disable CA1019 + + public class FirstModel + { + [Required] + [Microsoft.Extensions.Options.ValidateObjectMembers] + public SecondModel? P1 { get; set; } + + [Required] + [Microsoft.Extensions.Options.ValidateObjectMembers] + public SecondModel? P2 { get; set; } + + [Required] + [Microsoft.Extensions.Options.ValidateObjectMembers] + public ThirdModel? P3 { get; set; } + } + + public class SecondModel + { + [Required] + [Microsoft.Extensions.Options.ValidateObjectMembers] + public ThirdModel? P4 { get; set; } + } + + public class ThirdModel + { + [Required] + [MinLength(5)] + public string? P5; + } + + [OptionsValidator] + public partial class FirstValidator : IValidateOptions + { + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/SelfValidation.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/SelfValidation.cs new file mode 100644 index 0000000000000..8fe5a9aa41e8a --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/SelfValidation.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using Microsoft.Extensions.Options; + +namespace SelfValidation +{ +#pragma warning disable SA1649 + + public class FirstModel : IValidatableObject + { + [Required] + public string P1 = string.Empty; + + public IEnumerable Validate(ValidationContext validationContext) + { + if (P1.Length < 5) + { + return new[] { new ValidationResult("P1 is not long enough") }; + } + + return Array.Empty(); + } + } + + [OptionsValidator] + public partial struct FirstValidator : IValidateOptions + { + } +} diff --git a/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/ValueTypes.cs b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/ValueTypes.cs new file mode 100644 index 0000000000000..1cdee371d321d --- /dev/null +++ b/src/libraries/Microsoft.Extensions.Options/tests/SourceGenerationTests/TestClasses/ValueTypes.cs @@ -0,0 +1,45 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Extensions.Options; + +namespace ValueTypes +{ +#pragma warning disable SA1649 + + public class FirstModel + { + [Required] + [MinLength(5)] + public string P1 { get; set; } = string.Empty; + + [ValidateObjectMembers] + public SecondModel? P2 { get; set; } + + [ValidateObjectMembers] + public SecondModel P3 { get; set; } + + [ValidateObjectMembers] + [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1125:Use shorthand for nullable types", Justification = "Testing System>Nullable")] + public Nullable P4 { get; set; } + } + + public struct SecondModel + { + [Required] + [MinLength(5)] + public string P4 { get; set; } = string.Empty; + + public SecondModel(object _) + { + } + } + + [OptionsValidator] + public partial struct FirstValidator : IValidateOptions + { + } +}