Skip to content

Commit 8d35d72

Browse files
authored
Add HTTP event processors to allow for custom logic for HTTP events (#374)
* Add HTTP event processors to allow for custom logic for HTTP events * Cleanup * Add xml docs to processors * Add http event IHostBuilder extensions * Run CI
1 parent 5d8370c commit 8d35d72

8 files changed

Lines changed: 118 additions & 29 deletions

File tree

Hosting/NetCord.Hosting.AspNetCore/EndpointRouteBuilderExtensions.cs renamed to Hosting/NetCord.Hosting.AspNetCore/HttpEventEndpointRouteBuilderExtensions.cs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
using Microsoft.AspNetCore.Builder;
22
using Microsoft.AspNetCore.Http;
33
using Microsoft.AspNetCore.Routing;
4+
using Microsoft.Extensions.DependencyInjection;
45

56
namespace NetCord.Hosting.AspNetCore;
67

7-
public static class EndpointRouteBuilderExtensions
8+
public static class HttpEventEndpointRouteBuilderExtensions
89
{
910
/// <summary>
1011
/// Adds a route to the <see cref="IEndpointRouteBuilder"/> that will handle Discord interactions.
@@ -14,10 +15,13 @@ public static class EndpointRouteBuilderExtensions
1415
/// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
1516
public static IEndpointConventionBuilder UseHttpInteractions(this IEndpointRouteBuilder endpoints, string pattern)
1617
{
17-
HttpInteractionHandler handler = new(endpoints.ServiceProvider, pattern);
18+
var parsedPattern = RoutePatternHelper.ParseLiteral(pattern);
19+
20+
var processor = endpoints.ServiceProvider.GetService<IHttpInteractionProcessor>()
21+
?? new HttpInteractionProcessor(endpoints.ServiceProvider);
1822

1923
return endpoints
20-
.Map(handler.Pattern, handler.HandleRequestAsync)
24+
.Map(parsedPattern, processor.ProcessAsync)
2125
.WithMetadata(new HttpMethodMetadata([HttpMethods.Post]));
2226
}
2327

@@ -29,10 +33,13 @@ public static IEndpointConventionBuilder UseHttpInteractions(this IEndpointRoute
2933
/// <returns>A <see cref="IEndpointConventionBuilder"/> that can be used to further customize the endpoint.</returns>
3034
public static IEndpointConventionBuilder UseWebhookEvents(this IEndpointRouteBuilder endpoints, string pattern)
3135
{
32-
WebhookEventHandler handler = new(endpoints.ServiceProvider, pattern);
36+
var parsedPattern = RoutePatternHelper.ParseLiteral(pattern);
37+
38+
var processor = endpoints.ServiceProvider.GetService<IWebhookEventProcessor>()
39+
?? new WebhookEventProcessor(endpoints.ServiceProvider);
3340

3441
return endpoints
35-
.Map(handler.Pattern, handler.HandleRequestAsync)
42+
.Map(parsedPattern, processor.ProcessAsync)
3643
.WithMetadata(new HttpMethodMetadata([HttpMethods.Post]));
3744
}
3845
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using Microsoft.Extensions.Hosting;
2+
3+
namespace NetCord.Hosting.AspNetCore;
4+
5+
public static class HttpEventHostBuilderExtensions
6+
{
7+
/// <summary>
8+
/// Adds an <see cref="IHttpInteractionProcessor"/> to the specified <see cref="IHostBuilder"/>.
9+
/// </summary>
10+
/// <param name="builder">The <see cref="IHostBuilder"/> to add the <see cref="IHttpInteractionProcessor"/> to.</param>
11+
/// <returns>A reference to this instance after the operation has completed.</returns>
12+
public static IHostBuilder UseHttpInteractionProcessor(this IHostBuilder builder)
13+
{
14+
builder.ConfigureServices((context, services) => services.AddHttpInteractionProcessor());
15+
return builder;
16+
}
17+
18+
/// <summary>
19+
/// Adds an <see cref="IWebhookEventProcessor"/> to the specified <see cref="IHostBuilder"/>.
20+
/// </summary>
21+
/// <param name="builder">The <see cref="IHostBuilder"/> to add the <see cref="IWebhookEventProcessor"/> to.</param>
22+
/// <returns>A reference to this instance after the operation has completed.</returns>
23+
public static IHostBuilder UseWebhookEventProcessor(this IHostBuilder builder)
24+
{
25+
builder.ConfigureServices((context, services) => services.AddWebhookEventProcessor());
26+
return builder;
27+
}
28+
}

Hosting/NetCord.Hosting.AspNetCore/HttpEventHandler.cs renamed to Hosting/NetCord.Hosting.AspNetCore/HttpEventProcessor.cs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,23 @@
33
using System.Text;
44

55
using Microsoft.AspNetCore.Http;
6-
using Microsoft.AspNetCore.Routing.Patterns;
76
using Microsoft.Extensions.DependencyInjection;
87
using Microsoft.Extensions.Options;
98

109
using NetCord.Rest;
1110

1211
namespace NetCord.Hosting.AspNetCore;
1312

14-
internal abstract class HttpEventHandler<TRawData> where TRawData : class
13+
internal abstract class HttpEventProcessor<TRawData> where TRawData : class
1514
{
16-
public HttpEventHandler(IServiceProvider services, string pattern)
15+
public HttpEventProcessor(IServiceProvider services)
1716
{
1817
_services = services;
1918

2019
var publicKey = services.GetRequiredService<IOptions<IDiscordOptions>>().Value.PublicKey ?? throw new InvalidOperationException($"'{nameof(IDiscordOptions.PublicKey)}' must be set.");
2120
_validator = new(publicKey);
2221

2322
_client = services.GetRequiredService<RestClient>();
24-
25-
Pattern = RoutePatternHelper.ParseLiteral(pattern);
2623
}
2724

2825
private protected readonly IServiceProvider _services;
@@ -31,15 +28,13 @@ public HttpEventHandler(IServiceProvider services, string pattern)
3128

3229
private protected readonly RestClient _client;
3330

34-
public RoutePattern Pattern { get; }
35-
3631
protected abstract TRawData GetData(HttpContext context, ReadOnlySpan<byte> body);
3732

3833
protected abstract ValueTask HandleAsync(HttpContext context, TRawData data);
3934

4035
protected abstract void LogHandlerException(Exception ex);
4136

42-
public async Task HandleRequestAsync(HttpContext context)
37+
public async Task ProcessAsync(HttpContext context)
4338
{
4439
var value = await ValidateAsync(context).ConfigureAwait(false);
4540
if (value is null)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
3+
namespace NetCord.Hosting.AspNetCore;
4+
5+
public static class HttpEventServiceCollectionExtensions
6+
{
7+
/// <summary>
8+
/// Adds an <see cref="IHttpInteractionProcessor"/> to the specified <see cref="IServiceCollection"/>.
9+
/// </summary>
10+
/// <param name="services">The <see cref="IServiceCollection"/> to add the <see cref="IHttpInteractionProcessor"/> to.</param>
11+
/// <returns>A reference to this instance after the operation has completed.</returns>
12+
public static IServiceCollection AddHttpInteractionProcessor(this IServiceCollection services)
13+
{
14+
services.AddSingleton<IHttpInteractionProcessor, HttpInteractionProcessor>();
15+
return services;
16+
}
17+
18+
/// <summary>
19+
/// Adds an <see cref="IWebhookEventProcessor"/> to the specified <see cref="IServiceCollection"/>.
20+
/// </summary>
21+
/// <param name="services">The <see cref="IServiceCollection"/> to add the <see cref="IWebhookEventProcessor"/> to.</param>
22+
/// <returns>A reference to this instance after the operation has completed.</returns>
23+
public static IServiceCollection AddWebhookEventProcessor(this IServiceCollection services)
24+
{
25+
services.AddSingleton<IWebhookEventProcessor, WebhookEventProcessor>();
26+
return services;
27+
}
28+
}

Hosting/NetCord.Hosting.AspNetCore/HttpInteractionHandler.cs renamed to Hosting/NetCord.Hosting.AspNetCore/IHttpInteractionProcessor.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,20 @@
66

77
namespace NetCord.Hosting.AspNetCore;
88

9-
internal sealed class HttpInteractionHandler(IServiceProvider services,
10-
string pattern) : HttpEventHandler<IInteraction>(services,
11-
pattern)
9+
public interface IHttpInteractionProcessor
1210
{
13-
private readonly ILogger<HttpInteractionHandler> _logger = services.GetRequiredService<ILogger<HttpInteractionHandler>>();
11+
/// <summary>
12+
/// Processes an incoming HTTP interaction request.
13+
/// </summary>
14+
/// <param name="context">The <see cref="HttpContext"/> of the incoming request.</param>
15+
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
16+
public Task ProcessAsync(HttpContext context);
17+
}
18+
19+
internal sealed class HttpInteractionProcessor(
20+
IServiceProvider services) : HttpEventProcessor<IInteraction>(services), IHttpInteractionProcessor
21+
{
22+
private readonly ILogger<HttpInteractionProcessor> _logger = services.GetRequiredService<ILogger<HttpInteractionProcessor>>();
1423

1524
private readonly Func<Interaction, ValueTask>[] _handlers = [.. services.GetServices<IHttpInteractionHandler>()
1625
.Select<IHttpInteractionHandler, Func<Interaction, ValueTask>>(h => h.HandleAsync)];

Hosting/NetCord.Hosting.AspNetCore/WebhookEventHandler.cs renamed to Hosting/NetCord.Hosting.AspNetCore/IWebhookEventProcessor.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,27 @@
77

88
namespace NetCord.Hosting.AspNetCore;
99

10+
public interface IWebhookEventProcessor
11+
{
12+
/// <summary>
13+
/// Processes an incoming webhook event request.
14+
/// </summary>
15+
/// <param name="context">The <see cref="HttpContext"/> of the incoming request.</param>
16+
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
17+
public Task ProcessAsync(HttpContext context);
18+
}
19+
1020
[GenerateHandler("APPLICATION_AUTHORIZED", typeof(ApplicationAuthorizedWebhookEventArgs))]
1121
[GenerateHandler("APPLICATION_DEAUTHORIZED", typeof(ApplicationDeauthorizedWebhookEventArgs))]
1222
[GenerateHandler("ENTITLEMENT_CREATE", typeof(EntitlementCreateWebhookEventArgs))]
1323
[GenerateHandler(null, typeof(UnknownEventWebhookEventArgs))]
14-
internal partial class WebhookEventHandler : HttpEventHandler<JsonWebhookEventArgs>
24+
internal partial class WebhookEventProcessor : HttpEventProcessor<JsonWebhookEventArgs>, IWebhookEventProcessor
1525
{
1626
private partial class StorageBuilder;
1727

1828
private partial class Storage;
1929

20-
public WebhookEventHandler(IServiceProvider services, string pattern) : base(services, pattern)
30+
public WebhookEventProcessor(IServiceProvider services) : base(services)
2131
{
2232
StorageBuilder builder = new();
2333

@@ -31,12 +41,12 @@ public WebhookEventHandler(IServiceProvider services, string pattern) : base(ser
3141

3242
_storage = builder.Build();
3343

34-
_logger = services.GetRequiredService<ILogger<WebhookEventHandler>>();
44+
_logger = services.GetRequiredService<ILogger<WebhookEventProcessor>>();
3545
}
3646

3747
private readonly Storage _storage;
3848

39-
private readonly ILogger<WebhookEventHandler> _logger;
49+
private readonly ILogger<WebhookEventProcessor> _logger;
4050

4151
protected override JsonWebhookEventArgs GetData(HttpContext context, ReadOnlySpan<byte> body)
4252
{

SourceGenerators/HostingWebhookEventsGenerator/HostingWebhookEventsGenerator.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ internal class GenerateHandlerAttribute(string? rawEventName, Type eventArgs) :
5757

5858
context.AddSource("WebhookHandlerInterfaces.g.cs", SourceText.From(GenerateHandlerInterfaces(attributesData), Encoding.UTF8));
5959

60-
context.AddSource("WebhookEventHandler.g.cs", SourceText.From(GenerateWebhookEventHandler(attributesData), Encoding.UTF8));
60+
context.AddSource("WebhookEventProcessor.g.cs", SourceText.From(GenerateWebhookEventProcessor(attributesData), Encoding.UTF8));
6161
});
6262
}
6363

@@ -185,21 +185,21 @@ private void WriteHandlerInterfaces(StringWriter stringWriter, ImmutableArray<Ge
185185
}
186186
}
187187

188-
private string GenerateWebhookEventHandler(ImmutableArray<GenerateHandlerAttributeData> attributesData)
188+
private string GenerateWebhookEventProcessor(ImmutableArray<GenerateHandlerAttributeData> attributesData)
189189
{
190190
StringWriter stringWriter = new();
191191
Setup(stringWriter);
192192

193-
WriteWebhookEventHandler(stringWriter, attributesData);
193+
WriteWebhookEventProcessor(stringWriter, attributesData);
194194

195195
return stringWriter.ToString();
196196
}
197197

198-
private void WriteWebhookEventHandler(StringWriter stringWriter, ImmutableArray<GenerateHandlerAttributeData> attributesData)
198+
private void WriteWebhookEventProcessor(StringWriter stringWriter, ImmutableArray<GenerateHandlerAttributeData> attributesData)
199199
{
200200
stringWriter.WriteLine();
201201

202-
stringWriter.WriteLine("partial class WebhookEventHandler");
202+
stringWriter.WriteLine("partial class WebhookEventProcessor");
203203

204204
stringWriter.Write("{");
205205

@@ -367,7 +367,7 @@ private void WriteStorageBuilderBuildMethod(StringWriter stringWriter, Immutable
367367
stringWriter.WriteLine();
368368

369369
stringWriter.WriteIndentation(2);
370-
stringWriter.WriteLine("public global::NetCord.Hosting.AspNetCore.WebhookEventHandler.Storage Build()");
370+
stringWriter.WriteLine("public global::NetCord.Hosting.AspNetCore.WebhookEventProcessor.Storage Build()");
371371

372372
stringWriter.WriteIndentation(2);
373373
stringWriter.WriteLine("{");

Tests/NetCord.Test.Hosting.AspNetCore/Program.cs

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
builder.Services
1414
.AddDiscordRest()
15+
.AddHttpInteractionProcessor()
16+
.AddWebhookEventProcessor()
1517
.AddHttpApplicationCommands()
1618
.AddHttpComponentInteractions()
1719
.AddHttpInteractionHandler<InteractionHandler>()
@@ -40,8 +42,18 @@
4042
app.AddSlashCommand("echo", "Echo!", ([SlashCommandParameter(AutocompleteProviderType = typeof(EchoAutocompleteProvider))] string s) => s);
4143
app.AddComponentInteraction("button", (string s) => $"Button! {s}");
4244

43-
app.UseHttpInteractions("/interactions");
45+
app.MapPost("/interactions", (HttpContext context, IHttpInteractionProcessor processor) =>
46+
{
47+
return processor.ProcessAsync(context);
48+
});
4449

45-
app.UseWebhookEvents("/webhooks");
50+
app.MapPost("/webhooks", (HttpContext context, IWebhookEventProcessor processor) =>
51+
{
52+
return processor.ProcessAsync(context);
53+
});
54+
55+
// app.UseHttpInteractions("/interactions");
56+
57+
// app.UseWebhookEvents("/webhooks");
4658

4759
await app.RunAsync();

0 commit comments

Comments
 (0)