Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace MintPlayer.AspNetCore.Endpoints;

public record EndpointDescriptor(string Name, string Path, IEnumerable<string> Methods, Type HandlerType);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace MintPlayer.AspNetCore.Endpoints;

[AttributeUsage(AttributeTargets.Class)]
public class EndpointNameAttribute(string name) : Attribute
{
public string Name { get; } = name;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace MintPlayer.AspNetCore.Endpoints;

/// <summary>
/// Overrides the name of the generated MapEndpoints extension method for this assembly.
/// Without this attribute, the method name is derived from the assembly name
/// (e.g., assembly "MyApp.Api" -> "MapMyAppApiEndpoints").
/// </summary>
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)]
public class EndpointsMethodNameAttribute(string methodName) : Attribute
{
public string MethodName { get; } = methodName;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace MintPlayer.AspNetCore.Endpoints;

public interface IDeleteEndpoint : IEndpoint
{
static IEnumerable<string> IEndpointBase.Methods => ["DELETE"];
}

public interface IDeleteEndpoint<TRequest> : IEndpoint<TRequest>
{
static IEnumerable<string> IEndpointBase.Methods => ["DELETE"];
}

public interface IDeleteEndpoint<TRequest, TResponse> : IEndpoint<TRequest, TResponse>
{
static IEnumerable<string> IEndpointBase.Methods => ["DELETE"];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace MintPlayer.AspNetCore.Endpoints;

/// <summary>
/// Level 1: Raw endpoint — full control over HttpContext.
/// </summary>
public interface IEndpoint : IEndpointBase
{
Task<IResult> HandleAsync(HttpContext httpContext);
}

/// <summary>
/// Level 2: Typed request — automatic request binding via the abstract base class.
/// </summary>
public interface IEndpoint<TRequest> : IEndpoint
{
Task<IResult> HandleAsync(TRequest request, CancellationToken cancellationToken);
}

/// <summary>
/// Level 3: Typed request and response — full type information for OpenAPI/Swagger.
/// TResponse is used by the source generator to emit .Produces&lt;TResponse&gt;(statusCode).
/// </summary>
public interface IEndpoint<TRequest, TResponse> : IEndpoint<TRequest>
{
/// <summary>
/// The HTTP status code for successful responses.
/// Used by the source generator for .Produces&lt;TResponse&gt;(statusCode).
/// Default: 200 (OK).
/// </summary>
static virtual int SuccessStatusCode => 200;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace MintPlayer.AspNetCore.Endpoints;

/// <summary>
/// Base contract for all endpoints. Provides route metadata as static abstract members.
/// </summary>
public interface IEndpointBase
{
/// <summary>The route pattern (e.g., "/api/users/{id}").</summary>
static abstract string Path { get; }

/// <summary>
/// The HTTP methods this endpoint handles (e.g., ["GET"], ["POST"], ["GET", "HEAD"]).
/// Convenience interfaces (IGetEndpoint, IPostEndpoint, etc.) provide this automatically.
/// </summary>
static abstract IEnumerable<string> Methods { get; }

/// <summary>
/// Optional hook to configure the route handler (auth, caching, OpenAPI metadata, etc.).
/// Default implementation is a no-op.
/// </summary>
static virtual void Configure(RouteHandlerBuilder builder) { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace MintPlayer.AspNetCore.Endpoints;

/// <summary>
/// Defines a route group with a shared prefix and optional group-wide configuration.
/// Endpoints join a group by implementing IMemberOf&lt;TGroup&gt;.
/// </summary>
public interface IEndpointGroup
{
/// <summary>The route prefix for all endpoints in this group (e.g., "/api/users").</summary>
static abstract string Prefix { get; }

/// <summary>
/// Optional hook to configure the route group (auth, rate limiting, CORS, tags, etc.).
/// Default implementation is a no-op.
/// </summary>
static virtual void Configure(RouteGroupBuilder group) { }
}

/// <summary>
/// Marker interface that associates an endpoint with an endpoint group.
/// The endpoint's Path becomes relative to the group's Prefix.
/// </summary>
public interface IMemberOf<TGroup> where TGroup : IEndpointGroup;
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace MintPlayer.AspNetCore.Endpoints;

public interface IGetEndpoint : IEndpoint
{
static IEnumerable<string> IEndpointBase.Methods => ["GET"];
}

public interface IGetEndpoint<TRequest> : IEndpoint<TRequest>
{
static IEnumerable<string> IEndpointBase.Methods => ["GET"];
}

public interface IGetEndpoint<TRequest, TResponse> : IEndpoint<TRequest, TResponse>
{
static IEnumerable<string> IEndpointBase.Methods => ["GET"];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace MintPlayer.AspNetCore.Endpoints;

public interface IPatchEndpoint : IEndpoint
{
static IEnumerable<string> IEndpointBase.Methods => ["PATCH"];
}

public interface IPatchEndpoint<TRequest> : IEndpoint<TRequest>
{
static IEnumerable<string> IEndpointBase.Methods => ["PATCH"];
}

public interface IPatchEndpoint<TRequest, TResponse> : IEndpoint<TRequest, TResponse>
{
static IEnumerable<string> IEndpointBase.Methods => ["PATCH"];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace MintPlayer.AspNetCore.Endpoints;

public interface IPostEndpoint : IEndpoint
{
static IEnumerable<string> IEndpointBase.Methods => ["POST"];
}

public interface IPostEndpoint<TRequest> : IEndpoint<TRequest>
{
static IEnumerable<string> IEndpointBase.Methods => ["POST"];
}

public interface IPostEndpoint<TRequest, TResponse> : IEndpoint<TRequest, TResponse>
{
static IEnumerable<string> IEndpointBase.Methods => ["POST"];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace MintPlayer.AspNetCore.Endpoints;

public interface IPutEndpoint : IEndpoint
{
static IEnumerable<string> IEndpointBase.Methods => ["PUT"];
}

public interface IPutEndpoint<TRequest> : IEndpoint<TRequest>
{
static IEnumerable<string> IEndpointBase.Methods => ["PUT"];
}

public interface IPutEndpoint<TRequest, TResponse> : IEndpoint<TRequest, TResponse>
{
static IEnumerable<string> IEndpointBase.Methods => ["PUT"];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<OutputType>Library</OutputType>

<IsPackable>true</IsPackable>
<PackageId>MintPlayer.AspNetCore.Endpoints.Abstractions</PackageId>
<Version>10.0.0</Version>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<Description>Interfaces and attributes for MintPlayer.AspNetCore.Endpoints - a class-per-endpoint mapping library for ASP.NET Core.</Description>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageTags>ASP.NET Core, Endpoints, Minimal API</PackageTags>
<PackageProjectUrl>https://github.com/MintPlayer/MintPlayer.AspNetCore.Tools/Endpoints/MintPlayer.AspNetCore.Endpoints.Abstractions</PackageProjectUrl>
<RepositoryUrl>https://github.com/MintPlayer/MintPlayer.AspNetCore.Tools</RepositoryUrl>
<RepositoryType>Git</RepositoryType>
<Authors>Pieterjan De Clippel</Authors>
<Company>MintPlayer</Company>
<Product>MintPlayer.AspNetCore.Endpoints</Product>
</PropertyGroup>

<!-- Include XML markups in the nupkg -->
<PropertyGroup Condition="'$(Configuration)'=='Release'">
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Microsoft.CodeAnalysis;

namespace MintPlayer.AspNetCore.Endpoints.Generator;

internal static class DiagnosticDescriptors
{
public static readonly DiagnosticDescriptor EndpointMustBePartial = new(
id: "MPEP001",
title: "Endpoint class must be partial",
messageFormat: "Endpoint class '{0}' implements a typed endpoint interface and must be declared as partial",
category: "MintPlayer.Endpoints",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor EndpointHasBaseClassConflict = new(
id: "MPEP002",
title: "Endpoint class has conflicting base class",
messageFormat: "Endpoint class '{0}' already has a base class; the source generator cannot add the required endpoint base class",
category: "MintPlayer.Endpoints",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);

public static readonly DiagnosticDescriptor EndpointHasMultipleGroups = new(
id: "MPEP003",
title: "Endpoint belongs to multiple groups",
messageFormat: "Endpoint class '{0}' implements IMemberOf<T> for multiple groups; only one group is allowed",
category: "MintPlayer.Endpoints",
defaultSeverity: DiagnosticSeverity.Error,
isEnabledByDefault: true);
}
Loading
Loading