-
-
Notifications
You must be signed in to change notification settings - Fork 128
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ability to use IEndpointMetadataProvider on resource types, and for a…
- Loading branch information
Jeremy D. Miller
authored and
Jeremy D. Miller
committed
Apr 19, 2023
1 parent
8c14775
commit 39337d8
Showing
7 changed files
with
253 additions
and
1 deletion.
There are no files selected for viewing
60 changes: 60 additions & 0 deletions
60
...ttp/Wolverine.Http.Tests/using_create_response_and_metadata_derived_from_response_type.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
using JasperFx.Core; | ||
using Microsoft.AspNetCore.Http.Metadata; | ||
using Shouldly; | ||
using WolverineWebApi; | ||
|
||
namespace Wolverine.Http.Tests; | ||
|
||
public class using_create_response_and_metadata_derived_from_response_type : IntegrationContext | ||
{ | ||
public using_create_response_and_metadata_derived_from_response_type(AppFixture fixture) : base(fixture) | ||
{ | ||
} | ||
|
||
|
||
[Fact] | ||
public void read_metadata_from_IEndpointMetadataProvider() | ||
{ | ||
var chain = HttpChain.ChainFor<CreateEndpoint>(x => x.Create(null)); | ||
|
||
var endpoint = chain.BuildEndpoint(); | ||
|
||
// Should remove the 200 OK response | ||
endpoint | ||
.Metadata | ||
.OfType<IProducesResponseTypeMetadata>() | ||
.Any(x => x.StatusCode == 200) | ||
.ShouldBeFalse(); | ||
|
||
var responseMetadata = endpoint | ||
.Metadata | ||
.OfType<IProducesResponseTypeMetadata>() | ||
.FirstOrDefault(x => x.StatusCode == 201); | ||
|
||
responseMetadata.ShouldNotBeNull(); | ||
responseMetadata.Type.ShouldBe(typeof(IssueCreated)); | ||
} | ||
|
||
[Fact] | ||
public async Task make_the_request() | ||
{ | ||
await Store.Advanced.Clean.DeleteDocumentsByTypeAsync(typeof(Issue)); | ||
|
||
var result = await Scenario(x => | ||
{ | ||
x.Post.Json(new CreateIssue("It's bad")).ToUrl("/issue"); | ||
x.StatusCodeShouldBe(201); | ||
}); | ||
|
||
var created = result.ReadAsJson<IssueCreated>(); | ||
created.ShouldNotBeNull(); | ||
|
||
using var session = Store.LightweightSession(); | ||
var issue = await session.LoadAsync<Issue>(created.Id); | ||
issue.ShouldNotBeNull(); | ||
issue.Title.ShouldBe("It's bad"); | ||
|
||
result.Context.Response.Headers.Location.Single().ShouldBe("/issue/" + created.Id); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
using System.Reflection; | ||
using JasperFx.CodeGeneration; | ||
using JasperFx.CodeGeneration.Frames; | ||
using JasperFx.CodeGeneration.Model; | ||
using JasperFx.Core; | ||
using JasperFx.Core.Reflection; | ||
using Lamar; | ||
using Microsoft.AspNetCore.Builder; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Http.Metadata; | ||
|
||
namespace Wolverine.Http; | ||
|
||
/// <summary> | ||
/// Interface for resource types in Wolverine.Http that need to modify | ||
/// how the HTTP response is formatted. Use this for additional headers | ||
/// or customized status codes | ||
/// </summary> | ||
public interface IHttpAware : IEndpointMetadataProvider | ||
{ | ||
void Apply(HttpContext context); | ||
} | ||
|
||
internal class HttpAwarePolicy : IHttpPolicy | ||
{ | ||
public void Apply(IReadOnlyList<HttpChain> chains, GenerationRules rules, IContainer container) | ||
{ | ||
var matching = chains.Where(x => x.ResourceType != null && x.ResourceType.CanBeCastTo(typeof(IHttpAware))); | ||
foreach (var chain in matching) | ||
{ | ||
var resource = chain.Method.Creates.FirstOrDefault(x => x.VariableType == chain.ResourceType); | ||
if (resource == null) return; | ||
|
||
var apply = new MethodCall(typeof(IHttpAware), nameof(IHttpAware.Apply)) | ||
{ | ||
Target = new CastVariable(resource, typeof(IHttpAware)) | ||
}; | ||
|
||
// This will have to run before any kind of resource writing | ||
chain.Postprocessors.Insert(0, apply); | ||
} | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Base class for resource types that denote some kind of resource being created | ||
/// in the system. Wolverine specific, and more efficient, version of Created<T> from ASP.Net Core | ||
/// </summary> | ||
public abstract record CreationResponse : IHttpAware | ||
{ | ||
public static void PopulateMetadata(MethodInfo method, EndpointBuilder builder) | ||
{ | ||
builder.Metadata.RemoveAll(x => x is IProducesResponseTypeMetadata m && m.StatusCode == 200); | ||
|
||
var create = new MethodCall(method.DeclaringType, method).Creates.FirstOrDefault()?.VariableType; | ||
var metadata = new Metadata { Type = create, StatusCode = 201 }; | ||
builder.Metadata.Add(metadata); | ||
} | ||
|
||
protected virtual string Url() => string.Empty; | ||
|
||
void IHttpAware.Apply(HttpContext context) | ||
{ | ||
context.Response.Headers.Location = Url(); | ||
context.Response.StatusCode = 201; | ||
} | ||
|
||
internal class Metadata : IProducesResponseTypeMetadata | ||
{ | ||
public Type? Type { get; init; } | ||
public int StatusCode { get; init; } | ||
public IEnumerable<string> ContentTypes => new string[] { "application/json" }; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
using Marten.Schema.Identity; | ||
using Wolverine.Http; | ||
using Wolverine.Marten; | ||
|
||
namespace WolverineWebApi; | ||
|
||
public class CreateEndpoint | ||
{ | ||
[WolverinePost("/issue")] | ||
public (IssueCreated, InsertDoc<Issue>) Create(CreateIssue command) | ||
{ | ||
var id = CombGuidIdGeneration.NewGuid(); | ||
var issue = new Issue | ||
{ | ||
Id = id, Title = command.Title | ||
}; | ||
|
||
return (new IssueCreated(id), MartenOps.Insert(issue)); | ||
} | ||
} | ||
|
||
public record CreateIssue(string Title); | ||
|
||
public record IssueCreated(Guid Id) : CreationResponse | ||
{ | ||
protected override string Url() | ||
{ | ||
return "/issue/" + Id; | ||
} | ||
} | ||
|
||
public class Issue | ||
{ | ||
public Guid Id { get; set; } | ||
public string Title { get; set; } | ||
} |
57 changes: 57 additions & 0 deletions
57
src/Http/WolverineWebApi/Internal/Generated/WolverineHandlers/POST_issue.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// <auto-generated/> | ||
#pragma warning disable | ||
using Microsoft.AspNetCore.Routing; | ||
using System; | ||
using System.Linq; | ||
using Wolverine.Http; | ||
using Wolverine.Marten.Publishing; | ||
using Wolverine.Runtime; | ||
|
||
namespace Internal.Generated.WolverineHandlers | ||
{ | ||
// START: POST_issue | ||
public class POST_issue : Wolverine.Http.HttpHandler | ||
{ | ||
private readonly Wolverine.Http.WolverineHttpOptions _options; | ||
private readonly Wolverine.Marten.Publishing.OutboxedSessionFactory _outboxedSessionFactory; | ||
private readonly Wolverine.Runtime.IWolverineRuntime _wolverineRuntime; | ||
|
||
public POST_issue(Wolverine.Http.WolverineHttpOptions options, Wolverine.Marten.Publishing.OutboxedSessionFactory outboxedSessionFactory, Wolverine.Runtime.IWolverineRuntime wolverineRuntime) : base(options) | ||
{ | ||
_options = options; | ||
_outboxedSessionFactory = outboxedSessionFactory; | ||
_wolverineRuntime = wolverineRuntime; | ||
} | ||
|
||
|
||
|
||
public override async System.Threading.Tasks.Task Handle(Microsoft.AspNetCore.Http.HttpContext httpContext) | ||
{ | ||
var messageContext = new Wolverine.Runtime.MessageContext(_wolverineRuntime); | ||
await using var documentSession = _outboxedSessionFactory.OpenSession(messageContext); | ||
var createEndpoint = new WolverineWebApi.CreateEndpoint(); | ||
var (command, jsonContinue) = await ReadJsonAsync<WolverineWebApi.CreateIssue>(httpContext); | ||
if (jsonContinue == Wolverine.HandlerContinuation.Stop) return; | ||
(var issueCreated, var insertDoc) = createEndpoint.Create(command); | ||
|
||
// Outgoing, cascaded message | ||
await messageContext.EnqueueCascadingAsync(insertDoc).ConfigureAwait(false); | ||
|
||
((Wolverine.Http.IHttpAware)issueCreated).Apply(httpContext); | ||
|
||
// Placed by Wolverine's ISideEffect policy | ||
insertDoc.Execute(documentSession); | ||
|
||
await WriteJsonAsync(httpContext, issueCreated); | ||
|
||
// Commit the unit of work | ||
await documentSession.SaveChangesAsync(httpContext.RequestAborted).ConfigureAwait(false); | ||
} | ||
|
||
} | ||
|
||
// END: POST_issue | ||
|
||
|
||
} | ||
|