diff --git a/docs/guide/http/problemdetails.md b/docs/guide/http/problemdetails.md
index 2bec4ec7e..a4378f154 100644
--- a/docs/guide/http/problemdetails.md
+++ b/docs/guide/http/problemdetails.md
@@ -139,3 +139,13 @@ public static ProblemDetails Before(IShipOrder command, Order order)
```
snippet source | anchor
+
+## Within Message Handlers
+
+`ProblemDetails` can be used within message handlers as well with similar rules. See this example
+from the tests:
+
+snippet: sample_using_problem_details_in_message_handler
+
+This functionality was added so that some handlers could be both an endpoint and message handler
+without having to duplicate code or delegate to the handler through an endpoint.
\ No newline at end of file
diff --git a/src/Http/Wolverine.Http.Tests/problem_details_usage_in_http_middleware.cs b/src/Http/Wolverine.Http.Tests/problem_details_usage_in_http_middleware.cs
index e4aa50c84..48e1e45fb 100644
--- a/src/Http/Wolverine.Http.Tests/problem_details_usage_in_http_middleware.cs
+++ b/src/Http/Wolverine.Http.Tests/problem_details_usage_in_http_middleware.cs
@@ -4,6 +4,7 @@
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
+using Wolverine.Tracking;
using WolverineWebApi;
namespace Wolverine.Http.Tests;
@@ -40,6 +41,30 @@ public async Task stop_with_problems_if_middleware_trips_off()
}
#endregion
+
+ [Fact]
+ public async Task continue_happy_path_as_handler_acting_as_endpoint()
+ {
+ // Should be good
+ await Scenario(x =>
+ {
+ x.Post.Json(new NumberMessage(3)).ToUrl("/problems2");
+ x.StatusCodeShouldBe(204);
+ });
+ }
+
+ [Fact]
+ public async Task stop_with_problems_if_middleware_trips_off_in_handler_acting_as_endpoint()
+ {
+ // This is the "sad path" that should spawn a ProblemDetails
+ // object
+ var result = await Scenario(x =>
+ {
+ x.Post.Json(new NumberMessage(10)).ToUrl("/problems2");
+ x.StatusCodeShouldBe(400);
+ x.ContentTypeShouldBe("application/problem+json");
+ });
+ }
[Fact]
public void adds_default_problem_details_to_open_api_metadata()
@@ -53,4 +78,28 @@ public void adds_default_problem_details_to_open_api_metadata()
produces.StatusCode.ShouldBe(400);
produces.ContentTypes.Single().ShouldBe("application/problem+json");
}
+
+ [Fact]
+ public async Task problem_details_in_message_handlers_positive()
+ {
+ NumberMessageHandler.Handled = false;
+
+ var tracked = await Host.InvokeMessageAndWaitAsync(new NumberMessage(3));
+ tracked.Executed.SingleMessage()
+ .Number.ShouldBe(3);
+
+ NumberMessageHandler.Handled.ShouldBeTrue();
+ }
+
+ [Fact]
+ public async Task problem_details_in_message_handlers_negative()
+ {
+ NumberMessageHandler.Handled = false;
+
+ var tracked = await Host.InvokeMessageAndWaitAsync(new NumberMessage(10));
+ tracked.Executed.SingleMessage()
+ .Number.ShouldBe(10);
+
+ NumberMessageHandler.Handled.ShouldBeFalse();
+ }
}
\ No newline at end of file
diff --git a/src/Http/Wolverine.Http/CodeGen/ProblemDetailsContinuationPolicy.cs b/src/Http/Wolverine.Http/CodeGen/ProblemDetailsContinuationPolicy.cs
index 19e062d97..71d39876f 100644
--- a/src/Http/Wolverine.Http/CodeGen/ProblemDetailsContinuationPolicy.cs
+++ b/src/Http/Wolverine.Http/CodeGen/ProblemDetailsContinuationPolicy.cs
@@ -4,19 +4,36 @@
using JasperFx.Core.Reflection;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using Wolverine.Configuration;
using Wolverine.Middleware;
namespace Wolverine.Http.CodeGen;
-internal class ProblemDetailsContinuationPolicy : IContinuationStrategy
+public class ProblemDetailsContinuationPolicy : IContinuationStrategy
{
- public bool TryFindContinuationHandler(MethodCall call, out Frame? frame)
+ public static void WriteProblems(ILogger logger, ProblemDetails details)
+ {
+ var json = JsonConvert.SerializeObject(details, Formatting.None);
+ logger.LogInformation("Found problems with this message: {Problems}", json);
+ }
+
+ public bool TryFindContinuationHandler(IChain chain, MethodCall call, out Frame? frame)
{
var details = call.Creates.FirstOrDefault(x => x.VariableType == typeof(ProblemDetails));
if (details != null)
{
- frame = new MaybeEndWithProblemDetailsFrame(details);
+ if (chain is HttpChain)
+ {
+ frame = new MaybeEndWithProblemDetailsFrame(details);
+ }
+ else
+ {
+ frame = new MaybeEndHandlerWithProblemDetailsFrame(details);
+ }
+
return true;
}
@@ -39,13 +56,13 @@ public MaybeEndWithProblemDetailsFrame(Variable details)
uses.Add(details);
_details = details;
}
-
+
public override IEnumerable FindVariables(IMethodVariables chain)
{
_context = chain.FindVariable(typeof(HttpContext));
yield return _context;
}
-
+
public override void GenerateCode(GeneratedMethod method, ISourceWriter writer)
{
writer.WriteComment("Evaluate whether the processing should stop if there are any problems");
@@ -55,6 +72,40 @@ public override void GenerateCode(GeneratedMethod method, ISourceWriter writer)
writer.FinishBlock();
writer.BlankLine();
+ Next?.GenerateCode(method, writer);
+ }
+}
+
+///
+/// Used to potentially stop the execution of an Http request
+/// based on whether the IResult is a WolverineContinue or something else
+///
+public class MaybeEndHandlerWithProblemDetailsFrame : AsyncFrame
+{
+ private readonly Variable _details;
+ private Variable? _logger;
+
+ public MaybeEndHandlerWithProblemDetailsFrame(Variable details)
+ {
+ uses.Add(details);
+ _details = details;
+ }
+
+ public override IEnumerable FindVariables(IMethodVariables chain)
+ {
+ _logger = chain.FindVariable(typeof(ILogger));
+ yield return _logger;
+ }
+
+ public override void GenerateCode(GeneratedMethod method, ISourceWriter writer)
+ {
+ writer.WriteComment("Evaluate whether the processing should stop if there are any problems");
+ writer.Write($"BLOCK:if (!(ReferenceEquals({_details.Usage}, {typeof(WolverineContinue).FullNameInCode()}.{nameof(WolverineContinue.NoProblems)})))");
+ writer.Write($"{typeof(ProblemDetailsContinuationPolicy).FullNameInCode()}.{nameof(ProblemDetailsContinuationPolicy.WriteProblems)}({_logger.Usage}, {_details.Usage});");
+ writer.Write("return;");
+ writer.FinishBlock();
+ writer.BlankLine();
+
Next?.GenerateCode(method, writer);
}
}
\ No newline at end of file
diff --git a/src/Http/Wolverine.Http/CodeGen/ResultContinuationPolicy.cs b/src/Http/Wolverine.Http/CodeGen/ResultContinuationPolicy.cs
index 1e0423f57..2604d5823 100644
--- a/src/Http/Wolverine.Http/CodeGen/ResultContinuationPolicy.cs
+++ b/src/Http/Wolverine.Http/CodeGen/ResultContinuationPolicy.cs
@@ -3,13 +3,14 @@
using JasperFx.CodeGeneration.Model;
using JasperFx.Core.Reflection;
using Microsoft.AspNetCore.Http;
+using Wolverine.Configuration;
using Wolverine.Middleware;
namespace Wolverine.Http.CodeGen;
internal class ResultContinuationPolicy : IContinuationStrategy
{
- public bool TryFindContinuationHandler(MethodCall call, out Frame? frame)
+ public bool TryFindContinuationHandler(IChain chain, MethodCall call, out Frame? frame)
{
var result = call.Creates.FirstOrDefault(x => x.VariableType.CanBeCastTo());
diff --git a/src/Http/WolverineWebApi/ProblemDetailsUsage.cs b/src/Http/WolverineWebApi/ProblemDetailsUsage.cs
index 9e28b66af..7b0d23b8d 100644
--- a/src/Http/WolverineWebApi/ProblemDetailsUsage.cs
+++ b/src/Http/WolverineWebApi/ProblemDetailsUsage.cs
@@ -1,3 +1,4 @@
+using System.Diagnostics;
using Microsoft.AspNetCore.Mvc;
using Wolverine.Http;
@@ -31,4 +32,36 @@ public static string Post(NumberMessage message)
public record NumberMessage(int Number);
-#endregion
\ No newline at end of file
+#endregion
+
+public static class NumberMessageHandler
+{
+ #region sample_using_problem_details_in_message_handler
+
+ public static ProblemDetails Validate(NumberMessage message)
+ {
+ if (message.Number > 5)
+ {
+ return new ProblemDetails
+ {
+ Detail = "Number is bigger than 5",
+ Status = 400
+ };
+ }
+
+ // All good, keep on going!
+ return WolverineContinue.NoProblems;
+ }
+
+ // Look at this! You can use this as an HTTP endpoint too!
+ [WolverinePost("/problems2")]
+ public static void Handle(NumberMessage message)
+ {
+ Debug.WriteLine("Handled " + message);
+ Handled = true;
+ }
+
+ #endregion
+
+ public static bool Handled { get; set; }
+}
\ No newline at end of file
diff --git a/src/Http/WolverineWebApi/Validation/ValidatedEndpoint.cs b/src/Http/WolverineWebApi/Validation/ValidatedEndpoint.cs
index c7fd7d794..225a58fb2 100644
--- a/src/Http/WolverineWebApi/Validation/ValidatedEndpoint.cs
+++ b/src/Http/WolverineWebApi/Validation/ValidatedEndpoint.cs
@@ -1,3 +1,4 @@
+using System.Diagnostics;
using FluentValidation;
using Wolverine.Http;
diff --git a/src/Samples/Diagnostics/DiagnosticsApp/Invoices.cs b/src/Samples/Diagnostics/DiagnosticsApp/Invoices.cs
index 56b9309bf..560f6fe08 100644
--- a/src/Samples/Diagnostics/DiagnosticsApp/Invoices.cs
+++ b/src/Samples/Diagnostics/DiagnosticsApp/Invoices.cs
@@ -1,5 +1,6 @@
using Wolverine;
using Wolverine.Attributes;
+using Wolverine.Configuration;
using Wolverine.ErrorHandling;
using Wolverine.Runtime.Handlers;
diff --git a/src/Wolverine/Configuration/Chain.cs b/src/Wolverine/Configuration/Chain.cs
index d9653c8d6..1036a6795 100644
--- a/src/Wolverine/Configuration/Chain.cs
+++ b/src/Wolverine/Configuration/Chain.cs
@@ -238,7 +238,7 @@ protected void applyImpliedMiddlewareFromHandlers(GenerationRules generationRule
Middleware.Add(frame);
// Potentially add handling for IResult or HandlerContinuation
- if (generationRules.TryFindContinuationHandler(frame, out var continuation))
+ if (generationRules.TryFindContinuationHandler(this, frame, out var continuation))
{
Middleware.Add(continuation!);
}
diff --git a/src/Wolverine/Middleware/ContinuationHandling.cs b/src/Wolverine/Middleware/ContinuationHandling.cs
index 3da65f650..4db9d626e 100644
--- a/src/Wolverine/Middleware/ContinuationHandling.cs
+++ b/src/Wolverine/Middleware/ContinuationHandling.cs
@@ -1,5 +1,6 @@
using JasperFx.CodeGeneration;
using JasperFx.CodeGeneration.Frames;
+using Wolverine.Configuration;
namespace Wolverine.Middleware;
@@ -41,11 +42,12 @@ public static List ContinuationStrategies(this Generation
}
}
- public static bool TryFindContinuationHandler(this GenerationRules rules, MethodCall call, out Frame? frame)
+ public static bool TryFindContinuationHandler(this GenerationRules rules, IChain chain, MethodCall call,
+ out Frame? frame)
{
var strategies = rules.ContinuationStrategies();
foreach (var strategy in strategies)
- if (strategy.TryFindContinuationHandler(call, out frame))
+ if (strategy.TryFindContinuationHandler(chain, call, out frame))
{
return true;
}
@@ -57,12 +59,12 @@ public static bool TryFindContinuationHandler(this GenerationRules rules, Method
public interface IContinuationStrategy
{
- bool TryFindContinuationHandler(MethodCall call, out Frame? frame);
+ bool TryFindContinuationHandler(IChain chain, MethodCall call, out Frame? frame);
}
internal class HandlerContinuationPolicy : IContinuationStrategy
{
- public bool TryFindContinuationHandler(MethodCall call, out Frame? frame)
+ public bool TryFindContinuationHandler(IChain chain, MethodCall call, out Frame? frame)
{
if (call.CreatesNewOf())
{
diff --git a/src/Wolverine/Middleware/MiddlewarePolicy.cs b/src/Wolverine/Middleware/MiddlewarePolicy.cs
index d03a3ac67..c244a1f27 100644
--- a/src/Wolverine/Middleware/MiddlewarePolicy.cs
+++ b/src/Wolverine/Middleware/MiddlewarePolicy.cs
@@ -143,19 +143,19 @@ public IEnumerable BuildBeforeCalls(IChain chain, GenerationRules rules)
foreach (var frame in frames) yield return frame;
}
- private IEnumerable wrapBeforeFrame(MethodCall call, GenerationRules rules)
+ private IEnumerable wrapBeforeFrame(IChain chain, MethodCall call, GenerationRules rules)
{
if (_finals.Length == 0)
{
yield return call;
- if (rules.TryFindContinuationHandler(call, out var frame))
+ if (rules.TryFindContinuationHandler(chain, call, out var frame))
{
yield return frame!;
}
}
else
{
- if (rules.TryFindContinuationHandler(call, out var frame))
+ if (rules.TryFindContinuationHandler(chain, call, out var frame))
{
call.Next = frame;
}
@@ -209,7 +209,7 @@ private IEnumerable buildBefores(IChain chain, GenerationRules rules)
{
AssertMethodDoesNotHaveDuplicateReturnValues(call);
- foreach (var frame in wrapBeforeFrame(call, rules)) yield return frame;
+ foreach (var frame in wrapBeforeFrame(chain, call, rules)) yield return frame;
}
}
}