diff --git a/Directory.Packages.props b/Directory.Packages.props index bfaa105dd..3aea6d1be 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -32,13 +32,13 @@ - - - + + + - + diff --git a/src/Http/Wolverine.Http/CodeGen/JsonHandling.cs b/src/Http/Wolverine.Http/CodeGen/JsonHandling.cs index d6f682297..09a4a56d4 100644 --- a/src/Http/Wolverine.Http/CodeGen/JsonHandling.cs +++ b/src/Http/Wolverine.Http/CodeGen/JsonHandling.cs @@ -6,6 +6,7 @@ using JasperFx.Core.Reflection; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Wolverine.Configuration; using Wolverine.Runtime; namespace Wolverine.Http.CodeGen; @@ -40,6 +41,20 @@ public override void GenerateCode(GeneratedMethod method, ISourceWriter writer) Next?.GenerateCode(method, writer); } + + public override void GenerateFSharpCode(GeneratedMethod method, ISourceWriter writer) + { + writer.WriteComment("Reading the request body via JSON deserialization"); + + // ReadJsonAsync is an inherited *instance* method on HttpHandler (qualified with the member's + // `this` self identifier, jasperfx#393) returning (body, continuation). F# has no early + // `return`, so the abort guard renders the rest of the chain inside its `else` branch. + writer.Write( + $"let! ({Variable.Usage}, jsonContinue) = this.{nameof(HttpHandler.ReadJsonAsync)}<{Variable.VariableType.FSharpName()}>(httpContext)"); + + var condition = $"jsonContinue = {typeof(HandlerContinuation).FSharpName()}.{nameof(HandlerContinuation.Stop)}"; + FSharpEmitHelpers.WriteAbortGuard(writer, method, condition, Next); + } } internal class JsonBodyParameterStrategy : IParameterStrategy diff --git a/src/Http/Wolverine.Http/CodeGen/WriteJsonFrame.cs b/src/Http/Wolverine.Http/CodeGen/WriteJsonFrame.cs index 514008603..18b7bf32a 100644 --- a/src/Http/Wolverine.Http/CodeGen/WriteJsonFrame.cs +++ b/src/Http/Wolverine.Http/CodeGen/WriteJsonFrame.cs @@ -20,4 +20,16 @@ public override void GenerateCode(GeneratedMethod method, ISourceWriter writer) writer.Write($"await {nameof(HttpHandler.WriteJsonAsync)}(httpContext, {_resourceVariable.Usage});"); Next?.GenerateCode(method, writer); } + + public override void GenerateFSharpCode(GeneratedMethod method, ISourceWriter writer) + { + writer.WriteComment("Writing the response body to JSON because this was the first 'return variable' in the method signature"); + + // WriteJsonAsync is an inherited *instance* method on HttpHandler, so it must be qualified with + // the generated member's `this` self identifier (jasperfx#393). + var call = $"this.{nameof(HttpHandler.WriteJsonAsync)}(httpContext, {_resourceVariable.Usage})"; + writer.Write(method.AsyncMode == AsyncMode.AsyncTask ? $"do! {call}" : call); + + Next?.GenerateFSharpCode(method, writer); + } } \ No newline at end of file diff --git a/src/Testing/Wolverine.Core.FSharpFixture/Generated.fs b/src/Testing/Wolverine.Core.FSharpFixture/Generated.fs index cdc79fa08..8a56551db 100644 --- a/src/Testing/Wolverine.Core.FSharpFixture/Generated.fs +++ b/src/Testing/Wolverine.Core.FSharpFixture/Generated.fs @@ -14,7 +14,7 @@ type CheckThingHandler649476295(loggerForMessage: Microsoft.Extensions.Logging.I inherit Wolverine.Runtime.Handlers.MessageHandler() let _loggerForMessage = loggerForMessage - override _.HandleAsync(context: Wolverine.Runtime.MessageContext, cancellation: System.Threading.CancellationToken) : System.Threading.Tasks.Task = + override this.HandleAsync(context: Wolverine.Runtime.MessageContext, cancellation: System.Threading.CancellationToken) : System.Threading.Tasks.Task = // The actual message body let checkThing = context.Envelope.Message :?> Wolverine.Core.FSharpContracts.CheckThing @@ -32,13 +32,14 @@ type CheckThingHandler649476295(loggerForMessage: Microsoft.Extensions.Logging.I // The actual message execution checkThingHandler.Handle(checkThing) + this.RecordCauseAndEffect(context, context.Runtime.Observer) System.Threading.Tasks.Task.CompletedTask type CreateNameHandler1923366998(loggerForMessage: Microsoft.Extensions.Logging.ILogger) = inherit Wolverine.Runtime.Handlers.MessageHandler() let _loggerForMessage = loggerForMessage - override _.HandleAsync(context: Wolverine.Runtime.MessageContext, cancellation: System.Threading.CancellationToken) : System.Threading.Tasks.Task = + override this.HandleAsync(context: Wolverine.Runtime.MessageContext, cancellation: System.Threading.CancellationToken) : System.Threading.Tasks.Task = task { // The actual message body let createName = context.Envelope.Message :?> Wolverine.Core.FSharpContracts.CreateName @@ -64,11 +65,12 @@ type CreateNameHandler1923366998(loggerForMessage: Microsoft.Extensions.Logging. // Outgoing, cascaded message do! context.EnqueueCascadingAsync(outgoing1) + this.RecordCauseAndEffect(context, context.Runtime.Observer) } type GateHandler1696712162() = inherit Wolverine.Runtime.Handlers.MessageHandler() - override _.HandleAsync(context: Wolverine.Runtime.MessageContext, cancellation: System.Threading.CancellationToken) : System.Threading.Tasks.Task = + override this.HandleAsync(context: Wolverine.Runtime.MessageContext, cancellation: System.Threading.CancellationToken) : System.Threading.Tasks.Task = // The actual message body let gate = context.Envelope.Message :?> Wolverine.Core.FSharpContracts.Gate @@ -86,13 +88,14 @@ type GateHandler1696712162() = // The actual message execution gateHandler.Handle(gate) + this.RecordCauseAndEffect(context, context.Runtime.Observer) System.Threading.Tasks.Task.CompletedTask type IncrementCountHandler540640831(inMemorySagaPersistor: Wolverine.Persistence.Sagas.InMemorySagaPersistor) = inherit Wolverine.Runtime.Handlers.MessageHandler() let _inMemorySagaPersistor = inMemorySagaPersistor - override _.HandleAsync(context: Wolverine.Runtime.MessageContext, cancellation: System.Threading.CancellationToken) : System.Threading.Tasks.Task = + override this.HandleAsync(context: Wolverine.Runtime.MessageContext, cancellation: System.Threading.CancellationToken) : System.Threading.Tasks.Task = // The actual message body let incrementCount = context.Envelope.Message :?> Wolverine.Core.FSharpContracts.IncrementCount @@ -126,7 +129,7 @@ type StartCountHandler1561563330(inMemorySagaPersistor: Wolverine.Persistence.Sa inherit Wolverine.Runtime.Handlers.MessageHandler() let _inMemorySagaPersistor = inMemorySagaPersistor - override _.HandleAsync(context: Wolverine.Runtime.MessageContext, cancellation: System.Threading.CancellationToken) : System.Threading.Tasks.Task = + override this.HandleAsync(context: Wolverine.Runtime.MessageContext, cancellation: System.Threading.CancellationToken) : System.Threading.Tasks.Task = // The actual message body let startCount = context.Envelope.Message :?> Wolverine.Core.FSharpContracts.StartCount diff --git a/src/Testing/Wolverine.Core.FSharpTests/FSharpCodegenSample.cs b/src/Testing/Wolverine.Core.FSharpTests/FSharpCodegenSample.cs index 941b8449e..5587f2bf9 100644 --- a/src/Testing/Wolverine.Core.FSharpTests/FSharpCodegenSample.cs +++ b/src/Testing/Wolverine.Core.FSharpTests/FSharpCodegenSample.cs @@ -38,6 +38,10 @@ public static string GenerateCode() // Inserts ApplyExecutionDiagnosticTagsFrame at the head of every chain. opts.Tracking.HandlerExecutionDiagnosticsEnabled = true; + + // Inserts RecordMessageCausationFrame after the handler call (a `this.`-qualified + // inherited instance call, now resolvable per JasperFx 2.2.4 / jasperfx#393). + opts.Tracking.EnableMessageCausationTracking = true; }) .Build(); diff --git a/src/Testing/Wolverine.Http.FSharpFixture/Generated.fs b/src/Testing/Wolverine.Http.FSharpFixture/Generated.fs index 44e81e55f..70e95a965 100644 --- a/src/Testing/Wolverine.Http.FSharpFixture/Generated.fs +++ b/src/Testing/Wolverine.Http.FSharpFixture/Generated.fs @@ -13,7 +13,7 @@ type GET_fsharp_hello(wolverineHttpOptions: Wolverine.Http.WolverineHttpOptions) inherit Wolverine.Http.HttpHandler(wolverineHttpOptions) let _wolverineHttpOptions = wolverineHttpOptions - override _.Handle(httpContext: Microsoft.AspNetCore.Http.HttpContext) : System.Threading.Tasks.Task = + override this.Handle(httpContext: Microsoft.AspNetCore.Http.HttpContext) : System.Threading.Tasks.Task = task { let thingEndpoints = Wolverine.Http.FSharpContracts.ThingEndpoints() @@ -23,3 +23,23 @@ type GET_fsharp_hello(wolverineHttpOptions: Wolverine.Http.WolverineHttpOptions) do! Wolverine.Http.HttpHandler.WriteString(httpContext, result_of_Hello) } +type POST_fsharp_things(wolverineHttpOptions: Wolverine.Http.WolverineHttpOptions) = + inherit Wolverine.Http.HttpHandler(wolverineHttpOptions) + let _wolverineHttpOptions = wolverineHttpOptions + + override this.Handle(httpContext: Microsoft.AspNetCore.Http.HttpContext) : System.Threading.Tasks.Task = + task { + // Reading the request body via JSON deserialization + let! (command, jsonContinue) = this.ReadJsonAsync(httpContext) + if jsonContinue = Wolverine.HandlerContinuation.Stop then + () + else + let thingEndpoints = Wolverine.Http.FSharpContracts.ThingEndpoints() + + // The actual HTTP request handler execution + let thingCreated_response = thingEndpoints.Create(command) + + // Writing the response body to JSON because this was the first 'return variable' in the method signature + do! this.WriteJsonAsync(httpContext, thingCreated_response) + } + diff --git a/src/Testing/Wolverine.Http.FSharpTests/HttpFSharpCodegenSample.cs b/src/Testing/Wolverine.Http.FSharpTests/HttpFSharpCodegenSample.cs index 60032482f..e4059681a 100644 --- a/src/Testing/Wolverine.Http.FSharpTests/HttpFSharpCodegenSample.cs +++ b/src/Testing/Wolverine.Http.FSharpTests/HttpFSharpCodegenSample.cs @@ -27,14 +27,13 @@ public static string GenerateCode() var container = new ServiceContainer(registry, registry.BuildServiceProvider()); var httpGraph = new HttpGraph(new WolverineOptions { ApplicationAssembly = typeof(ThingEndpoints).Assembly }, container); - // Phase C increment 1 renders the static-response GET endpoint. The JSON POST endpoint - // (ReadJsonBody / WriteJsonFrame) calls *instance* HttpHandler methods unqualified, which F# - // cannot resolve from a `member _.Handle` body — blocked on the JasperFx F# self-identifier - // gap (tracked upstream; same gap as RecordMessageCausationFrame). Add it back once JasperFx - // emits a named self for generated members. + // GET (static string response) + POST (JSON body bind + JSON response). The JSON path calls + // the inherited instance HttpHandler methods ReadJsonAsync/WriteJsonAsync, now qualified with + // the generated member's `this` self identifier (JasperFx 2.2.4 / jasperfx#393). var chains = new[] { - HttpChain.ChainFor(x => x.Hello(), httpGraph) + HttpChain.ChainFor(x => x.Hello(), httpGraph), + HttpChain.ChainFor(x => x.Create(null!), httpGraph) }; var generatedAssembly = httpGraph.StartAssembly(httpGraph.Rules); diff --git a/src/Wolverine/Runtime/RecordMessageCausationFrame.cs b/src/Wolverine/Runtime/RecordMessageCausationFrame.cs index c41aaec52..61005d551 100644 --- a/src/Wolverine/Runtime/RecordMessageCausationFrame.cs +++ b/src/Wolverine/Runtime/RecordMessageCausationFrame.cs @@ -45,4 +45,14 @@ public override void GenerateCode(GeneratedMethod method, ISourceWriter writer) Next?.GenerateCode(method, writer); } + + public override void GenerateFSharpCode(GeneratedMethod method, ISourceWriter writer) + { + // RecordCauseAndEffect is an inherited instance method on the generated MessageHandler subclass, + // so it must be qualified with the member's `this` self identifier (jasperfx#393). + writer.Write( + $"this.{nameof(MessageHandler.RecordCauseAndEffect)}({_context!.Usage}, {_context!.Usage}.Runtime.Observer)"); + + Next?.GenerateFSharpCode(method, writer); + } }