Skip to content

Commit 7c7c32f

Browse files
committed
Split out the Futurum.Core integration.
1 parent 0e8949f commit 7c7c32f

File tree

80 files changed

+939
-7257
lines changed

Some content is hidden

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

80 files changed

+939
-7257
lines changed

.idea/.idea.Futurum.WebApiEndpoint.Micro/.idea/aws.xml

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 1 addition & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,9 @@ public class GreetingWebApiEndpoint : IWebApiEndpoint
3232
- [x] Full support and built on top of [minimal apis](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/minimal-apis?view=aspnetcore-7.0)
3333
- [x] Full support for OpenApi
3434
- [x] Full support for [TypedResults](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.typedresults?view=aspnetcore-7.0)
35-
- [x] [Full compatibility](#full-compatibility-with-futurumcore) with [Futurum.Core](https://www.nuget.org/packages/Futurum.Core)
3635
- [x] [Supports uploading file(s) with additional JSON payload](#uploading-files-with-additional-json-payload)
3736
- [x] Api Versioning baked-in
3837
- [x] Built in [sandbox runner](#sandbox-runner) with full [TypedResults support](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.typedresults?view=aspnetcore-7.0), catching unhandled exceptions and returning a [ProblemDetails](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.problemdetails?view=aspnetcore-7.0) response
39-
- [x] [Built in Validation support](#validation)
40-
- [x] [Integrated FluentValidation](#fluentvalidationservice)
41-
- [x] [Integrated DataAnnotations](#dataannotationsvalidationservice)
4238
- [x] Autodiscovery of WebApiEndpoint(s), based on Source Generators
4339
- [x] [Roslyn Analysers](#roslyn-analysers) to help build your WebApiEndpoint(s), using best practices
4440
- [x] Built on dotnet 7
@@ -345,64 +341,6 @@ global using static Futurum.WebApiEndpoint.Micro.WebApiEndpointRunner;
345341

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

348-
## Validation
349-
### ValidationService
350-
Executes FluentValidation and DataAnnotations
351-
```csharp
352-
IValidationService<ArticleDto> validationService
353-
```
354-
355-
e.g.
356-
```csharp
357-
private static Results<Ok<ArticleDto>, ValidationProblem, BadRequest<ProblemDetails>> ValidationHandler(HttpContext context, IValidationService<ArticleDto> validationService,
358-
ArticleDto articleDto) =>
359-
validationService.Execute(articleDto)
360-
.Map(() => new Article(null, articleDto.Url))
361-
.Map(ArticleMapper.MapToDto)
362-
.ToWebApi(context, ToOk, ToValidationProblem);
363-
```
364-
365-
### FluentValidationService
366-
Calls FluentValidation
367-
```csharp
368-
IFluentValidationService<ArticleDto> fluentValidationService
369-
```
370-
371-
e.g.
372-
```csharp
373-
private static Results<Ok<ArticleDto>, ValidationProblem, BadRequest<ProblemDetails>> FluentValidationHandler(HttpContext context, IFluentValidationService<ArticleDto> fluentValidationService,
374-
ArticleDto articleDto) =>
375-
fluentValidationService.Execute(articleDto)
376-
.Map(() => new Article(null, articleDto.Url))
377-
.Map(ArticleMapper.MapToDto)
378-
.ToWebApi(context, ToOk, ToValidationProblem);
379-
380-
public class ArticleDtoValidator : AbstractValidator<ArticleDto>
381-
{
382-
public ArticleDtoValidator()
383-
{
384-
RuleFor(x => x.Url).NotEmpty().WithMessage("must have a value;");
385-
}
386-
}
387-
```
388-
389-
### DataAnnotationsValidationService
390-
Calls DataAnnotations validation
391-
```csharp
392-
IDataAnnotationsValidationService dataAnnotationsValidationService
393-
```
394-
395-
e.g.
396-
```csharp
397-
private static Results<Ok<ArticleDto>, ValidationProblem, BadRequest<ProblemDetails>> DataAnnotationsValidationHandler(HttpContext context,
398-
IDataAnnotationsValidationService dataAnnotationsValidationService,
399-
ArticleDto articleDto) =>
400-
dataAnnotationsValidationService.Execute(articleDto)
401-
.Map(() => new Article(null, articleDto.Url))
402-
.Map(ArticleMapper.MapToDto)
403-
.ToWebApi(context, ToOk, ToValidationProblem);
404-
```
405-
406344
## Uploading file(s) with additional JSON payload
407345
### Upload single file and payload
408346
Use the *FormFileWithPayload* type to upload a single file and a JSON payload
@@ -452,68 +390,6 @@ Use the *FormFilesWithPayload* type to upload multiple files and a JSON payload
452390
}
453391
```
454392

455-
## Full compatibility with [Futurum.Core](https://www.nuget.org/packages/Futurum.Core)
456-
Comprehensive set of extension methods to transform a [Result](https://docs.futurum.dev/dotnet.futurum.core/result/overview.html) and [Result&lt;T&gt;](https://docs.futurum.dev/dotnet.futurum.core/result/overview.html) to an *TypedResult*.
457-
458-
- If the method passed in is a *success*, then the *IResult* will be returned.
459-
- If the method passed in is a *failure*, 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.
460-
461-
The returned type from *ToWebApi* is always augmented to additionally include *BadRequest&lt;ProblemDetails&gt;*
462-
463-
```csharp
464-
Result<T> -> Results<T, BadRequest<ProblemDetails>>
465-
466-
Result<Results<TIResult1, TIResult2>> -> Results<TIResult1, TIResult2, BadRequest<ProblemDetails>>
467-
468-
Result<Results<TIResult1, TIResult2, TIResult3>> -> Results<TIResult1, TIResult2, TIResult3, BadRequest<ProblemDetails>>
469-
470-
Result<Results<TIResult1, TIResult2, TIResult3, TIResult4>> -> Results<TIResult1, TIResult2, TIResult3, TIResult4, BadRequest<ProblemDetails>>
471-
472-
Result<Results<TIResult1, TIResult2, TIResult3, TIResult4, TIResult5>> -> Results<TIResult1, TIResult2, TIResult3, TIResult5, BadRequest<ProblemDetails>>
473-
```
474-
475-
*Results* has a maximum of 6 types. So 5 are allowed leaving one space left for the *BadRequest&lt;ProblemDetails&gt;*.
476-
477-
#### How to handle *successful* and *failure* cases in a typed way with *TypedResult*
478-
You can optionally specify which TypedResult success cases you want to handle. This is useful if you want to handle a specific successes case differently.
479-
480-
You can specify which TypedResult error cases you want to handle. This is useful if you want to handle a specific error case differently.
481-
482-
If you have a *success* case, you must pass in the the *success* helper function first, then the *failure* helper functions.
483-
484-
There can only be 1 *success* helper function, but there can be multiple *failure* helper functions.
485-
486-
##### Example use
487-
The *ToWebApi* extension method will change the method return type to add *BadRequest&lt;ProblemDetails&gt;*, 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.
488-
489-
You can then pass in additional helper functions to deal with successes and failures and these will change the return type to the appropriate *TypedResult*'s.
490-
491-
*ToOk* is a function that will convert a *T* to an *Ok&lt;T&gt;*.
492-
493-
*ToValidationProblem* is a function that will convert a *ValidationResultError* to a *ValidationProblem*.
494-
495-
#### Full Example
496-
```csharp
497-
private static Results<Ok<ArticleDto>, ValidationProblem, BadRequest<ProblemDetails>> ValidationHandler(HttpContext context, IValidationService<ArticleDto> validationService,
498-
ArticleDto articleDto) =>
499-
validationService.Execute(articleDto)
500-
.Map(() => new Article(null, articleDto.Url))
501-
.Map(ArticleMapper.MapToDto)
502-
.ToWebApi(context, ToOk, ToValidationProblem);
503-
```
504-
505-
## Success and Failure helper functions
506-
If you have a *success* case, you must pass in the the *success* helper function first, then the *failure* helper functions.
507-
508-
There can only be 1 *success* helper function, but there can be multiple *failure* helper functions.
509-
510-
**Note:** It is recommended to add the following to your *GlobalUsings.cs* file.
511-
```csharp
512-
global using static Futurum.WebApiEndpoint.Micro.WebApiResultsExtensions;
513-
```
514-
515-
This means you can use the helper functions without having to specify the namespace. As in the examples.
516-
517393
### Success helper functions
518394
#### ToOk
519395
Converts a *T* to an *Ok&lt;T&gt;*.
@@ -562,21 +438,6 @@ This can be overridden by passing in a *string*.
562438
ToAccepted<T>("/api/articles")
563439
```
564440

565-
### Failure helper functions
566-
#### ToNotFound
567-
If a *ResultErrorKeyNotFound* has occured then it will convert it to a *NotFound&lt;ProblemDetails&gt;*, with the correct information set on the *ProblemDetails*.
568-
569-
```csharp
570-
ToNotFound
571-
```
572-
573-
#### ToValidationProblem
574-
If a *ResultErrorValidation* has occured then it will convert it to a *ValidationProblem*, with the correct information set on the *HttpValidationProblemDetails*.
575-
576-
```csharp
577-
ToValidationProblem
578-
```
579-
580441
## Comprehensive samples
581442
There are examples showing the following:
582443
- [x] A basic blog CRUD implementation
@@ -585,15 +446,13 @@ There are examples showing the following:
585446
- [x] Bytes file download
586447
- [x] EndpointFilter on a specific WebApiEndpoint
587448
- [x] Exception handling
588-
- [x] [Result](https://docs.futurum.dev/dotnet.futurum.core/result/overview.html) error handling
589449
- [x] File(s) upload
590450
- [x] File(s) upload with Payload
591451
- [x] File download
592452
- [x] OpenApi versioning
593453
- [x] Output Caching
594454
- [x] Rate Limiting
595455
- [x] Security with a basic JWT example on a specific WebApiEndpoint
596-
- [x] Validation - DataAnnotations and FluentValidation and both combined
597456
- [x] Weather Forecast
598457
- [x] Addition project containing WebApiEndpoints
599458

@@ -658,4 +517,4 @@ This uses the following attributes:
658517

659518
## Roslyn Analysers
660519
- FWAEM0001 - Non empty constructor found on WebApiEndpoint
661-
- FWAEM0002 - BadRequest without 'ProblemDetails' use found on WebApiEndpoint
520+
- FWAEM0002 - BadRequest without 'ProblemDetails' use found on WebApiEndpoint
-306 KB
Loading

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ private static Ok<DataCollectionDto<AdditionDto>> GetHandler(HttpContext context
1616
.Select(AdditionMapper.Map)
1717
.ToDataCollectionDto()
1818
.ToOk();
19-
}
19+
}

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

Lines changed: 0 additions & 11 deletions
This file was deleted.

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

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,38 @@ namespace Futurum.WebApiEndpoint.Micro.Sample.Blog;
22

33
public interface IBlogStorageBroker
44
{
5-
Task<Result<IEnumerable<Blog>>> GetAsync();
6-
Task<Result<Blog>> GetByIdAsync(Id id);
5+
Task<IEnumerable<Blog>> GetAsync();
6+
Task<Blog> GetByIdAsync(Id id);
77

8-
Task<Result<Blog>> AddAsync(Blog blog);
8+
Task<Blog> AddAsync(Blog blog);
99

10-
Task<Result<Blog>> UpdateAsync(Blog blog);
10+
Task<Blog> UpdateAsync(Blog blog);
1111

12-
Task<Result> DeleteAsync(Id id);
12+
Task DeleteAsync(Id id);
1313
}
1414

1515
public class BlogStorageBroker : IBlogStorageBroker
1616
{
1717
private readonly List<Blog> _items = new();
1818

19-
public async Task<Result<IEnumerable<Blog>>> GetAsync() =>
20-
_items.AsReadOnly().AsEnumerable().ToResultOk();
19+
public async Task<IEnumerable<Blog>> GetAsync() =>
20+
_items.AsReadOnly().AsEnumerable();
2121

22-
public async Task<Result<Blog>> GetByIdAsync(Id id) =>
23-
_items.TrySingle(x => x.Id == id)
24-
.ToResultErrorKeyNotFound(id.ToString(), typeof(Blog).FullName);
22+
public async Task<Blog> GetByIdAsync(Id id)
23+
{
24+
try
25+
{
26+
var existingBlog = _items.Single(x => x.Id == id);
2527

26-
public async Task<Result<Blog>> AddAsync(Blog blog)
28+
return existingBlog;
29+
}
30+
catch (Exception exception)
31+
{
32+
throw new KeyNotFoundException($"Unable to find {nameof(Blog)} with Id : '{id}'", exception);
33+
}
34+
}
35+
36+
public async Task<Blog> AddAsync(Blog blog)
2737
{
2838
var newBlog = blog with
2939
{
@@ -32,26 +42,37 @@ public async Task<Result<Blog>> AddAsync(Blog blog)
3242

3343
_items.Add(newBlog);
3444

35-
return Result.Ok(newBlog);
45+
return newBlog;
3646
}
3747

38-
public async Task<Result<Blog>> UpdateAsync(Blog blog)
48+
public async Task<Blog> UpdateAsync(Blog blog)
3949
{
40-
return _items.TrySingle(x => x.Id == blog.Id)
41-
.ToResultErrorKeyNotFound(blog.Id.ToString(), typeof(Blog).FullName)
42-
.Then(existingBlog =>
43-
{
44-
_items.Remove(existingBlog);
45-
_items.Add(blog);
46-
47-
return blog.ToResultOk();
48-
});
50+
try
51+
{
52+
var existingBlog = _items.Single(x => x.Id == blog.Id);
53+
54+
_items.Remove(existingBlog);
55+
_items.Add(blog);
56+
57+
return blog;
58+
}
59+
catch (Exception exception)
60+
{
61+
throw new KeyNotFoundException($"Unable to find {nameof(Blog)} with Id : '{blog.Id}'", exception);
62+
}
4963
}
5064

51-
public async Task<Result> DeleteAsync(Id id)
65+
public async Task DeleteAsync(Id id)
5266
{
53-
return _items.TrySingle(x => x.Id == id)
54-
.ToResult(() => $"Unable to find {nameof(Blog)} with Id : '{id}'")
55-
.Do(x => _items.Remove(x));
67+
try
68+
{
69+
var existingBlog = _items.Single(x => x.Id == id);
70+
71+
_items.Remove(existingBlog);
72+
}
73+
catch (Exception exception)
74+
{
75+
throw new KeyNotFoundException($"Unable to find {nameof(Blog)} with Id : '{id}'", exception);
76+
}
5677
}
57-
}
78+
}

0 commit comments

Comments
 (0)