Skip to content

Commit

Permalink
ability to use array types as query string values in Wolverine.HTTP. C…
Browse files Browse the repository at this point in the history
…loses GH-1137
  • Loading branch information
jeremydmiller committed Nov 21, 2024
1 parent 374293d commit 9721c36
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 23 deletions.
78 changes: 78 additions & 0 deletions src/Http/Wolverine.Http.Tests/using_querystring_parameters.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
using JasperFx.CodeGeneration.Frames;
using Shouldly;
using Wolverine.Http.CodeGen;
using Wolverine.Runtime;
using WolverineWebApi;

namespace Wolverine.Http.Tests;

Expand Down Expand Up @@ -198,6 +202,68 @@ public async Task use_parsed_enum_collection()
body.ReadAsText().ShouldBe($"North,East,South");
}

[Fact]
public async Task using_string_array_completely_hit()
{
var body = await Scenario(x =>
{
x.Get
.Url("/querystring/stringarray")
.QueryString("values", "foo")
.QueryString("values", "bar")
.QueryString("values", "baz");
x.Header("content-type").SingleValueShouldEqual("text/plain");
});

body.ReadAsText().ShouldBe("foo,bar,baz");
}

[Fact]
public async Task using_string_array_completely_miss()
{
var body = await Scenario(x =>
{
x.Get
.Url("/querystring/stringarray");
x.Header("content-type").SingleValueShouldEqual("text/plain");
});

body.ReadAsText().ShouldBe("none");
}

[Fact]
public async Task using_int_array_completely_hit()
{
var body = await Scenario(x =>
{
x.Get
.Url("/querystring/intarray")
.QueryString("values", "4")
.QueryString("values", "2")
.QueryString("values", "1");
x.Header("content-type").SingleValueShouldEqual("text/plain");
});

body.ReadAsText().ShouldBe("1,2,4");
}

[Fact]
public async Task using_int_array_completely_miss()
{
var body = await Scenario(x =>
{
x.Get
.Url("/querystring/intarray");
x.Header("content-type").SingleValueShouldEqual("text/plain");
});

body.ReadAsText().ShouldBe("none");
}

#region sample_query_string_usage

[Fact]
Expand Down Expand Up @@ -238,4 +304,16 @@ public async Task use_decimal_querystring_hit()
}

#endregion

[Fact]
public void trouble_shoot_querystring_matching()
{
var method = new MethodCall(typeof(QuerystringEndpoints), "IntArray");
var chain = new HttpChain(method, new HttpGraph(new WolverineOptions(), ServiceContainer.Empty()));

var parameter = method.Method.GetParameters().Single();

var variable = chain.TryFindOrCreateQuerystringValue(parameter);
variable.Creator.ShouldBeOfType<ParsedArrayQueryStringValue>();
}
}
56 changes: 56 additions & 0 deletions src/Http/Wolverine.Http/CodeGen/ParsedArrayQueryStringValue.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System.Reflection;
using JasperFx.CodeGeneration;
using JasperFx.CodeGeneration.Frames;
using JasperFx.Core.Reflection;

namespace Wolverine.Http.CodeGen;

internal class ParsedArrayQueryStringValue : SyncFrame
{
public ParsedArrayQueryStringValue(ParameterInfo parameter)
{
Variable = new QuerystringVariable(parameter.ParameterType, parameter.Name!, this);
}

public QuerystringVariable Variable { get; }

public override void GenerateCode(GeneratedMethod method, ISourceWriter writer)
{
var elementType = Variable.VariableType.GetElementType();
if (elementType == typeof(string))
{
writer.Write($"var {Variable.Usage} = httpContext.Request.Query[\"{Variable.Usage}\"].ToArray();");
}
else
{
var collectionAlias = typeof(List<>).MakeGenericType(elementType).FullNameInCode();
var elementAlias = elementType.FullNameInCode();

writer.Write($"var {Variable.Usage}_List = new {collectionAlias}();");

writer.Write($"BLOCK:foreach (var {Variable.Usage}Value in httpContext.Request.Query[\"{Variable.Usage}\"])");

if (elementType.IsEnum)
{
writer.Write($"BLOCK:if ({elementAlias}.TryParse<{elementAlias}>({Variable.Usage}Value, out var {Variable.Usage}ValueParsed))");
}
else if (elementType.IsBoolean())
{
writer.Write($"BLOCK:if ({elementAlias}.TryParse({Variable.Usage}Value, out var {Variable.Usage}ValueParsed))");
}
else
{
writer.Write($"BLOCK:if ({elementAlias}.TryParse({Variable.Usage}Value, System.Globalization.CultureInfo.InvariantCulture, out var {Variable.Usage}ValueParsed))");
}

writer.Write($"{Variable.Usage}_List.Add({Variable.Usage}ValueParsed);");
writer.FinishBlock(); // parsing block

writer.FinishBlock(); // foreach blobck

writer.Write($"var {Variable.Usage} = {Variable.Usage}_List.ToArray();");
}

Next?.GenerateCode(method, writer);
}
}
16 changes: 16 additions & 0 deletions src/Http/Wolverine.Http/HttpChain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,14 @@ private void applyMetadata()
if (parameter.ParameterType == typeof(string))
{
variable = new ReadStringQueryStringValue(key).Variable;
variable.Name = key;
_querystringVariables.Add(variable);
}

if (parameter.ParameterType == typeof(string[]))
{
variable = new ParsedArrayQueryStringValue(parameter).Variable;
variable.Name = key;
_querystringVariables.Add(variable);
}

Expand All @@ -330,10 +338,18 @@ private void applyMetadata()
_querystringVariables.Add(variable);
}
}

if (parameter.ParameterType.IsArray && RouteParameterStrategy.CanParse(parameter.ParameterType.GetElementType()))
{
variable = new ParsedArrayQueryStringValue(parameter).Variable;
variable.Name = key;
_querystringVariables.Add(variable);
}

if (ParsedCollectionQueryStringValue.CanParse(parameter.ParameterType))
{
variable = new ParsedCollectionQueryStringValue(parameter).Variable;
variable.Name = key;
_querystringVariables.Add(variable);
}

Expand Down
44 changes: 44 additions & 0 deletions src/Http/WolverineWebApi/QuerystringEndpoints.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using JasperFx.Core;
using Marten;
using Microsoft.AspNetCore.Mvc;
using Wolverine.Http;

namespace WolverineWebApi;

public static class QuerystringEndpoints
{

[WolverineGet("/querystring/enum")]
public static string UsingEnumQuerystring(Direction direction)
{
return direction.ToString();
}

[WolverineGet("/querystring/explicit")]
public static string UsingEnumQuerystring([FromQuery(Name = "name")]string value)
{
return value ?? "";
}

[WolverineGet("/querystring/enum/nullable")]
public static string UsingNullableEnumQuerystring(Direction? direction)
{
return direction?.ToString() ?? "none";
}

[WolverineGet("/querystring/stringarray")]
public static string StringArray(string[]? values)
{
if (values == null || values.IsEmpty()) return "none";

return values.Join(",");
}

[WolverineGet("/querystring/intarray")]
public static string IntArray(int[]? values)
{
if (values == null || values.IsEmpty()) return "none";

return values.OrderBy(x => x).Select(x => x.ToString()).Join(",");
}
}
26 changes: 3 additions & 23 deletions src/Http/WolverineWebApi/TestEndpoints.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System.Globalization;
using Marten;
using Microsoft.AspNetCore.Mvc;
using Wolverine.Http;
using JasperFx.Core;

namespace WolverineWebApi;

Expand Down Expand Up @@ -136,28 +136,8 @@ public static string UsingEnumCollection(IEnumerable<Direction> collection)
{
return string.Join(",", collection);
}
}

public static class QuerystringEndpoints
{

[WolverineGet("/querystring/enum")]
public static string UsingEnumQuerystring(Direction direction)
{
return direction.ToString();
}

[WolverineGet("/querystring/explicit")]
public static string UsingEnumQuerystring([FromQuery(Name = "name")]string value)
{
return value ?? "";
}

[WolverineGet("/querystring/enum/nullable")]
public static string UsingNullableEnumQuerystring(Direction? direction)
{
return direction?.ToString() ?? "none";
}


}

public class ArithmeticResults
Expand Down

0 comments on commit 9721c36

Please sign in to comment.