Skip to content

Commit

Permalink
Ability to read and bind data from headers to parameters. Closes GH-301
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeremy D. Miller authored and Jeremy D. Miller committed Apr 11, 2023
1 parent a92ea92 commit 29edb9e
Show file tree
Hide file tree
Showing 8 changed files with 284 additions and 0 deletions.
44 changes: 44 additions & 0 deletions src/Http/Wolverine.Http.Tests/header_binding.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using Alba;

namespace Wolverine.Http.Tests;

public class header_binding : IntegrationContext
{
public header_binding(AppFixture fixture) : base(fixture)
{
}

[Fact]
public async Task read_single_header_with_explicit_name_mapping()
{
await Scenario(x =>
{
x.WithRequestHeader("x-wolverine", "one");
x.Get.Url("/headers/simple");
x.ContentShouldBe("one");
});
}

[Fact]
public async Task read_single_parsed_header_with_explicit_name_mapping()
{
await Scenario(x =>
{
x.WithRequestHeader("x-wolverine", "111");
x.Get.Url("/headers/int");
x.ContentShouldBe("222");
});
}

[Fact]
public async Task read_header_by_variable_name()
{
await Scenario(x =>
{
x.WithRequestHeader("accepts", "text/plain");
x.Get.Url("/headers/accepts");
x.ContentShouldBe("text/plain");
});
}
}
94 changes: 94 additions & 0 deletions src/Http/Wolverine.Http/CodeGen/FromHeaderStrategy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
using System.Reflection;
using JasperFx.CodeGeneration;
using JasperFx.CodeGeneration.Frames;
using JasperFx.CodeGeneration.Model;
using Lamar;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Metadata;

namespace Wolverine.Http.CodeGen;

public class FromHeaderStrategy : IParameterStrategy
{
public bool TryMatch(HttpChain chain, IContainer container, ParameterInfo parameter, out Variable? variable)
{
var att = parameter.GetCustomAttributes().OfType<IFromHeaderMetadata>().FirstOrDefault();
if (att != null)
{
if (parameter.ParameterType == typeof(string))
{
var frame = new FromHeaderValue(att, parameter);
chain.Middleware.Add(frame);
variable = frame.Variable;
}
else
{
var frame = new ParsedHeaderValue(att, parameter);
chain.Middleware.Add(frame);
variable = frame.Variable;
}



return true;
}

variable = default;
return false;
}
}

internal class FromHeaderValue : SyncFrame
{
private Variable _httpContext;
private readonly string _header;

public FromHeaderValue(IFromHeaderMetadata header, ParameterInfo parameter)
{
Variable = new Variable(parameter.ParameterType, parameter.Name, this);
_header = header.Name ?? parameter.Name;
}

public Variable Variable { get; }

public override IEnumerable<Variable> FindVariables(IMethodVariables chain)
{
_httpContext = chain.FindVariable(typeof(HttpContext));
yield return _httpContext;
}

public override void GenerateCode(GeneratedMethod method, ISourceWriter writer)
{
writer.Write($"var {Variable.Usage} = {nameof(HttpHandler.ReadSingleHeaderValue)}({_httpContext.Usage}, \"{_header}\");");
Next?.GenerateCode(method, writer);
}
}

internal class ParsedHeaderValue : SyncFrame
{
private readonly string _header;
private Variable? _httpContext;

public ParsedHeaderValue(IFromHeaderMetadata header, ParameterInfo parameter)
{
_header = header.Name ?? parameter.Name!;
Variable = new Variable(parameter.ParameterType, parameter.Name!, this);
}

public override IEnumerable<Variable> FindVariables(IMethodVariables chain)
{
_httpContext = chain.FindVariable(typeof(HttpContext));
yield return _httpContext;
}

public Variable Variable { get; }

public override void GenerateCode(GeneratedMethod method, ISourceWriter writer)
{
var alias = Variable.VariableType.ShortNameInCode();
writer.Write($"{alias} {Variable.Usage} = default;");
writer.Write($"{alias}.TryParse({nameof(HttpHandler.ReadSingleHeaderValue)}({_httpContext!.Usage}, \"{_header}\"), out {Variable.Usage});");

Next?.GenerateCode(method, writer);
}
}
1 change: 1 addition & 0 deletions src/Http/Wolverine.Http/HttpGraph.ParameterMatching.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public partial class HttpGraph
new MessageBusStrategy(),
new HttpContextElements(),
new RouteParameterStrategy(),
new FromHeaderStrategy(),
new QueryStringParameterStrategy(),
new JsonBodyParameterStrategy()
};
Expand Down
10 changes: 10 additions & 0 deletions src/Http/Wolverine.Http/HttpHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ public HttpHandler(WolverineHttpOptions options)

public abstract Task Handle(HttpContext httpContext);

public static string? ReadSingleHeaderValue(HttpContext context, string headerKey)
{
return context.Request.Headers[headerKey].SingleOrDefault();
}

public static string[] ReadManyHeaderValues(HttpContext context, string headerKey)
{
return context.Request.Headers[headerKey].ToArray();
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Task WriteString(HttpContext context, string text)
{
Expand Down
26 changes: 26 additions & 0 deletions src/Http/WolverineWebApi/HeaderUsingEndpoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using JasperFx.Core;
using Microsoft.AspNetCore.Mvc;
using Wolverine.Http;

namespace WolverineWebApi;

public class HeaderUsingEndpoint
{
[WolverineGet("/headers/simple")]
public string Get([FromHeader(Name = "x-wolverine")] string name)
{
return name;
}

[WolverineGet("/headers/int")]
public string Get([FromHeader(Name = "x-wolverine")] int number)
{
return (number * 2).ToString();
}

[WolverineGet("/headers/accepts")]
public string GetETag([FromHeader] string accepts)
{
return accepts;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// <auto-generated/>
#pragma warning disable
using Microsoft.AspNetCore.Routing;
using System;
using System.Linq;
using Wolverine.Http;

namespace Internal.Generated.WolverineHandlers
{
// START: GET_headers_accepts
public class GET_headers_accepts : Wolverine.Http.HttpHandler
{
private readonly Wolverine.Http.WolverineHttpOptions _options;

public GET_headers_accepts(Wolverine.Http.WolverineHttpOptions options) : base(options)
{
_options = options;
}



public override async System.Threading.Tasks.Task Handle(Microsoft.AspNetCore.Http.HttpContext httpContext)
{
var headerUsingEndpoint = new WolverineWebApi.HeaderUsingEndpoint();
var accepts = ReadSingleHeaderValue(httpContext, "accepts");
var result_of_GetETag = headerUsingEndpoint.GetETag(accepts);
await WriteString(httpContext, result_of_GetETag);
}

}

// END: GET_headers_accepts


}

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// <auto-generated/>
#pragma warning disable
using Microsoft.AspNetCore.Routing;
using System;
using System.Linq;
using Wolverine.Http;

namespace Internal.Generated.WolverineHandlers
{
// START: GET_headers_int
public class GET_headers_int : Wolverine.Http.HttpHandler
{
private readonly Wolverine.Http.WolverineHttpOptions _options;

public GET_headers_int(Wolverine.Http.WolverineHttpOptions options) : base(options)
{
_options = options;
}



public override async System.Threading.Tasks.Task Handle(Microsoft.AspNetCore.Http.HttpContext httpContext)
{
var headerUsingEndpoint = new WolverineWebApi.HeaderUsingEndpoint();
int number = default;
int.TryParse(ReadSingleHeaderValue(httpContext, "x-wolverine"), out number);
var result_of_Get = headerUsingEndpoint.Get(number);
await WriteString(httpContext, result_of_Get);
}

}

// END: GET_headers_int


}

Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// <auto-generated/>
#pragma warning disable
using Microsoft.AspNetCore.Routing;
using System;
using System.Linq;
using Wolverine.Http;

namespace Internal.Generated.WolverineHandlers
{
// START: GET_headers_simple
public class GET_headers_simple : Wolverine.Http.HttpHandler
{
private readonly Wolverine.Http.WolverineHttpOptions _options;

public GET_headers_simple(Wolverine.Http.WolverineHttpOptions options) : base(options)
{
_options = options;
}



public override async System.Threading.Tasks.Task Handle(Microsoft.AspNetCore.Http.HttpContext httpContext)
{
var headerUsingEndpoint = new WolverineWebApi.HeaderUsingEndpoint();
var name = ReadSingleHeaderValue(httpContext, "x-wolverine");
var result_of_Get = headerUsingEndpoint.Get(name);
await WriteString(httpContext, result_of_Get);
}

}

// END: GET_headers_simple


}

0 comments on commit 29edb9e

Please sign in to comment.