File: ValidationsGenerator\ValidationsGenerator.Parsable.cs
Web Access
Project: src\src\Http\Http.Extensions\test\Microsoft.AspNetCore.Http.Extensions.Tests.csproj (Microsoft.AspNetCore.Http.Extensions.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
namespace Microsoft.AspNetCore.Http.ValidationsGenerator.Tests;
 
public partial class ValidationsGeneratorTests : ValidationsGeneratorTestBase
{
    [Fact]
    public async Task CanValidateTypeWithParsableProperties()
    {
        // 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.AspNetCore.Http.Validation;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
 
var builder = WebApplication.CreateBuilder();
 
builder.Services.AddValidation();
 
var app = builder.Build();
 
app.MapPost("/complex-type-with-parsable-properties", (ComplexTypeWithParsableProperties complexType) => Results.Ok("Passed"!));
 
app.Run();
 
public class ComplexTypeWithParsableProperties
{
    [RegularExpression("^((?!00000000-0000-0000-0000-000000000000).)*$", ErrorMessage = "Cannot use default Guid")]
    public Guid? GuidWithRegularExpression { get; set; } = default;
 
    [Required]
    public TimeOnly? TimeOnlyWithRequiredValue { get; set; } = TimeOnly.FromDateTime(DateTime.UtcNow);
 
    [Url(ErrorMessage = "The field Url must be a valid URL.")]
    public string? Url { get; set; } = "https://example.com";
 
    [Required]
    [Range(typeof(DateOnly), "2023-01-01", "2025-12-31", ErrorMessage = "Date must be between 2023-01-01 and 2025-12-31")]
    public DateOnly? DateOnlyWithRange { get; set; } = DateOnly.FromDateTime(DateTime.UtcNow);
 
    [Range(typeof(DateTime), "2023-01-01", "2025-12-31", ErrorMessage = "DateTime must be between 2023-01-01 and 2025-12-31")]
    public DateTime? DateTimeWithRange { get; set; } = DateTime.UtcNow;
 
    [Range(typeof(decimal), "0.1", "100.5", ErrorMessage = "Amount must be between 0.1 and 100.5")]
    public decimal? DecimalWithRange { get; set; } = 50.5m;
 
    [Range(0, 12, ErrorMessage = "Hours must be between 0 and 12")]
    public TimeSpan? TimeSpanWithHourRange { get; set; } = TimeSpan.FromHours(12);
 
    [Range(0, 1, ErrorMessage = "Boolean value must be 0 or 1")]
    public bool BooleanWithRange { get; set; } = true;
 
    [RegularExpression(@"^\d+\.\d+\.\d+$", ErrorMessage = "Must be a valid version number (e.g. 1.0.0)")]
    public Version? VersionWithRegex { get; set; } = new Version(1, 0, 0);
}
""";
        await Verify(source, out var compilation);
        await VerifyEndpoint(compilation, "/complex-type-with-parsable-properties", async (endpoint, serviceProvider) =>
        {
            var payload = """
            {
              "TimeOnlyWithRequiredValue": null,
              "IntWithRange": 150,
              "StringWithLength": "AB",
              "Email": "invalid-email",
              "Url": "invalid-url",
              "DateOnlyWithRange": "2026-05-01",
              "DecimalWithRange": "150.75",
              "TimeSpanWithHourRange": "22:00:00",
              "VersionWithRegex": "1.0",
              "EnumProperty": "Invalid"
            }
            """;
            var context = CreateHttpContextWithPayload(payload, serviceProvider);
 
            await endpoint.RequestDelegate(context);
 
            var problemDetails = await AssertBadRequest(context);
 
            // Assert on each error with Assert.Collection
            Assert.Collection(problemDetails.Errors.OrderBy(kvp => kvp.Key),
                error =>
                {
                    Assert.Equal("DateOnlyWithRange", error.Key);
                    Assert.Contains("Date must be between 2023-01-01 and 2025-12-31", error.Value);
                },
                error =>
                {
                    Assert.Equal("DecimalWithRange", error.Key);
                    Assert.Contains("Amount must be between 0.1 and 100.5", error.Value);
                },
                error =>
                {
                    Assert.Equal("TimeOnlyWithRequiredValue", error.Key);
                    Assert.Contains("The TimeOnlyWithRequiredValue field is required.", error.Value);
                },
                error =>
                {
                    Assert.Equal("TimeSpanWithHourRange", error.Key);
                    Assert.Contains("Hours must be between 0 and 12", error.Value);
                },
                error =>
                {
                    Assert.Equal("Url", error.Key);
                    Assert.Contains("The field Url must be a valid URL.", error.Value);
                },
                error =>
                {
                    Assert.Equal("VersionWithRegex", error.Key);
                    Assert.Contains("Must be a valid version number (e.g. 1.0.0)", error.Value);
                }
            );
        });
    }
}