Skip to content

Commit

Permalink
Add initial validations generator for minimal APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
captainsafia committed Jan 10, 2025
1 parent f7ff82f commit ab949d5
Show file tree
Hide file tree
Showing 81 changed files with 4,847 additions and 31 deletions.
57 changes: 38 additions & 19 deletions AspNetCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -1732,7 +1732,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.Tests", "src\Servers\Kestrel\Transport.NamedPipes\test\Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.Tests.csproj", "{97C7D2A4-87E5-4A4A-A170-D736427D5C21}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.RequestDelegateGenerator", "src\Http\Http.Extensions\gen\Microsoft.AspNetCore.Http.RequestDelegateGenerator.csproj", "{4730F56D-24EF-4BB2-AA75-862E31205F3A}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.RequestDelegateGenerator", "src\Http\Http.Extensions\gen\RequestDelegateGenerator\Microsoft.AspNetCore.Http.RequestDelegateGenerator.csproj", "{4730F56D-24EF-4BB2-AA75-862E31205F3A}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "QuickGrid", "QuickGrid", "{C406D9E0-1585-43F9-AA8F-D468AF84A996}"
EndProject
Expand Down Expand Up @@ -1812,6 +1812,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Assets", "Assets", "{2B858B
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.App.Internal.Assets", "src\Assets\Microsoft.AspNetCore.App.Internal.Assets.csproj", "{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Http.ValidationsGenerator", "src\Http\Http.Extensions\gen\ValidationsGenerator\Microsoft.AspNetCore.Http.ValidationsGenerator.csproj", "{185D74AB-76CE-44C1-86EB-16E93C84033F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -10943,22 +10945,38 @@ Global
{C3928C15-1836-46DB-A09D-9EFBCCA33E08}.Release|x64.Build.0 = Release|Any CPU
{C3928C15-1836-46DB-A09D-9EFBCCA33E08}.Release|x86.ActiveCfg = Release|Any CPU
{C3928C15-1836-46DB-A09D-9EFBCCA33E08}.Release|x86.Build.0 = Release|Any CPU
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Debug|arm64.ActiveCfg = Debug|Any CPU
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Debug|arm64.Build.0 = Debug|Any CPU
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Debug|x64.ActiveCfg = Debug|Any CPU
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Debug|x64.Build.0 = Debug|Any CPU
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Debug|x86.ActiveCfg = Debug|Any CPU
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Debug|x86.Build.0 = Debug|Any CPU
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Release|Any CPU.Build.0 = Release|Any CPU
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Release|arm64.ActiveCfg = Release|Any CPU
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Release|arm64.Build.0 = Release|Any CPU
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Release|x64.ActiveCfg = Release|Any CPU
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Release|x64.Build.0 = Release|Any CPU
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Release|x86.ActiveCfg = Release|Any CPU
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62}.Release|x86.Build.0 = Release|Any CPU
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Debug|arm64.ActiveCfg = Debug|Any CPU
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Debug|arm64.Build.0 = Debug|Any CPU
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Debug|x64.ActiveCfg = Debug|Any CPU
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Debug|x64.Build.0 = Debug|Any CPU
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Debug|x86.ActiveCfg = Debug|Any CPU
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Debug|x86.Build.0 = Debug|Any CPU
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Release|Any CPU.Build.0 = Release|Any CPU
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Release|arm64.ActiveCfg = Release|Any CPU
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Release|arm64.Build.0 = Release|Any CPU
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Release|x64.ActiveCfg = Release|Any CPU
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Release|x64.Build.0 = Release|Any CPU
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Release|x86.ActiveCfg = Release|Any CPU
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11}.Release|x86.Build.0 = Release|Any CPU
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Debug|arm64.ActiveCfg = Debug|Any CPU
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Debug|arm64.Build.0 = Debug|Any CPU
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Debug|x64.ActiveCfg = Debug|Any CPU
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Debug|x64.Build.0 = Debug|Any CPU
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Debug|x86.ActiveCfg = Debug|Any CPU
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Debug|x86.Build.0 = Debug|Any CPU
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Release|Any CPU.Build.0 = Release|Any CPU
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Release|arm64.ActiveCfg = Release|Any CPU
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Release|arm64.Build.0 = Release|Any CPU
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Release|x64.ActiveCfg = Release|Any CPU
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Release|x64.Build.0 = Release|Any CPU
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Release|x86.ActiveCfg = Release|Any CPU
{185D74AB-76CE-44C1-86EB-16E93C84033F}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -11853,8 +11871,9 @@ Global
{B32FF7A7-9CB3-4DCD-AE97-3B2594DB9DAC} = {2299CCD8-8F9C-4F2B-A633-9BF4DA81022B}
{B9BBC1A8-7F58-4F43-94C3-5F3CB125CEF7} = {B32FF7A7-9CB3-4DCD-AE97-3B2594DB9DAC}
{C3928C15-1836-46DB-A09D-9EFBCCA33E08} = {B5D98AEB-9409-4280-8225-9C1EC6A791B2}
{2B858B82-5F0B-4A24-B3C0-5E99149F70D6} = {017429CC-C5FB-48B4-9C46-034E29EE2F06}
{2AAE7819-BC3E-48F4-9CFA-5DD4CD5FFD62} = {2B858B82-5F0B-4A24-B3C0-5E99149F70D6}
{18A3AF88-D633-44E6-9407-3B2C708F7F64} = {225AEDCF-7162-4A86-AC74-06B84660B379}
{FB45AD76-D348-4F96-A8EE-71F61DC6DA11} = {18A3AF88-D633-44E6-9407-3B2C708F7F64}
{185D74AB-76CE-44C1-86EB-16E93C84033F} = {18A3AF88-D633-44E6-9407-3B2C708F7F64}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}
Expand Down
1 change: 1 addition & 0 deletions eng/Dependencies.props
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ and are generated based on the last package release.
<LatestPackageReference Include="Swashbuckle.AspNetCore" />
<LatestPackageReference Include="System.Reactive.Linq" />
<LatestPackageReference Include="Verify.Xunit" />
<LatestPackageReference Include="Verify.SourceGenerators" />
<LatestPackageReference Include="xunit.abstractions" />
<LatestPackageReference Include="xunit.analyzers" />
<LatestPackageReference Include="xunit.assert" />
Expand Down
1 change: 1 addition & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@
<SystemReactiveLinqVersion>5.0.0</SystemReactiveLinqVersion>
<SwashbuckleAspNetCoreVersion>6.6.2</SwashbuckleAspNetCoreVersion>
<VerifyXunitVersion>19.14.0</VerifyXunitVersion>
<VerifySourceGeneratorsVersion>2.2.0</VerifySourceGeneratorsVersion>
<XunitAbstractionsVersion>2.0.3</XunitAbstractionsVersion>
<XunitAnalyzersVersion>1.15.0</XunitAnalyzersVersion>
<XunitVersion>2.9.2</XunitVersion>
Expand Down
2 changes: 1 addition & 1 deletion eng/testing/linker/SupportFiles/Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
<ProjectReference Include="$(RepoRoot)src\SignalR\clients\csharp\Client\src\Microsoft.AspNetCore.SignalR.Client.csproj" />
<ProjectReference Include="$(RepoRoot)src\SignalR\server\SignalR\src\Microsoft.AspNetCore.SignalR.csproj" />
<ProjectReference Include="$(RepoRoot)src\OpenApi\src\Microsoft.AspNetCore.OpenApi.csproj" />
<ProjectReference Include="$(RepoRoot)src\Http\Http.Extensions\gen\Microsoft.AspNetCore.Http.RequestDelegateGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="$(RepoRoot)src\Http\Http.Extensions\gen\RequestDelegateGenerator\Microsoft.AspNetCore.Http.RequestDelegateGenerator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>

</Project>
1 change: 1 addition & 0 deletions src/DefaultBuilder/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
#nullable enable
Microsoft.AspNetCore.Builder.WebApplication.Conventions.get -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder!
23 changes: 20 additions & 3 deletions src/DefaultBuilder/src/WebApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,18 @@ public sealed class WebApplication : IHost, IApplicationBuilder, IEndpointRouteB
internal const string GlobalEndpointRouteBuilderKey = "__GlobalEndpointRouteBuilder";

private readonly IHost _host;
private readonly List<EndpointDataSource> _dataSources = new();
private readonly GlobalEndpointRouteBuilder _innerBuilder;
private readonly RouteGroupBuilder _globalRouteGroup;

internal WebApplication(IHost host)
{
_host = host;
_innerBuilder = new(this);
_globalRouteGroup = _innerBuilder.MapGroup("");
ApplicationBuilder = new ApplicationBuilder(host.Services, ServerFeatures);
Logger = host.Services.GetRequiredService<ILoggerFactory>().CreateLogger(Environment.ApplicationName ?? nameof(WebApplication));

Properties[GlobalEndpointRouteBuilderKey] = this;
Properties[GlobalEndpointRouteBuilderKey] = _innerBuilder;
}

/// <summary>
Expand Down Expand Up @@ -80,9 +83,14 @@ IServiceProvider IApplicationBuilder.ApplicationServices
internal IDictionary<string, object?> Properties => ApplicationBuilder.Properties;
IDictionary<string, object?> IApplicationBuilder.Properties => Properties;

internal ICollection<EndpointDataSource> DataSources => _dataSources;
internal ICollection<EndpointDataSource> DataSources => ((IEndpointRouteBuilder)_globalRouteGroup).DataSources;
ICollection<EndpointDataSource> IEndpointRouteBuilder.DataSources => DataSources;

/// <summary>
/// Gets the <see cref="IEndpointConventionBuilder"/> for the application.
/// </summary>
public IEndpointConventionBuilder Conventions => _globalRouteGroup;

internal ApplicationBuilder ApplicationBuilder { get; }

IServiceProvider IEndpointRouteBuilder.ServiceProvider => Services;
Expand Down Expand Up @@ -307,4 +315,13 @@ public IList<string>? Middleware
}
}
}

private class GlobalEndpointRouteBuilder(WebApplication application) : IEndpointRouteBuilder
{
public IServiceProvider ServiceProvider => application.Services;

public ICollection<EndpointDataSource> DataSources { get; } = [];

public IApplicationBuilder CreateApplicationBuilder() => application;
}
}
2 changes: 1 addition & 1 deletion src/DefaultBuilder/src/WebApplicationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -408,7 +408,7 @@ private void ConfigureApplication(WebHostBuilderContext context, IApplicationBui
// destination.UseEndpoints()

// Set the route builder so that UseRouting will use the WebApplication as the IEndpointRouteBuilder for route matching
app.Properties.Add(WebApplication.GlobalEndpointRouteBuilderKey, _builtApplication);
app.Properties.Add(WebApplication.GlobalEndpointRouteBuilderKey, _builtApplication.Properties[WebApplication.GlobalEndpointRouteBuilderKey]);

// Only call UseRouting() if there are endpoints configured and UseRouting() wasn't called on the global route builder already
if (_builtApplication.DataSources.Count > 0)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// 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.Metadata;

/// <summary>
/// A marker interface which can be used to identify metadata that disables validation
/// for a specific endpoint.
/// </summary>
public interface IDisableValidationMetadata
{
}
1 change: 1 addition & 0 deletions src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#nullable enable
Microsoft.AspNetCore.Http.Metadata.IDisableValidationMetadata
Microsoft.AspNetCore.Http.ProducesResponseTypeMetadata.Description.get -> string?
Microsoft.AspNetCore.Http.ProducesResponseTypeMetadata.Description.set -> void
Microsoft.AspNetCore.Http.Metadata.IProducesResponseTypeMetadata.Description.get -> string?
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -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 System.Collections.Immutable;
using System.IO;
using Microsoft.CodeAnalysis;

namespace Microsoft.AspNetCore.Http.ValidationsGenerator;

public sealed partial class ValidationsGenerator
{
internal static void EmitValidationsFile(SourceProductionContext context, ((string Left, string Right) Left, ImmutableArray<string> Right) source)
{
var withValidations = source.Left.Left;
var typeValidations = source.Left.Right;
var validationsFilters = source.Right;
var writer = new StringWriter();
var output = new CodeWriter(writer, baseIndent: 0);
output.WriteLine("// <auto-generated/>");
output.WriteLine("#nullable enable");
output.WriteLine("namespace System.Runtime.CompilerServices");
output.StartBlock();
output.WriteLine("[AttributeUsage(System.AttributeTargets.Method, AllowMultiple = true)]");
output.WriteLine("file sealed class InterceptsLocationAttribute : Attribute");
output.StartBlock();
output.WriteLine("public InterceptsLocationAttribute(int version, string data) { }");
output.EndBlock();
output.EndBlock();
output.WriteLine();
output.WriteLine("namespace Microsoft.AspNetCore.Http.Validations.Generated");
output.StartBlock();
output.WriteLine("using System;");
output.WriteLine("using System.Linq;");
output.WriteLine("using System.Diagnostics;");
output.WriteLine("using System.ComponentModel.DataAnnotations;");
output.WriteLine();
output.Indent--;
output.WriteLine(EmitEndpointKey());
output.WriteLine(EmitValidationProblemBuilder());
output.WriteLine(withValidations);
output.WriteLine();
output.WriteLine(typeValidations);
output.WriteLine();
output.Write(EmitEndpointValidationFilters(validationsFilters));
output.WriteLine("}");
output.WriteLine("#nullable restore");
context.AddSource("RouteHandlerValidations.g.cs", writer.ToString());
}
}
Original file line number Diff line number Diff line change
@@ -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.IO;

namespace Microsoft.AspNetCore.Http.ValidationsGenerator;

public sealed partial class ValidationsGenerator
{
internal static string EmitEndpointKey()
{
var writer = new StringWriter();
var code = new CodeWriter(writer, baseIndent: 1);
code.WriteLine("file class EndpointKey(string route, global::System.Collections.Generic.IEnumerable<string> methods)");
code.StartBlock();
code.WriteLine("public string Route { get; } = route;");
code.WriteLine("public global::System.Collections.Generic.IEnumerable<string> Methods { get; } = methods;");
code.WriteLine();
code.WriteLine("public override bool Equals(object? obj)");
code.StartBlock();
code.WriteLine("if (obj is EndpointKey other)");
code.StartBlock();
code.WriteLine("return string.Equals(Route, other.Route, global::System.StringComparison.OrdinalIgnoreCase) &&");
code.WriteLine("Methods.SequenceEqual(other.Methods, global::System.StringComparer.OrdinalIgnoreCase);");
code.EndBlock();
code.WriteLine("return false;");
code.EndBlock();
code.WriteLine();
code.WriteLine("public override int GetHashCode()");
code.StartBlock();
code.WriteLine("int hash = 17;");
code.WriteLine("hash = hash * 23 + (Route?.GetHashCode(global::System.StringComparison.OrdinalIgnoreCase) ?? 0);");
code.WriteLine("hash = hash * 23 + GetMethodsHashCode(Methods);");
code.WriteLine("return hash;");
code.EndBlock();
code.WriteLine();
code.WriteLine("private static int GetMethodsHashCode(global::System.Collections.Generic.IEnumerable<string> methods)");
code.StartBlock();
code.WriteLine("if (methods == null)");
code.StartBlock();
code.WriteLine("return 0;");
code.EndBlock();
code.WriteLine("int hash = 17;");
code.WriteLine("foreach (var method in methods)");
code.StartBlock();
code.WriteLine("hash = hash * 23 + (method?.GetHashCode(global::System.StringComparison.OrdinalIgnoreCase) ?? 0);");
code.EndBlock();
code.WriteLine("return hash;");
code.EndBlock();
code.EndBlock();
return writer.ToString();
}
}
Loading

0 comments on commit ab949d5

Please sign in to comment.