Skip to content

Commit

Permalink
Added new [Document] attribute in the Marten HTTP support
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremydmiller committed Oct 31, 2023
1 parent 488fb78 commit b18f69f
Show file tree
Hide file tree
Showing 11 changed files with 480 additions and 91 deletions.
1 change: 1 addition & 0 deletions build/build.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,7 @@ private static void Main(string[] args)
"./src/Extensions/Wolverine.MessagePack",
"./src/Http/Wolverine.Http",
"./src/Http/Wolverine.Http.FluentValidation",
"./src/Http/Wolverine.Http.Marten",
};

Target("pack", ForEach(nugetProjects), project =>
Expand Down
75 changes: 1 addition & 74 deletions src/Http/Wolverine.Http.Marten/AggregateAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Data;
using System.Reflection;
using JasperFx.CodeGeneration;
using JasperFx.CodeGeneration.Frames;
using JasperFx.CodeGeneration.Model;
using JasperFx.Core;
Expand Down Expand Up @@ -157,76 +156,4 @@ private MethodCall generateLoadAggregateCode(IChain chain)

}

// TODO -- this should absolutely be in JasperFx.CodeGeneration
internal class MemberAccessFrame : SyncFrame
{
private readonly Type _targetType;
private readonly MemberInfo _member;
private Variable _parent;
public Variable Variable { get; }

public MemberAccessFrame(Type targetType, MemberInfo member, string name)
{
_targetType = targetType;
_member = member;
Variable = new Variable(member.GetMemberType(), name, this);
}

public override void GenerateCode(GeneratedMethod method, ISourceWriter writer)
{
writer.Write($"var {Variable.Usage} = {_parent.Usage}.{_member.Name};");
Next?.GenerateCode(method, writer);
}

public override IEnumerable<Variable> FindVariables(IMethodVariables chain)
{
_parent = chain.FindVariable(_targetType);
yield return _parent;
}
}

internal class LoadAggregateFrame<T> : MethodCall where T : class
{
private readonly AggregateAttribute _att;

public LoadAggregateFrame(AggregateAttribute att) : base(typeof(IEventStore), FindMethod(att))
{
_att = att;
CommentText = "Loading Marten aggregate";
}

public override IEnumerable<Variable> FindVariables(IMethodVariables chain)
{
Arguments[0] = _att.IdVariable;
if (_att.LoadStyle == ConcurrencyStyle.Optimistic && _att.VersionVariable != null)
{
Arguments[1] = _att.VersionVariable;
}

foreach (var variable in base.FindVariables(chain)) yield return variable;
}

internal static MethodInfo FindMethod(AggregateAttribute att)
{
var isGuidIdentified = att.IdVariable.VariableType == typeof(Guid);

if (att.LoadStyle == ConcurrencyStyle.Exclusive)
{
return isGuidIdentified
? ReflectionHelper.GetMethod<IEventStore>(x => x.FetchForExclusiveWriting<T>(Guid.Empty, default))!
: ReflectionHelper.GetMethod<IEventStore>(x => x.FetchForExclusiveWriting<T>(string.Empty, default))!;
}

if (att.VersionVariable == null)
{
return isGuidIdentified
? ReflectionHelper.GetMethod<IEventStore>(x => x.FetchForWriting<T>(Guid.Empty, default))!
: ReflectionHelper.GetMethod<IEventStore>(x => x.FetchForWriting<T>(string.Empty, default))!;
}

return isGuidIdentified
? ReflectionHelper.GetMethod<IEventStore>(x => x.FetchForWriting<T>(Guid.Empty, long.MaxValue, default))!
: ReflectionHelper.GetMethod<IEventStore>(x => x.FetchForWriting<T>(string.Empty, long.MaxValue, default))!;
}
}

// TODO -- this should absolutely be in JasperFx.CodeGeneration
71 changes: 71 additions & 0 deletions src/Http/Wolverine.Http.Marten/DocumentAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System.Reflection;
using JasperFx.CodeGeneration.Frames;
using JasperFx.CodeGeneration.Model;
using JasperFx.Core;
using Lamar;
using Marten;

namespace Wolverine.Http.Marten;

/// <summary>
/// Marks a parameter to an HTTP endpoint as being loaded as a Marten
/// document identified by a route argument. If the route argument
/// is not specified, this would look for either "typeNameId" or "id"
/// </summary>
[AttributeUsage(AttributeTargets.Parameter)]
public class DocumentAttribute : HttpChainParameterAttribute
{
public string? RouteArgumentName { get; }

public DocumentAttribute()
{
}

public DocumentAttribute(string routeArgumentName)
{
RouteArgumentName = routeArgumentName;
}

public override Variable Modify(HttpChain chain, ParameterInfo parameter, IContainer container)
{
var store = container.GetInstance<IDocumentStore>();
var documentType = parameter.ParameterType;
var mapping = store.Options.FindOrResolveDocumentType(documentType);
var idType = mapping.IdType;

var argument = FindRouteVariable(idType, documentType, chain);

var loader = typeof(IQuerySession).GetMethods()
.FirstOrDefault(x => x.Name == nameof(IDocumentSession.LoadAsync) && x.GetParameters()[0].ParameterType == idType);

var load = new MethodCall(typeof(IDocumentSession), loader.MakeGenericMethod(documentType));
load.Arguments[0] = argument;

chain.Middleware.Add(load);

return load.ReturnVariable;
}

public Variable? FindRouteVariable(Type idType, Type documentType, HttpChain chain)
{
if (RouteArgumentName.IsNotEmpty())
{
if (chain.FindRouteVariable(idType, RouteArgumentName, out var variable))
{
return variable;
}
}

if (chain.FindRouteVariable(idType, $"{documentType.Name.ToCamelCase()}Id", out var v2))
{
return v2;
}

if (chain.FindRouteVariable(idType, "id", out var v3))
{
return v3;
}

return null;
}
}
53 changes: 53 additions & 0 deletions src/Http/Wolverine.Http.Marten/LoadAggregateFrame.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System.Reflection;
using JasperFx.CodeGeneration.Frames;
using JasperFx.CodeGeneration.Model;
using JasperFx.Core.Reflection;
using Marten.Events;
using Wolverine.Marten;

namespace Wolverine.Http.Marten;

internal class LoadAggregateFrame<T> : MethodCall where T : class
{
private readonly AggregateAttribute _att;

public LoadAggregateFrame(AggregateAttribute att) : base(typeof(IEventStore), FindMethod(att))
{
_att = att;
CommentText = "Loading Marten aggregate";
}

public override IEnumerable<Variable> FindVariables(IMethodVariables chain)
{
Arguments[0] = _att.IdVariable;
if (_att.LoadStyle == ConcurrencyStyle.Optimistic && _att.VersionVariable != null)
{
Arguments[1] = _att.VersionVariable;
}

foreach (var variable in base.FindVariables(chain)) yield return variable;
}

internal static MethodInfo FindMethod(AggregateAttribute att)
{
var isGuidIdentified = att.IdVariable.VariableType == typeof(Guid);

if (att.LoadStyle == ConcurrencyStyle.Exclusive)
{
return isGuidIdentified
? ReflectionHelper.GetMethod<IEventStore>(x => x.FetchForExclusiveWriting<T>(Guid.Empty, default))!
: ReflectionHelper.GetMethod<IEventStore>(x => x.FetchForExclusiveWriting<T>(string.Empty, default))!;
}

if (att.VersionVariable == null)
{
return isGuidIdentified
? ReflectionHelper.GetMethod<IEventStore>(x => x.FetchForWriting<T>(Guid.Empty, default))!
: ReflectionHelper.GetMethod<IEventStore>(x => x.FetchForWriting<T>(string.Empty, default))!;
}

return isGuidIdentified
? ReflectionHelper.GetMethod<IEventStore>(x => x.FetchForWriting<T>(Guid.Empty, long.MaxValue, default))!
: ReflectionHelper.GetMethod<IEventStore>(x => x.FetchForWriting<T>(string.Empty, long.MaxValue, default))!;
}
}
34 changes: 34 additions & 0 deletions src/Http/Wolverine.Http.Marten/MemberAccessFrame.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using System.Reflection;
using JasperFx.CodeGeneration;
using JasperFx.CodeGeneration.Frames;
using JasperFx.CodeGeneration.Model;
using JasperFx.Core.Reflection;

namespace Wolverine.Http.Marten;

internal class MemberAccessFrame : SyncFrame
{
private readonly Type _targetType;
private readonly MemberInfo _member;
private Variable _parent;
public Variable Variable { get; }

public MemberAccessFrame(Type targetType, MemberInfo member, string name)
{
_targetType = targetType;
_member = member;
Variable = new Variable(member.GetMemberType(), name, this);
}

public override void GenerateCode(GeneratedMethod method, ISourceWriter writer)
{
writer.Write($"var {Variable.Usage} = {_parent.Usage}.{_member.Name};");
Next?.GenerateCode(method, writer);
}

public override IEnumerable<Variable> FindVariables(IMethodVariables chain)
{
_parent = chain.FindVariable(_targetType);
yield return _parent;
}
}
71 changes: 71 additions & 0 deletions src/Http/Wolverine.Http.Tests/Marten/document_attribute_usage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using Alba;
using Shouldly;
using WolverineWebApi.Marten;

namespace Wolverine.Http.Tests.Marten;

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

[Fact]
public async Task returns_404_on_id_miss()
{
await Scenario(x =>
{
x.Get.Url("/invoices/" + Guid.NewGuid());
x.StatusCodeShouldBe(404);
});
}

[Fact]
public async Task default_to_id_route()
{
var invoice = new Invoice();
using var session = Store.LightweightSession();
session.Store(invoice);
await session.SaveChangesAsync();


var invoice2 = await Host.GetAsJson<Invoice>("/invoices/" + invoice.Id);
invoice2.ShouldNotBeNull();
}

[Fact]
public async Task try_to_use_document_name_id_naming_convention()
{
var invoice = new Invoice();
using var session = Store.LightweightSession();
session.Store(invoice);
await session.SaveChangesAsync();

await Host.Scenario(x =>
{
x.Post.Url($"/invoices/{invoice.Id}/pay");
x.StatusCodeShouldBe(204);
});

var loaded = await session.LoadAsync<Invoice>(invoice.Id);
loaded.Paid.ShouldBeTrue();
}

[Fact]
public async Task use_explicit_path_argument()
{
var invoice = new Invoice();
using var session = Store.LightweightSession();
session.Store(invoice);
await session.SaveChangesAsync();

await Host.Scenario(x =>
{
x.Post.Url($"/invoices/{invoice.Id}/approve");
x.StatusCodeShouldBe(204);
});

var loaded = await session.LoadAsync<Invoice>(invoice.Id);
loaded.Approved.ShouldBeTrue();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// <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: GET_invoices_id
public class GET_invoices_id : Wolverine.Http.HttpHandler
{
private readonly Wolverine.Http.WolverineHttpOptions _wolverineHttpOptions;
private readonly Wolverine.Runtime.IWolverineRuntime _wolverineRuntime;
private readonly Wolverine.Marten.Publishing.OutboxedSessionFactory _outboxedSessionFactory;

public GET_invoices_id(Wolverine.Http.WolverineHttpOptions wolverineHttpOptions, Wolverine.Runtime.IWolverineRuntime wolverineRuntime, Wolverine.Marten.Publishing.OutboxedSessionFactory outboxedSessionFactory) : base(wolverineHttpOptions)
{
_wolverineHttpOptions = wolverineHttpOptions;
_wolverineRuntime = wolverineRuntime;
_outboxedSessionFactory = outboxedSessionFactory;
}



public override async System.Threading.Tasks.Task Handle(Microsoft.AspNetCore.Http.HttpContext httpContext)
{
if (!System.Guid.TryParse((string)httpContext.GetRouteValue("id"), out var id))
{
httpContext.Response.StatusCode = 404;
return;
}


var messageContext = new Wolverine.Runtime.MessageContext(_wolverineRuntime);
// Building the Marten session
await using var documentSession = _outboxedSessionFactory.OpenSession(messageContext);
var invoice = await documentSession.LoadAsync<WolverineWebApi.Marten.Invoice>(id, httpContext.RequestAborted).ConfigureAwait(false);

// The actual HTTP request handler execution
var invoice_response = WolverineWebApi.Marten.InvoicesEndpoint.Get(invoice);


// Commit any outstanding Marten changes
await documentSession.SaveChangesAsync(httpContext.RequestAborted).ConfigureAwait(false);


// Have to flush outgoing messages just in case Marten did nothing because of https://github.com/JasperFx/wolverine/issues/536
await messageContext.FlushOutgoingMessagesAsync().ConfigureAwait(false);

// Writing the response body to JSON because this was the first 'return variable' in the method signature
await WriteJsonAsync(httpContext, invoice_response);
}

}

// END: GET_invoices_id


}

Loading

0 comments on commit b18f69f

Please sign in to comment.