diff --git a/src/Http/Wolverine.Http.Tests/Bugs/Bug_1941_do_not_try_to_parse_IPAddress.cs b/src/Http/Wolverine.Http.Tests/Bugs/Bug_1941_do_not_try_to_parse_IPAddress.cs new file mode 100644 index 000000000..ed11ee6d2 --- /dev/null +++ b/src/Http/Wolverine.Http.Tests/Bugs/Bug_1941_do_not_try_to_parse_IPAddress.cs @@ -0,0 +1,77 @@ +using System.Net; +using Alba; +using IntegrationTests; +using Marten; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Wolverine.Marten; + +namespace Wolverine.Http.Tests.Bugs; + +public class Bug_1941_do_not_try_to_parse_IPAddress +{ + [Fact] + public async Task pipe_ipaddress_from_load_to_main_handle_method() + { + var builder = WebApplication.CreateBuilder([]); + + builder.Services.AddMarten(opts => + { + // Establish the connection string to your Marten database + opts.Connection(Servers.PostgresConnectionString); + opts.DisableNpgsqlLogging = true; + }).IntegrateWithWolverine(); + + builder.Host.UseWolverine(opts => + { + opts.Discovery.DisableConventionalDiscovery().IncludeType(typeof(IpEndpoint)); + opts.ApplicationAssembly = GetType().Assembly; + }); + + builder.Services.AddWolverineHttp(); + + // This is using Alba, which uses WebApplicationFactory under the covers + await using var host = await AlbaHost.For(builder, app => + { + app.MapWolverineEndpoints(x => + { + x.AddMiddleware(typeof(RequestIpMiddleware)); + }); + }); + + var result = await host.Scenario(x => + { + x.Post.Json(new IpRequest { Name = "Jeremy", Age = 51 }).ToUrl("/ip"); + }); + } +} + +public class RequestIpMiddleware +{ + public static (IResult, IPAddress?) LoadAsync(HttpContext httpContext) + { + var ip = httpContext.Connection.RemoteIpAddress; + if (ip != null && ip.IsIPv4MappedToIPv6) + { + ip = ip.MapToIPv4(); + } + return (WolverineContinue.Result(), ip); + } +} + +public class IpRequest +{ + public string Name { get; set; } + public int Age { get; set; } +} + +public static class IpEndpoint +{ + + [WolverinePost("/ip")] + public static string Get(IpRequest request, IPAddress? address) + { + return address?.ToString() ?? "no address"; + } +} \ 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 858987d8f..d5ac0d270 100644 --- a/src/Http/Wolverine.Http/CodeGen/ResultContinuationPolicy.cs +++ b/src/Http/Wolverine.Http/CodeGen/ResultContinuationPolicy.cs @@ -16,6 +16,13 @@ public bool TryFindContinuationHandler(IChain chain, MethodCall call, out Frame? if (result != null) { + // Preventing double generation + if (chain.Middleware.OfType().Any(x => ReferenceEquals(x.Result, result))) + { + frame = null; + return false; + } + frame = new MaybeEndWithResultFrame(result); return true; } @@ -31,15 +38,16 @@ public bool TryFindContinuationHandler(IChain chain, MethodCall call, out Frame? /// public class MaybeEndWithResultFrame : AsyncFrame { - private readonly Variable _result; private Variable? _context; public MaybeEndWithResultFrame(Variable result) { uses.Add(result); - _result = result; + Result = result; } + public Variable Result { get; } + public override IEnumerable FindVariables(IMethodVariables chain) { _context = chain.FindVariable(typeof(HttpContext)); @@ -48,9 +56,17 @@ public override IEnumerable FindVariables(IMethodVariables chain) public override void GenerateCode(GeneratedMethod method, ISourceWriter writer) { + // Super hacky. Cannot for the life of me stop the double generation of "maybe end with IResult", so + // this. + if (Next is MaybeEndWithResultFrame next && ReferenceEquals(next.Result, Result)) + { + Next?.GenerateCode(method, writer); + return; + } + writer.WriteComment("Evaluate whether or not the execution should be stopped based on the IResult value"); - writer.Write($"BLOCK:if ({_result.Usage} != null && !({_result.Usage} is {typeof(WolverineContinue).FullNameInCode()}))"); - writer.Write($"await {_result.Usage}.{nameof(IResult.ExecuteAsync)}({_context!.Usage}).ConfigureAwait(false);"); + writer.Write($"BLOCK:if ({Result.Usage} != null && !({Result.Usage} is {typeof(WolverineContinue).FullNameInCode()}))"); + writer.Write($"await {Result.Usage}.{nameof(IResult.ExecuteAsync)}({_context!.Usage}).ConfigureAwait(false);"); writer.Write("return;"); writer.FinishBlock(); writer.BlankLine(); diff --git a/src/Http/Wolverine.Http/CodeGen/RouteHandling.cs b/src/Http/Wolverine.Http/CodeGen/RouteHandling.cs index fb0f35a5a..3c46a910a 100644 --- a/src/Http/Wolverine.Http/CodeGen/RouteHandling.cs +++ b/src/Http/Wolverine.Http/CodeGen/RouteHandling.cs @@ -1,3 +1,4 @@ +using System.Net; using System.Reflection; using JasperFx; using JasperFx.CodeGeneration; @@ -56,6 +57,9 @@ public bool TryMatch(HttpChain chain, IServiceContainer container, ParameterInfo public static bool CanParse(Type argType) { + // Edge case from GH-1941 + if (argType == typeof(IPAddress)) return false; + return TypeOutputs.ContainsKey(argType) || argType.IsEnum || argType.GetMethods().Any( x=> x.Name == "TryParse"); }