@@ -384,4 +384,146 @@ async Task ValidInputProducesNoWarnings(Endpoint endpoint)
384384 } ) ;
385385
386386 }
387+
388+ [ Fact ]
389+ public async Task CanValidateRecordStructTypes ( )
390+ {
391+ // Arrange
392+ var source = """
393+ using System;
394+ using System.ComponentModel.DataAnnotations;
395+ using System.Collections.Generic;
396+ using System.Threading.Tasks;
397+ using Microsoft.AspNetCore.Builder;
398+ using Microsoft.AspNetCore.Http;
399+ using Microsoft.Extensions.Validation;
400+ using Microsoft.AspNetCore.Routing;
401+ using Microsoft.Extensions.DependencyInjection;
402+
403+ var builder = WebApplication.CreateBuilder();
404+
405+ builder.Services.AddValidation();
406+
407+ var app = builder.Build();
408+
409+ app.MapPost("/validatable-record-struct", (ValidatableRecordStruct validatableRecordStruct) => Results.Ok("Passed"));
410+
411+ app.Run();
412+
413+ public record struct SubRecordStruct([Required] string RequiredProperty, [StringLength(10)] string? StringWithLength);
414+
415+ public record struct ValidatableRecordStruct(
416+ [Range(10, 100)]
417+ int IntegerWithRange,
418+ [Range(10, 100), Display(Name = "Valid identifier")]
419+ int IntegerWithRangeAndDisplayName,
420+ SubRecordStruct SubProperty
421+ );
422+ """ ;
423+ await Verify ( source , out var compilation ) ;
424+ await VerifyEndpoint ( compilation , "/validatable-record-struct" , async ( endpoint , serviceProvider ) =>
425+ {
426+ await InvalidIntegerWithRangeProducesError ( endpoint ) ;
427+ await InvalidIntegerWithRangeAndDisplayNameProducesError ( endpoint ) ;
428+ await InvalidSubPropertyProducesError ( endpoint ) ;
429+ await ValidInputProducesNoWarnings ( endpoint ) ;
430+
431+ async Task InvalidIntegerWithRangeProducesError ( Endpoint endpoint )
432+ {
433+ var payload = """
434+ {
435+ "IntegerWithRange": 5,
436+ "IntegerWithRangeAndDisplayName": 50,
437+ "SubProperty": {
438+ "RequiredProperty": "valid",
439+ "StringWithLength": "valid"
440+ }
441+ }
442+ """ ;
443+ var context = CreateHttpContextWithPayload ( payload , serviceProvider ) ;
444+
445+ await endpoint . RequestDelegate ( context ) ;
446+
447+ var problemDetails = await AssertBadRequest ( context ) ;
448+ Assert . Collection ( problemDetails . Errors , kvp =>
449+ {
450+ Assert . Equal ( "IntegerWithRange" , kvp . Key ) ;
451+ Assert . Equal ( "The field IntegerWithRange must be between 10 and 100." , kvp . Value . Single ( ) ) ;
452+ } ) ;
453+ }
454+
455+ async Task InvalidIntegerWithRangeAndDisplayNameProducesError ( Endpoint endpoint )
456+ {
457+ var payload = """
458+ {
459+ "IntegerWithRange": 50,
460+ "IntegerWithRangeAndDisplayName": 5,
461+ "SubProperty": {
462+ "RequiredProperty": "valid",
463+ "StringWithLength": "valid"
464+ }
465+ }
466+ """ ;
467+ var context = CreateHttpContextWithPayload ( payload , serviceProvider ) ;
468+
469+ await endpoint . RequestDelegate ( context ) ;
470+
471+ var problemDetails = await AssertBadRequest ( context ) ;
472+ Assert . Collection ( problemDetails . Errors , kvp =>
473+ {
474+ Assert . Equal ( "IntegerWithRangeAndDisplayName" , kvp . Key ) ;
475+ Assert . Equal ( "The field Valid identifier must be between 10 and 100." , kvp . Value . Single ( ) ) ;
476+ } ) ;
477+ }
478+
479+ async Task InvalidSubPropertyProducesError ( Endpoint endpoint )
480+ {
481+ var payload = """
482+ {
483+ "IntegerWithRange": 50,
484+ "IntegerWithRangeAndDisplayName": 50,
485+ "SubProperty": {
486+ "RequiredProperty": "",
487+ "StringWithLength": "way-too-long"
488+ }
489+ }
490+ """ ;
491+ var context = CreateHttpContextWithPayload ( payload , serviceProvider ) ;
492+
493+ await endpoint . RequestDelegate ( context ) ;
494+
495+ var problemDetails = await AssertBadRequest ( context ) ;
496+ Assert . Collection ( problemDetails . Errors ,
497+ kvp =>
498+ {
499+ Assert . Equal ( "SubProperty.RequiredProperty" , kvp . Key ) ;
500+ Assert . Equal ( "The RequiredProperty field is required." , kvp . Value . Single ( ) ) ;
501+ } ,
502+ kvp =>
503+ {
504+ Assert . Equal ( "SubProperty.StringWithLength" , kvp . Key ) ;
505+ Assert . Equal ( "The field StringWithLength must be a string with a maximum length of 10." , kvp . Value . Single ( ) ) ;
506+ } ) ;
507+ }
508+
509+ async Task ValidInputProducesNoWarnings ( Endpoint endpoint )
510+ {
511+ var payload = """
512+ {
513+ "IntegerWithRange": 50,
514+ "IntegerWithRangeAndDisplayName": 50,
515+ "SubProperty": {
516+ "RequiredProperty": "valid",
517+ "StringWithLength": "valid"
518+ }
519+ }
520+ """ ;
521+ var context = CreateHttpContextWithPayload ( payload , serviceProvider ) ;
522+ await endpoint . RequestDelegate ( context ) ;
523+
524+ Assert . Equal ( 200 , context . Response . StatusCode ) ;
525+ }
526+ } ) ;
527+
528+ }
387529}
0 commit comments