Skip to content

Commit 175d1be

Browse files
committed
Make pure source generator
1 parent 7c7c32f commit 175d1be

File tree

66 files changed

+1245
-977
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+1245
-977
lines changed

.github/workflows/release.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ jobs:
1313
uses: actions/checkout@v2
1414
with:
1515
fetch-depth: 100
16-
16+
1717
- name: Setup .NET 7
1818
uses: actions/setup-dotnet@v1
1919
with:
@@ -28,7 +28,7 @@ jobs:
2828
- name: Test
2929
run: dotnet test --no-restore --verbosity normal
3030

31-
- name: Pack Futurum.Core
31+
- name: Pack Futurum.WebApiEndpoint.Micro
3232
run: dotnet pack ./src/Futurum.WebApiEndpoint.Micro/Futurum.WebApiEndpoint.Micro.csproj --output nuget-packages --configuration Release
3333
- name: NuGet publish
3434
run: find nuget-packages -name 'Futurum.WebApiEndpoint.Micro*.nupkg' | xargs -i dotnet nuget push {} --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json
@@ -37,4 +37,4 @@ jobs:
3737
uses: actions/upload-artifact@v2
3838
with:
3939
name: nuget-packages
40-
path: nuget-packages/**/*
40+
path: nuget-packages/**/*

README.md

Lines changed: 78 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ A dotnet library that allows you to build WebApiEndpoints using a vertical slice
99

1010
```csharp
1111
[WebApiEndpoint("greeting")]
12-
public class GreetingWebApiEndpoint : IWebApiEndpoint
12+
public class GreetingWebApiEndpoint
1313
{
14-
public void Register(IEndpointRouteBuilder builder)
14+
protected override void Build(IEndpointRouteBuilder builder)
1515
{
1616
builder.MapGet("/hello", HelloHandler);
1717
builder.MapGet("/goodbye", GoodbyeHandler);
@@ -82,6 +82,9 @@ app.Run();
8282

8383
#### AddWebApiEndpoints
8484
Allows you to configure:
85+
- GlobalRoutePrefix *(optional)*
86+
- This is used if you want to specify a global route prefix for all WebApiEndpoint
87+
- e.g. "/api"
8588
- DefaultApiVersion *(mandatory)*
8689
- This is used if a specific ApiVersion is not provided for a specific WebApiEndpoint
8790
- DefaultOpenApiInfo *(optional)*
@@ -132,17 +135,17 @@ Register the OpenApi UI (Swagger and SwaggerUI) middleware
132135
app.UseWebApiEndpointsOpenApi();
133136
```
134137

135-
### IWebApiEndpoint
136-
### Register
137-
You can *map* your minimal apis for this WebApiEndpoint in the *Register* method.
138+
### WebApiEndpoint
139+
### Build
140+
You can *map* your minimal apis for this WebApiEndpoint in the *Build* method.
138141

139142
The *IEndpointRouteBuilder* parameter is already:
140143
- configured with the API versioning
141144
- configured with the route prefix
142145
- been through the *optional* *Configure* method in the same class
143146

144147
```csharp
145-
public void Register(IEndpointRouteBuilder builder)
148+
protected override void Build(IEndpointRouteBuilder builder)
146149
{
147150
}
148151
```
@@ -151,14 +154,14 @@ public void Register(IEndpointRouteBuilder builder)
151154
#### Weather
152155
```csharp
153156
[WebApiEndpoint("weather")]
154-
public class WeatherWebApiEndpoint : IWebApiEndpoint
157+
public class WeatherWebApiEndpoint
155158
{
156159
private static readonly string[] Summaries =
157160
{
158161
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
159162
};
160163

161-
public void Register(IEndpointRouteBuilder builder)
164+
protected override void Build(IEndpointRouteBuilder builder)
162165
{
163166
builder.MapGet("/", GetHandler);
164167
}
@@ -173,9 +176,9 @@ public class WeatherWebApiEndpoint : IWebApiEndpoint
173176
#### File download
174177
```csharp
175178
[WebApiEndpoint("bytes", "feature")]
176-
public class BytesWebApiEndpoint : IWebApiEndpoint
179+
public class BytesWebApiEndpoint
177180
{
178-
public void Register(IEndpointRouteBuilder builder)
181+
protected override void Build(IEndpointRouteBuilder builder)
179182
{
180183
builder.MapGet("download", DownloadHandler);
181184
}
@@ -204,7 +207,7 @@ public class BytesWebApiEndpoint : IWebApiEndpoint
204207
You can *optionally* configure the WebApiEndpoint in the *Configure* method
205208

206209
```csharp
207-
public void Configure(RouteGroupBuilder groupBuilder, WebApiEndpointVersion webApiEndpointVersion)
210+
protected override RouteGroupBuilder Configure(RouteGroupBuilder groupBuilder, WebApiEndpointVersion webApiEndpointVersion)
208211
{
209212
}
210213
```
@@ -233,16 +236,16 @@ groupBuilder.RequireAuthorization(Authorization.Permission.Admin);
233236
```
234237

235238
## Sandbox runner
236-
### Run and RunAsync - Results<...> -> Results<..., BadRequest<ProblemDetails>>
237-
Comprehensive set of extension methods - *WebApiEndpointRunner.Run* and *WebApiEndpointRunner.RunAsync* - to run a method that returns *Results<...>* in a sandbox. If an unhandled *exception* is thrown it will be caught and transformed it into a *BadRequest<ProblemDetails>*.
239+
### Run and RunAsync - If your code returns an *IResult*
240+
Comprehensive set of extension methods, to run your code in a sandbox
241+
- If your code **does not** throw an unhandled exception, then the existing return remains the same.
242+
- If your code **does** throw an unhandled exception, then a *BadRequest<ProblemDetails>* will be returned, with the appropriate details set on the ProblemDetails.
238243

239-
The *Run* and *RunAsync* methods will:
240-
- If the method passed in **does not** throw an unhandled exception, then the existing return remains the same.
241-
- If the method passed in **does** throw an unhandled exception, then a *BadRequest<ProblemDetails>* will be returned, with the appropriate details set on the ProblemDetails. The *error message* will be safe to return to the client, that is, it will not contain any sensitive information e.g. StackTrace.
242-
243-
The returned *Results<...>* type from *Run* and *RunAsync* is always augmented to additionally include *BadRequest<ProblemDetails>*
244+
The returned *Results<...>* type is always augmented to additionally include *BadRequest<ProblemDetails>*
244245

245246
```csharp
247+
TIResult1 -> Results<TIResult1, BadRequest<ProblemDetails>>
248+
246249
Results<TIResult1, TIResult2> -> Results<TIResult1, TIResult2, BadRequest<ProblemDetails>>
247250

248251
Results<TIResult1, TIResult2, TIResult3> -> Results<TIResult1, TIResult2, TIResult3, BadRequest<ProblemDetails>>
@@ -275,7 +278,7 @@ private static Results<NotFound, FileStreamHttpResult, BadRequest<ProblemDetails
275278
}
276279
```
277280

278-
In this example the *Execute* method will return:
281+
In this example the *Execute* method is being wrapped by the runner. It returns:
279282
- a *NotFound* if the file does not exist
280283
- a *FileStreamHttpResult* if the file exists
281284

@@ -296,26 +299,24 @@ global using static Futurum.WebApiEndpoint.Micro.WebApiEndpointRunner;
296299

297300
This means you can use the helper functions without having to specify the namespace. As in the examples.
298301

299-
### Run and RunAsync - (T, Func&lt;T, IResult&gt;) -> Results&lt;IResult, BadRequest&lt;ProblemDetails&gt;&gt;
300-
Extension methods - *WebApiEndpointRunner.Run* and *WebApiEndpointRunner.RunAsync* - to run a method that returns *T* in a sandbox. If an unhandled *exception* is thrown it will be caught and transformed it into a *BadRequest&lt;ProblemDetails&gt;*.
301-
302-
The *Run* and *RunAsync* methods will:
303-
- If the method passed in **does not** throw an unhandled exception, then the *Func&lt;T, IResult&gt;* will be called with the return value.
304-
- If the method passed in **does** throw an unhandled exception, then a *BadRequest&lt;ProblemDetails&gt;* will be returned, with the appropriate details set on the ProblemDetails. The *error message* will be safe to return to the client, that is, it will not contain any sensitive information e.g. StackTrace.
302+
### RunToOk and RunToOkAsync - If your code returns an *T* (not a *IResult*)
303+
Comprehensive set of extension methods, to run your code in a sandbox
304+
- If your code **does not** throw an unhandled exception, then the existing return remains the same, *but* will be wrapped in an *Ok*.
305+
- If your code **does** throw an unhandled exception, then a *BadRequest&lt;ProblemDetails&gt;* will be returned, with the appropriate details set on the ProblemDetails.
305306

306-
The returned *T* type from *Run* and *RunAsync* is always augmented to additionally include *BadRequest&lt;ProblemDetails&gt;*
307+
The returned type from *Run* and *RunAsync* is always augmented to additionally include *BadRequest&lt;ProblemDetails&gt;*
307308

308309
```csharp
309-
(T, Func<T, TResult>) -> Results<TResult, BadRequest<ProblemDetails>> where TResult : IResult
310-
```
310+
_ -> Results<Ok, BadRequest<ProblemDetails>>
311311

312-
For the *Func&lt;T, IResult&gt;*, there are a number of built-in [success helper functions](#success-helper-functions) that can be used. Or you can use your own.
312+
T -> Results<Ok<T>, BadRequest<ProblemDetails>>
313+
```
313314

314315
#### Example use
315316
```csharp
316317
private static Results<Ok<IAsyncEnumerable<Todo>>, BadRequest<ProblemDetails>> GetAllHandler(HttpContext context, SqliteConnection db)
317318
{
318-
return Run(Execute, context, ToOk, "Failed to get todos");
319+
return RunToOk(Execute, context, "Failed to get todos");
319320

320321
IAsyncEnumerable<Todo> Execute() =>
321322
db.QueryAsync<Todo>("SELECT * FROM Todos");
@@ -328,7 +329,9 @@ In this example the *Execute* method returns *IAsyncEnumerable&lt;Todo&gt;*
328329
IAsyncEnumerable<Todo>
329330
```
330331

331-
The *Run* / *RunAsync* extension method with *ToOk* [success helper function](#success-helper-functions) passed in will change this to:
332+
The *RunToOk* / *RunToOkAsync* extension method will
333+
- change the *T* to *Ok&lt;T&gt;*
334+
- add *BadRequest&lt;ProblemDetails&gt;*.
332335

333336
```csharp
334337
Results<Ok<IAsyncEnumerable<Todo>>, BadRequest<ProblemDetails>>
@@ -346,51 +349,49 @@ This means you can use the helper functions without having to specify the namesp
346349
Use the *FormFileWithPayload* type to upload a single file and a JSON payload
347350

348351
```csharp
349-
private static Task<Results<Ok<FileDetailsWithPayloadDto>, BadRequest<ProblemDetails>>> UploadWithPayloadHandler(HttpContext context, FormFileWithPayload<PayloadDto> fileWithPayload)
350-
{
351-
return Result.TryAsync(Execute, () => "Failed to read file")
352-
.ToWebApiAsync(context, ToOk);
352+
private static Task<Results<Ok<FileDetailsWithPayloadDto>, BadRequest<ProblemDetails>>> UploadWithPayloadHandler(HttpContext context, FormFileWithPayload<PayloadDto> fileWithPayload)
353+
{
354+
return RunAsync(Execute, context, ToOk, "Failed to read file");
353355

354-
async Task<FileDetailsWithPayloadDto> Execute()
355-
{
356-
var tempFile = Path.GetTempFileName();
357-
await using var stream = File.OpenWrite(tempFile);
358-
await fileWithPayload.File.CopyToAsync(stream);
356+
async Task<FileDetailsWithPayloadDto> Execute()
357+
{
358+
var tempFile = Path.GetTempFileName();
359+
await using var stream = File.OpenWrite(tempFile);
360+
await fileWithPayload.File.CopyToAsync(stream);
359361

360-
return new FileDetailsWithPayloadDto(fileWithPayload.File.FileName, fileWithPayload.Payload.Name);
361-
}
362+
return new FileDetailsWithPayloadDto(fileWithPayload.File.FileName, fileWithPayload.Payload.Name);
362363
}
364+
}
363365
```
364366

365367
### Upload multiple files and payload
366368
Use the *FormFilesWithPayload* type to upload multiple files and a JSON payload
367369

368370
```csharp
369-
private static Task<Results<Ok<IEnumerable<FileDetailsWithPayloadDto>>, BadRequest<ProblemDetails>>> UploadsWithPayloadHandler(
370-
HttpContext context, FormFilesWithPayload<PayloadDto> filesWithPayload)
371+
private static Task<Results<Ok<IEnumerable<FileDetailsWithPayloadDto>>, BadRequest<ProblemDetails>>> UploadsWithPayloadHandler(
372+
HttpContext context, FormFilesWithPayload<PayloadDto> filesWithPayload)
373+
{
374+
return RunAsync(Execute, context, ToOk, "Failed to read file");
375+
376+
async Task<IEnumerable<FileDetailsWithPayloadDto>> Execute()
371377
{
372-
return Result.TryAsync(Execute, () => "Failed to read file")
373-
.ToWebApiAsync(context, ToOk);
378+
var fileDetails = new List<FileDetailsWithPayloadDto>();
374379

375-
async Task<IEnumerable<FileDetailsWithPayloadDto>> Execute()
380+
foreach (var file in filesWithPayload.Files)
376381
{
377-
var fileDetails = new List<FileDetailsWithPayloadDto>();
378-
379-
foreach (var file in filesWithPayload.Files)
380-
{
381-
var tempFile = Path.GetTempFileName();
382-
await using var stream = File.OpenWrite(tempFile);
383-
await file.CopyToAsync(stream);
384-
385-
fileDetails.Add(new FileDetailsWithPayloadDto(file.FileName, filesWithPayload.Payload.Name));
386-
}
382+
var tempFile = Path.GetTempFileName();
383+
await using var stream = File.OpenWrite(tempFile);
384+
await file.CopyToAsync(stream);
387385

388-
return fileDetails;
386+
fileDetails.Add(new FileDetailsWithPayloadDto(file.FileName, filesWithPayload.Payload.Name));
389387
}
388+
389+
return fileDetails;
390390
}
391+
}
391392
```
392393

393-
### Success helper functions
394+
### Additional helper functions
394395
#### ToOk
395396
Converts a *T* to an *Ok&lt;T&gt;*.
396397

@@ -452,12 +453,29 @@ There are examples showing the following:
452453
- [x] OpenApi versioning
453454
- [x] Output Caching
454455
- [x] Rate Limiting
455-
- [x] Security with a basic JWT example on a specific WebApiEndpoint
456+
- [x] [Security](#security-example) with a basic JWT example on a specific WebApiEndpoint
456457
- [x] Weather Forecast
457458
- [x] Addition project containing WebApiEndpoints
458459

459460
![Futurum.WebApiEndpoint.Micro.Sample-openapi.png](https://github.com/futurum-dev/dotnet.futurum.webapiendpoint.micro/raw/main/docs/Futurum.WebApiEndpoint.Micro.Sample-openapi.png)
460461

462+
### Security example
463+
How to use in Swagger UI:
464+
1. Run the Sample project
465+
2. In the Swagger UI, go to the 'Security' 'Login' endpoint
466+
3. Set the following
467+
Username = user1
468+
Password = password1
469+
SetPermissions = true
470+
SetClaim = true
471+
SetRole = true
472+
4. Copy the value returned without double quotes.
473+
5. Go to the 'Security' 'Protected' endpoint
474+
6. Click on the padlock
475+
7. In the value textbox, enter "Bearer " (don't forget the space at the end) + the value returned from the 'Login' endpoint that you copied in step 4.
476+
8. Click "Authorize"
477+
9. Run the 'Protected' endpoint
478+
461479
## Convention Customisation
462480
Although the default conventions are good enough for most cases, you can customise them.
463481

@@ -488,33 +506,6 @@ Use this instead
488506
builder.Services.AddWebApiEndpoints();
489507
```
490508

491-
### IWebApiEndpointMetadataStrategy
492-
This is used to get the *metadata* for each *WebApiEndpoint*.
493-
494-
The *metadata* contains:
495-
- PrefixRoute
496-
- Tag
497-
- WebApiEndpointVersion
498-
499-
```csharp
500-
serviceCollection.AddWebApiEndpointMetadataStrategy<WebApiEndpointMetadataAttributeStrategy>();
501-
```
502-
503-
The default strategy is *WebApiEndpointMetadataAttributeStrategy*.
504-
505-
#### WebApiEndpointMetadataAttributeStrategy
506-
This uses the following attributes:
507-
- WebApiEndpointAttribute - for 'PrefixRoute' and 'Tag'
508-
- WebApiEndpointVersionAttribute - for 'WebApiEndpointVersion', can have multiple
509-
510-
```csharp
511-
[WebApiEndpoint("weather")]
512-
```
513-
514-
```csharp
515-
[WebApiEndpointVersion(1)]
516-
```
517-
518509
## Roslyn Analysers
519510
- FWAEM0001 - Non empty constructor found on WebApiEndpoint
520511
- FWAEM0002 - BadRequest without 'ProblemDetails' use found on WebApiEndpoint

sample/Futurum.WebApiEndpoint.Micro.Sample.Addition/AdditionWebApiEndpoint.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22

33
namespace Futurum.WebApiEndpoint.Micro.Sample.Addition;
44

5-
[WebApiEndpoint(prefixRoute: "addition")]
6-
public class AdditionWebApiEndpoint : IWebApiEndpoint
5+
[WebApiEndpoint("addition")]
6+
public partial class AdditionWebApiEndpoint
77
{
8-
public void Register(IEndpointRouteBuilder builder)
8+
protected override void Build(IEndpointRouteBuilder builder)
99
{
1010
builder.MapGet("/", GetHandler);
1111
}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
namespace Futurum.WebApiEndpoint.Micro.Sample.Analyzers;
22

33
[WebApiEndpoint(prefixRoute: "method-returning-BadRequest-without-ProblemDetails", group: "analyzer")]
4-
public class MethodReturningBadRequestWithoutProblemDetails : IWebApiEndpoint
4+
public partial class MethodReturningBadRequestWithoutProblemDetails
55
{
6-
public void Register(IEndpointRouteBuilder builder)
6+
protected override void Build(IEndpointRouteBuilder builder)
77
{
88
builder.MapGet("/", ResultErrorHandler);
99
}
10-
10+
1111
private static Results<Ok<string>, BadRequest<string>> ResultErrorHandler(HttpContext context) =>
1212
"This WebApiEndpoint has a non-empty constructor and will raise a warning.".ToOk();
13-
}
13+
}
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
namespace Futurum.WebApiEndpoint.Micro.Sample.Analyzers;
22

33
[WebApiEndpoint("non-empty-constructor", "analyzer")]
4-
public class NonEmptyConstructorOnWebApiEndpoint : IWebApiEndpoint
4+
public partial class NonEmptyConstructorOnWebApiEndpoint
55
{
66
public NonEmptyConstructorOnWebApiEndpoint(IConfiguration configuration)
77
{
88
}
9-
10-
public void Register(IEndpointRouteBuilder builder)
9+
10+
protected override void Build(IEndpointRouteBuilder builder)
1111
{
1212
builder.MapGet("/", GetHandler);
1313
}
1414

1515
private static Ok<string> GetHandler(HttpContext context) =>
1616
"This WebApiEndpoint has a non-empty constructor and will raise a warning.".ToOk();
17-
}
17+
}

sample/Futurum.WebApiEndpoint.Micro.Sample/Blog/BlogMapper.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ namespace Futurum.WebApiEndpoint.Micro.Sample.Blog;
33
public static class BlogMapper
44
{
55
public static BlogDto MapToDto(Blog domain) =>
6-
new(domain.Id.GetValueOrDefault(x => x.Value, 0), domain.Url);
7-
}
6+
new(domain.Id?.Value ?? 0, domain.Url);
7+
}

0 commit comments

Comments
 (0)