From 690e927d6654da34966cc889d149521740d13330 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 3 Aug 2025 01:33:56 +0000 Subject: [PATCH 1/5] Initial plan From 07e71a67a6d99f334c7bf36b1054279dc655fc07 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 3 Aug 2025 01:46:25 +0000 Subject: [PATCH 2/5] Add JsonIgnore attribute support to validation generator Co-authored-by: captainsafia <1857993+captainsafia@users.noreply.github.com> --- src/Shared/RoslynUtils/WellKnownTypeData.cs | 2 + .../gen/Extensions/ITypeSymbolExtensions.cs | 24 +++ src/Validation/gen/Models/RequiredSymbols.cs | 1 + .../ValidationsGenerator.TypesParser.cs | 14 ++ .../ValidationsGenerator.ComplexType.cs | 88 ++++++++++ ...nore#ValidatableInfoResolver.g.received.cs | 164 ++++++++++++++++++ 6 files changed, 293 insertions(+) create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs diff --git a/src/Shared/RoslynUtils/WellKnownTypeData.cs b/src/Shared/RoslynUtils/WellKnownTypeData.cs index 1afb045fc713..1549ad178eac 100644 --- a/src/Shared/RoslynUtils/WellKnownTypeData.cs +++ b/src/Shared/RoslynUtils/WellKnownTypeData.cs @@ -119,6 +119,7 @@ public enum WellKnownType Microsoft_AspNetCore_Authorization_IAuthorizeData, System_AttributeUsageAttribute, System_Text_Json_Serialization_JsonDerivedTypeAttribute, + System_Text_Json_Serialization_JsonIgnoreAttribute, System_ComponentModel_DataAnnotations_DisplayAttribute, System_ComponentModel_DataAnnotations_ValidationAttribute, System_ComponentModel_DataAnnotations_RequiredAttribute, @@ -240,6 +241,7 @@ public enum WellKnownType "Microsoft.AspNetCore.Authorization.IAuthorizeData", "System.AttributeUsageAttribute", "System.Text.Json.Serialization.JsonDerivedTypeAttribute", + "System.Text.Json.Serialization.JsonIgnoreAttribute", "System.ComponentModel.DataAnnotations.DisplayAttribute", "System.ComponentModel.DataAnnotations.ValidationAttribute", "System.ComponentModel.DataAnnotations.RequiredAttribute", diff --git a/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs b/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs index 408ec7defb89..e5cf8f06e882 100644 --- a/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs +++ b/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs @@ -152,4 +152,28 @@ attr.AttributeClass is not null && (attr.AttributeClass.ImplementsInterface(fromServiceMetadataSymbol) || SymbolEqualityComparer.Default.Equals(attr.AttributeClass, fromKeyedServiceAttributeSymbol))); } + + /// + /// Checks if the property is marked with [JsonIgnore] attribute. + /// + /// The property to check. + /// The symbol representing the [JsonIgnore] attribute. + internal static bool IsJsonIgnoredProperty(this IPropertySymbol property, INamedTypeSymbol jsonIgnoreAttributeSymbol) + { + return property.GetAttributes().Any(attr => + attr.AttributeClass is not null && + SymbolEqualityComparer.Default.Equals(attr.AttributeClass, jsonIgnoreAttributeSymbol)); + } + + /// + /// Checks if the parameter is marked with [JsonIgnore] attribute. + /// + /// The parameter to check. + /// The symbol representing the [JsonIgnore] attribute. + internal static bool IsJsonIgnoredParameter(this IParameterSymbol parameter, INamedTypeSymbol jsonIgnoreAttributeSymbol) + { + return parameter.GetAttributes().Any(attr => + attr.AttributeClass is not null && + SymbolEqualityComparer.Default.Equals(attr.AttributeClass, jsonIgnoreAttributeSymbol)); + } } diff --git a/src/Validation/gen/Models/RequiredSymbols.cs b/src/Validation/gen/Models/RequiredSymbols.cs index 51f8c92ccf9e..f6d48f40eb7a 100644 --- a/src/Validation/gen/Models/RequiredSymbols.cs +++ b/src/Validation/gen/Models/RequiredSymbols.cs @@ -11,6 +11,7 @@ internal sealed record class RequiredSymbols( INamedTypeSymbol IEnumerable, INamedTypeSymbol IValidatableObject, INamedTypeSymbol JsonDerivedTypeAttribute, + INamedTypeSymbol JsonIgnoreAttribute, INamedTypeSymbol RequiredAttribute, INamedTypeSymbol CustomValidationAttribute, INamedTypeSymbol HttpContext, diff --git a/src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs b/src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs index b0617d79b3a6..51088713f3ef 100644 --- a/src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs +++ b/src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs @@ -114,6 +114,8 @@ internal ImmutableArray ExtractValidatableMembers(ITypeSymb WellKnownTypeData.WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromServiceMetadata); var fromKeyedServiceAttributeSymbol = wellKnownTypes.Get( WellKnownTypeData.WellKnownType.Microsoft_Extensions_DependencyInjection_FromKeyedServicesAttribute); + var jsonIgnoreAttributeSymbol = wellKnownTypes.Get( + WellKnownTypeData.WellKnownType.System_Text_Json_Serialization_JsonIgnoreAttribute); // Special handling for record types to extract properties from // the primary constructor. @@ -148,6 +150,12 @@ internal ImmutableArray ExtractValidatableMembers(ITypeSymb continue; } + // Skip parameters that have JsonIgnore attribute + if (parameter.IsJsonIgnoredParameter(jsonIgnoreAttributeSymbol)) + { + continue; + } + // Check if the property's type is validatable, this resolves // validatable types in the inheritance hierarchy var hasValidatableType = TryExtractValidatableType( @@ -186,6 +194,12 @@ internal ImmutableArray ExtractValidatableMembers(ITypeSymb continue; } + // Skip properties that have JsonIgnore attribute + if (member.IsJsonIgnoredProperty(jsonIgnoreAttributeSymbol)) + { + continue; + } + var hasValidatableType = TryExtractValidatableType(member.Type, wellKnownTypes, ref validatableTypes, ref visitedTypes); var attributes = ExtractValidationAttributes(member, wellKnownTypes, out var isRequired); diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.ComplexType.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.ComplexType.cs index 46158b90632d..08d86a052083 100644 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.ComplexType.cs +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.ComplexType.cs @@ -7,6 +7,94 @@ namespace Microsoft.Extensions.Validation.GeneratorTests; public partial class ValidationsGeneratorTests : ValidationsGeneratorTestBase { + [Fact] + public async Task CanValidateComplexTypesWithJsonIgnore() + { + // Arrange + var source = """ +using System; +using System.ComponentModel.DataAnnotations; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Validation; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Mvc; +using System.Text.Json.Serialization; + +var builder = WebApplication.CreateBuilder(); + +builder.Services.AddValidation(); + +var app = builder.Build(); + +app.MapPost("/complex-type-with-json-ignore", (ComplexTypeWithJsonIgnore complexType) => Results.Ok("Passed"!)); + +app.Run(); + +public class ComplexTypeWithJsonIgnore +{ + [Range(10, 100)] + public int ValidatedProperty { get; set; } = 10; + + [JsonIgnore] + [Required] // This should be ignored because of [JsonIgnore] + public string IgnoredProperty { get; set; } = null!; + + [JsonIgnore] + public CircularReferenceType CircularReference { get; set; } = new CircularReferenceType(); +} + +public class CircularReferenceType +{ + [JsonIgnore] + public ComplexTypeWithJsonIgnore Parent { get; set; } = new ComplexTypeWithJsonIgnore(); + + public string Name { get; set; } = "test"; +} +"""; + await Verify(source, out var compilation); + await VerifyEndpoint(compilation, "/complex-type-with-json-ignore", async (endpoint, serviceProvider) => + { + await ValidInputWithJsonIgnoreProducesNoWarnings(endpoint); + await InvalidValidatedPropertyProducesError(endpoint); + + async Task ValidInputWithJsonIgnoreProducesNoWarnings(Endpoint endpoint) + { + var payload = """ + { + "ValidatedProperty": 50 + } + """; + var context = CreateHttpContextWithPayload(payload, serviceProvider); + await endpoint.RequestDelegate(context); + + Assert.Equal(200, context.Response.StatusCode); + } + + async Task InvalidValidatedPropertyProducesError(Endpoint endpoint) + { + var payload = """ + { + "ValidatedProperty": 5 + } + """; + var context = CreateHttpContextWithPayload(payload, serviceProvider); + + await endpoint.RequestDelegate(context); + + var problemDetails = await AssertBadRequest(context); + Assert.Collection(problemDetails.Errors, kvp => + { + Assert.Equal("ValidatedProperty", kvp.Key); + Assert.Equal("The field ValidatedProperty must be between 10 and 100.", kvp.Value.Single()); + }); + } + }); + } + [Fact] public async Task CanValidateComplexTypes() { diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs new file mode 100644 index 000000000000..506f94c467a3 --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs @@ -0,0 +1,164 @@ +//HintName: ValidatableInfoResolver.g.cs +#nullable enable annotations +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +#nullable enable +#pragma warning disable ASP0029 + +namespace System.Runtime.CompilerServices +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : System.Attribute + { + public InterceptsLocationAttribute(int version, string data) + { + } + } +} + +namespace Microsoft.Extensions.Validation.Generated +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file sealed class GeneratedValidatablePropertyInfo : global::Microsoft.Extensions.Validation.ValidatablePropertyInfo + { + public GeneratedValidatablePropertyInfo( + [param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] + global::System.Type containingType, + global::System.Type propertyType, + string name, + string displayName) : base(containingType, propertyType, name, displayName) + { + ContainingType = containingType; + Name = name; + } + + [global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] + internal global::System.Type ContainingType { get; } + internal string Name { get; } + + protected override global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetValidationAttributes() + => ValidationAttributeCache.GetValidationAttributes(ContainingType, Name); + } + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file sealed class GeneratedValidatableTypeInfo : global::Microsoft.Extensions.Validation.ValidatableTypeInfo + { + public GeneratedValidatableTypeInfo( + [param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.Interfaces)] + global::System.Type type, + ValidatablePropertyInfo[] members) : base(type, members) { } + } + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file class GeneratedValidatableInfoResolver : global::Microsoft.Extensions.Validation.IValidatableInfoResolver + { + public bool TryGetValidatableTypeInfo(global::System.Type type, [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Microsoft.Extensions.Validation.IValidatableInfo? validatableInfo) + { + validatableInfo = null; + if (type == typeof(global::ComplexTypeWithJsonIgnore)) + { + validatableInfo = new GeneratedValidatableTypeInfo( + type: typeof(global::ComplexTypeWithJsonIgnore), + members: [ + new GeneratedValidatablePropertyInfo( + containingType: typeof(global::ComplexTypeWithJsonIgnore), + propertyType: typeof(int), + name: "ValidatedProperty", + displayName: "ValidatedProperty" + ), + ] + ); + return true; + } + + return false; + } + + // No-ops, rely on runtime code for ParameterInfo-based resolution + public bool TryGetValidatableParameterInfo(global::System.Reflection.ParameterInfo parameterInfo, [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Microsoft.Extensions.Validation.IValidatableInfo? validatableInfo) + { + validatableInfo = null; + return false; + } + } + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file static class GeneratedServiceCollectionExtensions + { + [InterceptsLocation] + public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddValidation(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::System.Action? configureOptions = null) + { + // Use non-extension method to avoid infinite recursion. + return global::Microsoft.Extensions.DependencyInjection.ValidationServiceCollectionExtensions.AddValidation(services, options => + { + options.Resolvers.Insert(0, new GeneratedValidatableInfoResolver()); + if (configureOptions is not null) + { + configureOptions(options); + } + }); + } + } + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file static class ValidationAttributeCache + { + private sealed record CacheKey( + [param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] + [property: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] + global::System.Type ContainingType, + string PropertyName); + private static readonly global::System.Collections.Concurrent.ConcurrentDictionary _cache = new(); + + public static global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetValidationAttributes( + [global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] + global::System.Type containingType, + string propertyName) + { + var key = new CacheKey(containingType, propertyName); + return _cache.GetOrAdd(key, static k => + { + var results = new global::System.Collections.Generic.List(); + + // Get attributes from the property + var property = k.ContainingType.GetProperty(k.PropertyName); + if (property != null) + { + var propertyAttributes = global::System.Reflection.CustomAttributeExtensions + .GetCustomAttributes(property, inherit: true); + + results.AddRange(propertyAttributes); + } + + // Check constructors for parameters that match the property name + // to handle record scenarios + foreach (var constructor in k.ContainingType.GetConstructors()) + { + // Look for parameter with matching name (case insensitive) + var parameter = global::System.Linq.Enumerable.FirstOrDefault( + constructor.GetParameters(), + p => string.Equals(p.Name, k.PropertyName, global::System.StringComparison.OrdinalIgnoreCase)); + + if (parameter != null) + { + var paramAttributes = global::System.Reflection.CustomAttributeExtensions + .GetCustomAttributes(parameter, inherit: true); + + results.AddRange(paramAttributes); + + break; + } + } + + return results.ToArray(); + }); + } + } +} \ No newline at end of file From 2247d7fff68d35e4cd67b738cd41c49317e3b694 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 3 Aug 2025 01:51:17 +0000 Subject: [PATCH 3/5] Fix JsonIgnore test case to prevent circular reference stack overflow Co-authored-by: captainsafia <1857993+captainsafia@users.noreply.github.com> --- .../ValidationsGenerator.ComplexType.cs | 4 ++-- ...TypesWithJsonIgnore#ValidatableInfoResolver.g.verified.cs} | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) rename src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/{ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs => ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.verified.cs} (99%) diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.ComplexType.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.ComplexType.cs index 08d86a052083..74f588a7ebd0 100644 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.ComplexType.cs +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.ComplexType.cs @@ -44,13 +44,13 @@ public class ComplexTypeWithJsonIgnore public string IgnoredProperty { get; set; } = null!; [JsonIgnore] - public CircularReferenceType CircularReference { get; set; } = new CircularReferenceType(); + public CircularReferenceType? CircularReference { get; set; } } public class CircularReferenceType { [JsonIgnore] - public ComplexTypeWithJsonIgnore Parent { get; set; } = new ComplexTypeWithJsonIgnore(); + public ComplexTypeWithJsonIgnore? Parent { get; set; } public string Name { get; set; } = "test"; } diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.verified.cs similarity index 99% rename from src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs rename to src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.verified.cs index 506f94c467a3..e82395ab046d 100644 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: ValidatableInfoResolver.g.cs +//HintName: ValidatableInfoResolver.g.cs #nullable enable annotations //------------------------------------------------------------------------------ // From d038ce74211883043059be4569b1c8464b1bc41f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 3 Aug 2025 02:27:25 +0000 Subject: [PATCH 4/5] Update CanValidateComplexTypesWithJsonIgnore test with record type cases Co-authored-by: captainsafia <1857993+captainsafia@users.noreply.github.com> --- .../ValidationsGenerator.ComplexType.cs | 61 +++++- ...nore#ValidatableInfoResolver.g.received.cs | 179 ++++++++++++++++++ 2 files changed, 239 insertions(+), 1 deletion(-) create mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.ComplexType.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.ComplexType.cs index 74f588a7ebd0..0a5f023ba00b 100644 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.ComplexType.cs +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/ValidationsGenerator.ComplexType.cs @@ -31,6 +31,7 @@ public async Task CanValidateComplexTypesWithJsonIgnore() var app = builder.Build(); app.MapPost("/complex-type-with-json-ignore", (ComplexTypeWithJsonIgnore complexType) => Results.Ok("Passed"!)); +app.MapPost("/record-type-with-json-ignore", (RecordTypeWithJsonIgnore recordType) => Results.Ok("Passed"!)); app.Run(); @@ -54,6 +55,27 @@ public class CircularReferenceType public string Name { get; set; } = "test"; } + +public record RecordTypeWithJsonIgnore +{ + [Range(10, 100)] + public int ValidatedProperty { get; set; } = 10; + + [JsonIgnore] + [Required] // This should be ignored because of [JsonIgnore] + public string IgnoredProperty { get; set; } = null!; + + [JsonIgnore] + public CircularReferenceRecord? CircularReference { get; set; } +} + +public record CircularReferenceRecord +{ + [JsonIgnore] + public RecordTypeWithJsonIgnore? Parent { get; set; } + + public string Name { get; set; } = "test"; +} """; await Verify(source, out var compilation); await VerifyEndpoint(compilation, "/complex-type-with-json-ignore", async (endpoint, serviceProvider) => @@ -93,8 +115,45 @@ async Task InvalidValidatedPropertyProducesError(Endpoint endpoint) }); } }); - } + + await VerifyEndpoint(compilation, "/record-type-with-json-ignore", async (endpoint, serviceProvider) => + { + await ValidInputWithJsonIgnoreProducesNoWarningsForRecord(endpoint); + await InvalidValidatedPropertyProducesErrorForRecord(endpoint); + + async Task ValidInputWithJsonIgnoreProducesNoWarningsForRecord(Endpoint endpoint) + { + var payload = """ + { + "ValidatedProperty": 50 + } + """; + var context = CreateHttpContextWithPayload(payload, serviceProvider); + await endpoint.RequestDelegate(context); + Assert.Equal(200, context.Response.StatusCode); + } + + async Task InvalidValidatedPropertyProducesErrorForRecord(Endpoint endpoint) + { + var payload = """ + { + "ValidatedProperty": 5 + } + """; + var context = CreateHttpContextWithPayload(payload, serviceProvider); + + await endpoint.RequestDelegate(context); + + var problemDetails = await AssertBadRequest(context); + Assert.Collection(problemDetails.Errors, kvp => + { + Assert.Equal("ValidatedProperty", kvp.Key); + Assert.Equal("The field ValidatedProperty must be between 10 and 100.", kvp.Value.Single()); + }); + } + }); + } [Fact] public async Task CanValidateComplexTypes() { diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs new file mode 100644 index 000000000000..496844084845 --- /dev/null +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs @@ -0,0 +1,179 @@ +//HintName: ValidatableInfoResolver.g.cs +#nullable enable annotations +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +#nullable enable +#pragma warning disable ASP0029 + +namespace System.Runtime.CompilerServices +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] + file sealed class InterceptsLocationAttribute : System.Attribute + { + public InterceptsLocationAttribute(int version, string data) + { + } + } +} + +namespace Microsoft.Extensions.Validation.Generated +{ + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file sealed class GeneratedValidatablePropertyInfo : global::Microsoft.Extensions.Validation.ValidatablePropertyInfo + { + public GeneratedValidatablePropertyInfo( + [param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] + global::System.Type containingType, + global::System.Type propertyType, + string name, + string displayName) : base(containingType, propertyType, name, displayName) + { + ContainingType = containingType; + Name = name; + } + + [global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] + internal global::System.Type ContainingType { get; } + internal string Name { get; } + + protected override global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetValidationAttributes() + => ValidationAttributeCache.GetValidationAttributes(ContainingType, Name); + } + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file sealed class GeneratedValidatableTypeInfo : global::Microsoft.Extensions.Validation.ValidatableTypeInfo + { + public GeneratedValidatableTypeInfo( + [param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.Interfaces)] + global::System.Type type, + ValidatablePropertyInfo[] members) : base(type, members) { } + } + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file class GeneratedValidatableInfoResolver : global::Microsoft.Extensions.Validation.IValidatableInfoResolver + { + public bool TryGetValidatableTypeInfo(global::System.Type type, [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Microsoft.Extensions.Validation.IValidatableInfo? validatableInfo) + { + validatableInfo = null; + if (type == typeof(global::ComplexTypeWithJsonIgnore)) + { + validatableInfo = new GeneratedValidatableTypeInfo( + type: typeof(global::ComplexTypeWithJsonIgnore), + members: [ + new GeneratedValidatablePropertyInfo( + containingType: typeof(global::ComplexTypeWithJsonIgnore), + propertyType: typeof(int), + name: "ValidatedProperty", + displayName: "ValidatedProperty" + ), + ] + ); + return true; + } + if (type == typeof(global::RecordTypeWithJsonIgnore)) + { + validatableInfo = new GeneratedValidatableTypeInfo( + type: typeof(global::RecordTypeWithJsonIgnore), + members: [ + new GeneratedValidatablePropertyInfo( + containingType: typeof(global::RecordTypeWithJsonIgnore), + propertyType: typeof(int), + name: "ValidatedProperty", + displayName: "ValidatedProperty" + ), + ] + ); + return true; + } + + return false; + } + + // No-ops, rely on runtime code for ParameterInfo-based resolution + public bool TryGetValidatableParameterInfo(global::System.Reflection.ParameterInfo parameterInfo, [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Microsoft.Extensions.Validation.IValidatableInfo? validatableInfo) + { + validatableInfo = null; + return false; + } + } + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file static class GeneratedServiceCollectionExtensions + { + [InterceptsLocation] + public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddValidation(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::System.Action? configureOptions = null) + { + // Use non-extension method to avoid infinite recursion. + return global::Microsoft.Extensions.DependencyInjection.ValidationServiceCollectionExtensions.AddValidation(services, options => + { + options.Resolvers.Insert(0, new GeneratedValidatableInfoResolver()); + if (configureOptions is not null) + { + configureOptions(options); + } + }); + } + } + + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + file static class ValidationAttributeCache + { + private sealed record CacheKey( + [param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] + [property: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] + global::System.Type ContainingType, + string PropertyName); + private static readonly global::System.Collections.Concurrent.ConcurrentDictionary _cache = new(); + + public static global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetValidationAttributes( + [global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] + global::System.Type containingType, + string propertyName) + { + var key = new CacheKey(containingType, propertyName); + return _cache.GetOrAdd(key, static k => + { + var results = new global::System.Collections.Generic.List(); + + // Get attributes from the property + var property = k.ContainingType.GetProperty(k.PropertyName); + if (property != null) + { + var propertyAttributes = global::System.Reflection.CustomAttributeExtensions + .GetCustomAttributes(property, inherit: true); + + results.AddRange(propertyAttributes); + } + + // Check constructors for parameters that match the property name + // to handle record scenarios + foreach (var constructor in k.ContainingType.GetConstructors()) + { + // Look for parameter with matching name (case insensitive) + var parameter = global::System.Linq.Enumerable.FirstOrDefault( + constructor.GetParameters(), + p => string.Equals(p.Name, k.PropertyName, global::System.StringComparison.OrdinalIgnoreCase)); + + if (parameter != null) + { + var paramAttributes = global::System.Reflection.CustomAttributeExtensions + .GetCustomAttributes(parameter, inherit: true); + + results.AddRange(paramAttributes); + + break; + } + } + + return results.ToArray(); + }); + } + } +} \ No newline at end of file From cabe2b1c5c5a18317bb4a55796f19c8d027dfdd3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 3 Aug 2025 03:08:27 +0000 Subject: [PATCH 5/5] Remove unused IsJsonIgnoredParameter method and clean up test snapshots - Remove IsJsonIgnoredParameter method since JsonIgnore can only be applied to properties and fields, not constructor parameters - Update record parameter validation to check corresponding property for JsonIgnore instead - Delete stale received snapshot file - Update verified snapshot to include record type validation results Co-authored-by: captainsafia <1857993+captainsafia@users.noreply.github.com> --- .../gen/Extensions/ITypeSymbolExtensions.cs | 12 -- .../ValidationsGenerator.TypesParser.cs | 4 +- ...nore#ValidatableInfoResolver.g.received.cs | 179 ------------------ ...nore#ValidatableInfoResolver.g.verified.cs | 17 +- 4 files changed, 18 insertions(+), 194 deletions(-) delete mode 100644 src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs diff --git a/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs b/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs index e5cf8f06e882..364ee29b0b40 100644 --- a/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs +++ b/src/Validation/gen/Extensions/ITypeSymbolExtensions.cs @@ -164,16 +164,4 @@ internal static bool IsJsonIgnoredProperty(this IPropertySymbol property, INamed attr.AttributeClass is not null && SymbolEqualityComparer.Default.Equals(attr.AttributeClass, jsonIgnoreAttributeSymbol)); } - - /// - /// Checks if the parameter is marked with [JsonIgnore] attribute. - /// - /// The parameter to check. - /// The symbol representing the [JsonIgnore] attribute. - internal static bool IsJsonIgnoredParameter(this IParameterSymbol parameter, INamedTypeSymbol jsonIgnoreAttributeSymbol) - { - return parameter.GetAttributes().Any(attr => - attr.AttributeClass is not null && - SymbolEqualityComparer.Default.Equals(attr.AttributeClass, jsonIgnoreAttributeSymbol)); - } } diff --git a/src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs b/src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs index 51088713f3ef..20e761deb12d 100644 --- a/src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs +++ b/src/Validation/gen/Parsers/ValidationsGenerator.TypesParser.cs @@ -150,8 +150,8 @@ internal ImmutableArray ExtractValidatableMembers(ITypeSymb continue; } - // Skip parameters that have JsonIgnore attribute - if (parameter.IsJsonIgnoredParameter(jsonIgnoreAttributeSymbol)) + // Skip properties that have JsonIgnore attribute + if (correspondingProperty.IsJsonIgnoredProperty(jsonIgnoreAttributeSymbol)) { continue; } diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs deleted file mode 100644 index 496844084845..000000000000 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.received.cs +++ /dev/null @@ -1,179 +0,0 @@ -//HintName: ValidatableInfoResolver.g.cs -#nullable enable annotations -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ -#nullable enable -#pragma warning disable ASP0029 - -namespace System.Runtime.CompilerServices -{ - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] - [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] - file sealed class InterceptsLocationAttribute : System.Attribute - { - public InterceptsLocationAttribute(int version, string data) - { - } - } -} - -namespace Microsoft.Extensions.Validation.Generated -{ - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] - file sealed class GeneratedValidatablePropertyInfo : global::Microsoft.Extensions.Validation.ValidatablePropertyInfo - { - public GeneratedValidatablePropertyInfo( - [param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] - global::System.Type containingType, - global::System.Type propertyType, - string name, - string displayName) : base(containingType, propertyType, name, displayName) - { - ContainingType = containingType; - Name = name; - } - - [global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] - internal global::System.Type ContainingType { get; } - internal string Name { get; } - - protected override global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetValidationAttributes() - => ValidationAttributeCache.GetValidationAttributes(ContainingType, Name); - } - - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] - file sealed class GeneratedValidatableTypeInfo : global::Microsoft.Extensions.Validation.ValidatableTypeInfo - { - public GeneratedValidatableTypeInfo( - [param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.Interfaces)] - global::System.Type type, - ValidatablePropertyInfo[] members) : base(type, members) { } - } - - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] - file class GeneratedValidatableInfoResolver : global::Microsoft.Extensions.Validation.IValidatableInfoResolver - { - public bool TryGetValidatableTypeInfo(global::System.Type type, [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Microsoft.Extensions.Validation.IValidatableInfo? validatableInfo) - { - validatableInfo = null; - if (type == typeof(global::ComplexTypeWithJsonIgnore)) - { - validatableInfo = new GeneratedValidatableTypeInfo( - type: typeof(global::ComplexTypeWithJsonIgnore), - members: [ - new GeneratedValidatablePropertyInfo( - containingType: typeof(global::ComplexTypeWithJsonIgnore), - propertyType: typeof(int), - name: "ValidatedProperty", - displayName: "ValidatedProperty" - ), - ] - ); - return true; - } - if (type == typeof(global::RecordTypeWithJsonIgnore)) - { - validatableInfo = new GeneratedValidatableTypeInfo( - type: typeof(global::RecordTypeWithJsonIgnore), - members: [ - new GeneratedValidatablePropertyInfo( - containingType: typeof(global::RecordTypeWithJsonIgnore), - propertyType: typeof(int), - name: "ValidatedProperty", - displayName: "ValidatedProperty" - ), - ] - ); - return true; - } - - return false; - } - - // No-ops, rely on runtime code for ParameterInfo-based resolution - public bool TryGetValidatableParameterInfo(global::System.Reflection.ParameterInfo parameterInfo, [global::System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out global::Microsoft.Extensions.Validation.IValidatableInfo? validatableInfo) - { - validatableInfo = null; - return false; - } - } - - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] - file static class GeneratedServiceCollectionExtensions - { - [InterceptsLocation] - public static global::Microsoft.Extensions.DependencyInjection.IServiceCollection AddValidation(this global::Microsoft.Extensions.DependencyInjection.IServiceCollection services, global::System.Action? configureOptions = null) - { - // Use non-extension method to avoid infinite recursion. - return global::Microsoft.Extensions.DependencyInjection.ValidationServiceCollectionExtensions.AddValidation(services, options => - { - options.Resolvers.Insert(0, new GeneratedValidatableInfoResolver()); - if (configureOptions is not null) - { - configureOptions(options); - } - }); - } - } - - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.Extensions.Validation.ValidationsGenerator, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] - file static class ValidationAttributeCache - { - private sealed record CacheKey( - [param: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] - [property: global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] - global::System.Type ContainingType, - string PropertyName); - private static readonly global::System.Collections.Concurrent.ConcurrentDictionary _cache = new(); - - public static global::System.ComponentModel.DataAnnotations.ValidationAttribute[] GetValidationAttributes( - [global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMembers(global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicProperties | global::System.Diagnostics.CodeAnalysis.DynamicallyAccessedMemberTypes.PublicConstructors)] - global::System.Type containingType, - string propertyName) - { - var key = new CacheKey(containingType, propertyName); - return _cache.GetOrAdd(key, static k => - { - var results = new global::System.Collections.Generic.List(); - - // Get attributes from the property - var property = k.ContainingType.GetProperty(k.PropertyName); - if (property != null) - { - var propertyAttributes = global::System.Reflection.CustomAttributeExtensions - .GetCustomAttributes(property, inherit: true); - - results.AddRange(propertyAttributes); - } - - // Check constructors for parameters that match the property name - // to handle record scenarios - foreach (var constructor in k.ContainingType.GetConstructors()) - { - // Look for parameter with matching name (case insensitive) - var parameter = global::System.Linq.Enumerable.FirstOrDefault( - constructor.GetParameters(), - p => string.Equals(p.Name, k.PropertyName, global::System.StringComparison.OrdinalIgnoreCase)); - - if (parameter != null) - { - var paramAttributes = global::System.Reflection.CustomAttributeExtensions - .GetCustomAttributes(parameter, inherit: true); - - results.AddRange(paramAttributes); - - break; - } - } - - return results.ToArray(); - }); - } - } -} \ No newline at end of file diff --git a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.verified.cs b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.verified.cs index e82395ab046d..496844084845 100644 --- a/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.verified.cs +++ b/src/Validation/test/Microsoft.Extensions.Validation.GeneratorTests/snapshots/ValidationsGeneratorTests.CanValidateComplexTypesWithJsonIgnore#ValidatableInfoResolver.g.verified.cs @@ -1,4 +1,4 @@ -//HintName: ValidatableInfoResolver.g.cs +//HintName: ValidatableInfoResolver.g.cs #nullable enable annotations //------------------------------------------------------------------------------ // @@ -77,6 +77,21 @@ public bool TryGetValidatableTypeInfo(global::System.Type type, [global::System. ); return true; } + if (type == typeof(global::RecordTypeWithJsonIgnore)) + { + validatableInfo = new GeneratedValidatableTypeInfo( + type: typeof(global::RecordTypeWithJsonIgnore), + members: [ + new GeneratedValidatablePropertyInfo( + containingType: typeof(global::RecordTypeWithJsonIgnore), + propertyType: typeof(int), + name: "ValidatedProperty", + displayName: "ValidatedProperty" + ), + ] + ); + return true; + } return false; }