Skip to content

Commit

Permalink
Little OpenAPI generation fixes for NSwag usage. Closes GH-685
Browse files Browse the repository at this point in the history
  • Loading branch information
jeremydmiller committed Jan 16, 2024
1 parent ffd304b commit 910dc0c
Show file tree
Hide file tree
Showing 12 changed files with 304 additions and 3 deletions.
89 changes: 89 additions & 0 deletions src/Http/NSwagDemonstrator/Endpoints.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using Marten;
using Wolverine;
using Wolverine.Http;

namespace NSwagDemonstrator;




public class Todo
{
public int Id { get; set; }
public string? Name { get; set; }
public bool IsComplete { get; set; }
}

public record CreateTodo(string Name);

public record UpdateTodo(int Id, string Name, bool IsComplete);

public record DeleteTodo(int Id);

public record TodoCreated(int Id);

public static class TodoEndpoints
{
[WolverineGet("/todoitems")]
public static Task<IReadOnlyList<Todo>> Get(IQuerySession session)
=> session.Query<Todo>().ToListAsync();


[WolverineGet("/todoitems/complete")]
public static Task<IReadOnlyList<Todo>> GetComplete(IQuerySession session) =>
session.Query<Todo>().Where(x => x.IsComplete).ToListAsync();

// Wolverine can infer the 200/404 status codes for you here
// so there's no code noise just to satisfy OpenAPI tooling
[WolverineGet("/todoitems/{id}")]
public static Task<Todo?> GetTodo(int id, IQuerySession session, CancellationToken cancellation)
=> session.LoadAsync<Todo>(id, cancellation);


[WolverinePost("/todoitems")]
public static async Task<IResult> Create(CreateTodo command, IDocumentSession session, IMessageBus bus)
{
var todo = new Todo { Name = command.Name };
session.Store(todo);

// Going to raise an event within out system to be processed later
await bus.PublishAsync(new TodoCreated(todo.Id));

return Results.Created($"/todoitems/{todo.Id}", todo);
}

[WolverineDelete("/todoitems")]
public static void Delete(DeleteTodo command, IDocumentSession session)
=> session.Delete<Todo>(command.Id);
}


public static class TodoCreatedHandler
{
// Do something in the background, like assign it to someone,
// send out emails or texts, alerts, whatever
public static void Handle(TodoCreated created, ILogger logger)
{
logger.LogInformation("Got a new TodoCreated event for " + created.Id);
}
}

public static class UpdateTodoEndpoint
{
public static async Task<(Todo? todo, IResult result)> LoadAsync(UpdateTodo command, IDocumentSession session)
{
var todo = await session.LoadAsync<Todo>(command.Id);
return todo != null
? (todo, new WolverineContinue())
: (todo, Results.NotFound());
}

[WolverinePut("/todoitems")]
public static void Put(UpdateTodo command, Todo todo, IDocumentSession session)
{
todo.Name = todo.Name;
todo.IsComplete = todo.IsComplete;
session.Store(todo);
}
}

13 changes: 13 additions & 0 deletions src/Http/NSwagDemonstrator/HelloEndpoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using Wolverine.Http;

namespace NSwagDemonstrator;

#region sample_hello_world_with_wolverine_http

public class HelloEndpoint
{
[WolverineGet("/")]
public string Get() => "Hello.";
}

#endregion
32 changes: 32 additions & 0 deletions src/Http/NSwagDemonstrator/NSwagDemonstrator.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Wolverine.Http.Marten\Wolverine.Http.Marten.csproj" />
</ItemGroup>

<ItemGroup>
<Compile Include="..\..\Servers.cs">
<Link>Servers.cs</Link>
</Compile>
</ItemGroup>

<ItemGroup>
<!--
To use OpenApiGenerateDocumentsOnBuild with Wolverine, upgrade Microsoft.Extensions.ApiDescription.Server (transitive NSwag dependency).
See also: https://github.com/RicoSuter/NSwag/issues/3403
-->
<!-- <PackageReference Include="Microsoft.Extensions.ApiDescription.Server" Version="8.0.1"> -->
<!-- <PrivateAssets>all</PrivateAssets> -->
<!-- <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> -->
<!-- </PackageReference> -->
<PackageReference Include="NSwag.AspNetCore" Version="14.0.0" />
</ItemGroup>

</Project>
6 changes: 6 additions & 0 deletions src/Http/NSwagDemonstrator/NSwagDemonstrator.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
@NSwagDemonstrator_HostAddress = http://localhost:5283

GET {{NSwagDemonstrator_HostAddress}}/weatherforecast/
Accept: application/json

###
56 changes: 56 additions & 0 deletions src/Http/NSwagDemonstrator/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@

using IntegrationTests;
using Marten;
using Oakton;
using Oakton.Resources;
using Wolverine;
using Wolverine.Http;
using Wolverine.Marten;

var builder = WebApplication.CreateBuilder(args);

// Adding Marten for persistence
builder.Services.AddMarten(opts =>
{
opts.Connection(Servers.PostgresConnectionString);
opts.DatabaseSchemaName = "todo";
})
.IntegrateWithWolverine();

builder.Services.AddResourceSetupOnStartup();

// Wolverine usage is required for WolverineFx.Http
builder.Host.UseWolverine(opts =>
{
opts.Durability.Mode = DurabilityMode.Solo;
opts.Durability.DurabilityAgentEnabled = false;
// This middleware will apply to the HTTP
// endpoints as well
opts.Policies.AutoApplyTransactions();
// Setting up the outbox on all locally handled
// background tasks
opts.Policies.UseDurableLocalQueues();
});

// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument();
//builder.Services.AddSwaggerDocument();

var app = builder.Build();

app.UseOpenApi(); // serve documents (same as app.UseSwagger())
app.UseSwaggerUi();
//app.UseReDoc(); // serve ReDoc UI


// Let's add in Wolverine HTTP endpoints to the routing tree
app.MapWolverineEndpoints();

// TODO Investigate if this is a dotnet-getdocument issue
args = args.Where(arg => !arg.StartsWith("--applicationName")).ToArray();

return await app.RunOaktonCommands(args);

41 changes: 41 additions & 0 deletions src/Http/NSwagDemonstrator/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:16225",
"sslPort": 44302
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5283",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7250;http://localhost:5283",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
32 changes: 32 additions & 0 deletions src/Http/NSwagDemonstrator/TodoListEndpoint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using JasperFx.Core;
using Wolverine.Http;
using Wolverine.Marten;

namespace NSwagDemonstrator;

public record CreateTodoListRequest(string Title);

public class TodoList
{

}

public record TodoListCreated(Guid ListId, string Title);

public static class TodoListEndpoint
{
[WolverinePost("/api/todo-lists")]
public static (IResult, IStartStream) CreateTodoList(
CreateTodoListRequest request,
HttpRequest req
)
{
var listId = CombGuidIdGeneration.NewGuid();
var result = new TodoListCreated(listId, request.Title);
var startStream = MartenOps.StartStream<TodoList>(result);
var response = Results.Created("api/todo-lists/" + listId, result);

return (response, startStream);
}
}

8 changes: 8 additions & 0 deletions src/Http/NSwagDemonstrator/appsettings.Development.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
9 changes: 9 additions & 0 deletions src/Http/NSwagDemonstrator/appsettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
12 changes: 10 additions & 2 deletions src/Http/Wolverine.Http/HttpChain.ApiDescription.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
using System.Collections.Immutable;
using System.Reflection;
using JasperFx.Core.Reflection;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Metadata;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.ApiExplorer;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;
using Microsoft.AspNetCore.Routing;
Expand All @@ -14,7 +16,7 @@ namespace Wolverine.Http;
/// <summary>
/// Describes a Wolverine HTTP endpoint implementation
/// </summary>
public class WolverineActionDescriptor : ActionDescriptor
public class WolverineActionDescriptor : ControllerActionDescriptor
{
public WolverineActionDescriptor(HttpChain chain)
{
Expand All @@ -23,10 +25,16 @@ public WolverineActionDescriptor(HttpChain chain)
RouteValues["action"] = chain.Method.Method.Name;
Chain = chain;

ControllerTypeInfo = chain.Method.HandlerType.GetTypeInfo();

if (chain.Endpoint != null)
{
EndpointMetadata = chain.Endpoint!.Metadata.ToArray();
}

ActionName = chain.OperationId;

MethodInfo = chain.Method.Method;
}

public override string? DisplayName
Expand All @@ -53,7 +61,7 @@ public ApiDescription CreateApiDescription(string httpMethod)
RelativePath = Endpoint.RoutePattern.RawText?.TrimStart('/'),
ActionDescriptor = new WolverineActionDescriptor(this)
};

foreach (var routeParameter in RoutePattern.Parameters)
{
var parameter = buildParameterDescription(routeParameter);
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Wolverine.Http/WolverineApiDescriptionProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public void OnProvidersExecuting(ApiDescriptionProviderContext context)
{
continue;
}

foreach (var httpMethod in chain.HttpMethods)
{
context.Results.Add(chain.CreateApiDescription(httpMethod));
Expand Down
7 changes: 7 additions & 0 deletions wolverine.sln
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wolverine.Http.Marten", "sr
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenApiDemonstrator", "src\Http\OpenApiDemonstrator\OpenApiDemonstrator.csproj", "{4FA38CED-74C9-4969-83B2-6BD54F245E6C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NSwagDemonstrator", "src\Http\NSwagDemonstrator\NSwagDemonstrator.csproj", "{DC18FDD3-2AD9-43C9-B8CC-850457FE1A07}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -593,6 +595,10 @@ Global
{4FA38CED-74C9-4969-83B2-6BD54F245E6C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4FA38CED-74C9-4969-83B2-6BD54F245E6C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4FA38CED-74C9-4969-83B2-6BD54F245E6C}.Release|Any CPU.Build.0 = Release|Any CPU
{DC18FDD3-2AD9-43C9-B8CC-850457FE1A07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DC18FDD3-2AD9-43C9-B8CC-850457FE1A07}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DC18FDD3-2AD9-43C9-B8CC-850457FE1A07}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DC18FDD3-2AD9-43C9-B8CC-850457FE1A07}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{24497E6A-D6B1-4C80-ABFB-57FFAD5070C4} = {96119B5E-B5F0-400A-9580-B342EBE26212}
Expand Down Expand Up @@ -698,5 +704,6 @@ Global
{6F6FB8FC-564C-4B04-B254-EB53A7E4562F} = {D953D733-D154-4DF2-B2B9-30BF942E1B6B}
{A484AD9E-04C7-4CF9-BB59-5C7DE772851C} = {4B0BC1E5-17F9-4DD0-AC93-DDC522E1BE3C}
{4FA38CED-74C9-4969-83B2-6BD54F245E6C} = {4B0BC1E5-17F9-4DD0-AC93-DDC522E1BE3C}
{DC18FDD3-2AD9-43C9-B8CC-850457FE1A07} = {4B0BC1E5-17F9-4DD0-AC93-DDC522E1BE3C}
EndGlobalSection
EndGlobal

0 comments on commit 910dc0c

Please sign in to comment.