Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/Http/Wolverine.Http.Marten/AggregateAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,16 @@ public override Variable Modify(HttpChain chain, ParameterInfo parameter, IServi
return v3;
}

if (chain.FindQuerystringVariable(idType, "id", out var v4))
{
return v4;
}

if (chain.FindQuerystringVariable(idType, $"{AggregateType.Name.ToCamelCase()}Id", out var v5))
{
return v5;
}

return null;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,32 @@ await Scenario(x =>
order.ShouldNotBeNull();
order.Shipped.HasValue.ShouldBeTrue();
}

[Fact]
public async Task use_aggregate_in_endpoint_from_query_param_in_url()
{
var result1 = await Scenario(x =>
{
x.Post.Json(new StartOrder(["Socks", "Shoes", "Shirt"])).ToUrl("/orders/create");
});

var status1 = result1.ReadAsJson<OrderStatus>();
status1.ShouldNotBeNull();

await Scenario(x =>
{
x.Post.Url($"/orders/ship/from-query?id={status1.OrderId}");

x.StatusCodeShouldBe(204);
});

await using var session = Store.LightweightSession();

var order = await session.Events.AggregateStreamAsync<Order>(status1.OrderId);

order.ShouldNotBeNull();
order.Shipped.HasValue.ShouldBeTrue();
}

[Fact]
public async Task use_stream_collision_policy()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,19 @@ await Scenario(x =>
x.StatusCodeShouldBe(404);
});
}

[Fact]
public async Task happy_path_reading_aggregate_from_query()
{
var id = Guid.NewGuid();

// Creating a new order
await Scenario(x =>
{
x.Post.Json(new StartOrderWithId(id, ["Socks", "Shoes", "Shirt"])).ToUrl("/orders/create4");
});

var result = await Host.GetAsJson<Order>("/orders/latest/from-query?id=" + id);
result.Items.Keys.ShouldContain("Socks");
}
}
5 changes: 5 additions & 0 deletions src/Http/Wolverine.Http/HttpChain.ApiDescription.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ public override bool TryFindVariable(string valueName, ValueSource source, Type
return true;
}

if ((source == ValueSource.FromQueryString || source == ValueSource.Anything) && FindQuerystringVariable(valueType, valueName, out variable))
{
return true;
}

if (HasRequestType)
{
var requestType = InputType();
Expand Down
17 changes: 17 additions & 0 deletions src/Http/Wolverine.Http/HttpChain.cs
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,23 @@ private void applyMetadata()

return variable;
}

public bool FindQuerystringVariable(Type variableType, string routeOrParameterName, [NotNullWhen(true)]out Variable? variable)
{
var matched = Method.Method.GetParameters()
.FirstOrDefault(x => x.ParameterType == variableType && x.Name != null && x.Name.EqualsIgnoreCase(routeOrParameterName));
if (matched is not null)
{
variable = TryFindOrCreateQuerystringValue(matched);
if (variable is not null)
{
return true;
}
}

variable = null;
return false;
}

public HttpElementVariable? TryFindOrCreateQuerystringValue(ParameterInfo parameter)
{
Expand Down
17 changes: 17 additions & 0 deletions src/Http/WolverineWebApi/Marten/Orders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,20 @@ public static OrderShipped Ship4([Aggregate] Order order)
return new OrderShipped();
}

#region sample_using_aggregate_attribute_query_parameter

[WolverinePost("/orders/ship/from-query"), EmptyResponse]
// The OrderShipped return value is treated as an event being posted
// to a Marten even stream
// instead of as the HTTP response body because of the presence of
// the [EmptyResponse] attribute
public static OrderShipped ShipFromQuery([FromQuery] Guid id, [Aggregate] Order order)
{
return new OrderShipped();
}

#endregion

[Transactional]
[WolverinePost("/orders/create")]
public static OrderStatus StartOrder(StartOrder command, IDocumentSession session)
Expand Down Expand Up @@ -305,6 +319,9 @@ public static (UpdatedAggregate, OrderConfirmed) ConfirmDifferent2(ConfirmOrder

[WolverineGet("/orders/V1.0/latest/{id}")]
public static Order GetLatestV1(Guid id, [ReadAggregate] Order order) => order;

[WolverineGet("/orders/latest/from-query")]
public static Order GetLatestFromQuery([FromQuery] Guid id, [ReadAggregate] Order order) => order;
}

#region sample_using_[FromQuery]_binding
Expand Down
7 changes: 6 additions & 1 deletion src/Wolverine/Attributes/ModifyChainAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ public enum ValueSource
/// <summary>
/// The value should be sourced by a route argument of an HTTP request
/// </summary>
RouteValue
RouteValue,

/// <summary>
/// The value should be sourced by a query string parameter of an HTTP request
/// </summary>
FromQueryString
}

#endregion
5 changes: 0 additions & 5 deletions src/Wolverine/Attributes/WolverineParameterAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,6 @@ protected bool tryFindIdentityVariable(IChain chain, ParameterInfo parameter, Ty
{
return true;
}

if (chain.TryFindVariable("id", ValueSource, idType, out variable))
Copy link
Copy Markdown
Contributor Author

@erdtsieck erdtsieck Jul 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is duplicate with the code above, so I removed it

{
return true;
}

variable = default;
return false;
Expand Down
Loading