Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: Handling errors with Result pattern and ErrorOr package #12

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 30 additions & 21 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,23 +1,32 @@
{
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#3399ff",
"activityBar.activeBorder": "#bf0060",
"activityBar.background": "#3399ff",
"activityBar.foreground": "#15202b",
"activityBar.inactiveForeground": "#15202b99",
"activityBarBadge.background": "#bf0060",
"activityBarBadge.foreground": "#e7e7e7",
"sash.hoverBorder": "#3399ff",
"statusBar.background": "#007fff",
"statusBar.foreground": "#e7e7e7",
"statusBarItem.hoverBackground": "#3399ff",
"statusBarItem.remoteBackground": "#007fff",
"statusBarItem.remoteForeground": "#e7e7e7",
"titleBar.activeBackground": "#007fff",
"titleBar.activeForeground": "#e7e7e7",
"titleBar.inactiveBackground": "#007fff99",
"titleBar.inactiveForeground": "#e7e7e799",
"commandCenter.border": "#e7e7e799"
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#65c89b",
"activityBar.activeBorder": "#bf0060",
"activityBar.background": "#65c89b",
"activityBar.foreground": "#15202b",
"activityBar.inactiveForeground": "#15202b99",
"activityBarBadge.background": "#945bc4",
"activityBarBadge.foreground": "#e7e7e7",
"sash.hoverBorder": "#65c89b",
"statusBar.background": "#42b883",
"statusBar.foreground": "#15202b",
"statusBarItem.hoverBackground": "#359268",
"statusBarItem.remoteBackground": "#42b883",
"statusBarItem.remoteForeground": "#15202b",
"titleBar.activeBackground": "#42b883",
"titleBar.activeForeground": "#15202b",
"titleBar.inactiveBackground": "#42b88399",
"titleBar.inactiveForeground": "#15202b99",
"commandCenter.border": "#15202b99"
},
"peacock.color": "#42b883",

"rest-client.environmentVariables": {
"$shared": {
"listId": "0"
},
"peacock.color": "#007fff"
}
"dev": {
"host": "https://localhost:7098"
}
}
}
3 changes: 2 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<PackageVersion Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.1" />
<!-- Open source packages -->
<PackageVersion Include="CsvHelper" Version="15.0.10" />
<PackageVersion Include="ErrorOr" Version="1.6.0" />
<PackageVersion Include="ErrorOr" Version="1.10.0" />
<PackageVersion Include="FluentValidation" Version="11.9.0" />
<PackageVersion Include="FluentValidation.AspNetCore" Version="11.3.0" />
<PackageVersion Include="MediatR" Version="12.2.0" />
Expand All @@ -28,5 +28,6 @@
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.6" />
<PackageVersion Include="coverlet.collector" Version="6.0.0" />
<PackageVersion Include="Moq" Version="4.16.1" />
<PackageVersion Include="NSubstitute" Version="5.1.0" />
</ItemGroup>
</Project>
9 changes: 9 additions & 0 deletions requests/TodoItems/DeleteTodoItem.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@host = https://localhost:7098


### Delete Todo itemId = 10 with wrong itemId. Check for 404 Not Found
DELETE {{host}}/api/todo-items/10


### Delete Todo itemId = 1, should return 204 No Content
DELETE {{host}}/api/todo-items/1
14 changes: 14 additions & 0 deletions requests/TodoItems/UpdateTodoItemDetails.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
@host = https://localhost:7098
@itemId = 4
@listId = 1

### Update Todo itemId = 1 with PriorityLevel = 1 and Note = "Updated Todo Item 1"
PUT {{host}}/api/todo-items/UpdateItemDetails?id={{itemId}}
Content-Type: application/json

{
"id": {{itemId}},
"listId": {{listId}},
"priorityLevel": 1,
"note": "Updated Todo Item 1"
}
16 changes: 16 additions & 0 deletions requests/TodoLists/CreateTodoList.http
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@
POST {{host}}/api/todo-lists
Content-Type: application/json

{
"title": "List 1"
}

### Create Todo List item, validate that it handles empty title
POST {{host}}/api/todo-lists
Content-Type: application/json

{
"title": ""
}

### Create Todo List item, validate that it duplicate title
POST {{host}}/api/todo-lists
Content-Type: application/json

{
"title": "List 2"
}
9 changes: 9 additions & 0 deletions requests/TodoLists/DeleteTodoList.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@host = https://localhost:7098


### Delete Todo itemId = 10 with wrong itemId. Check for 404 Not Found
DELETE {{host}}/api/todo-lists/10


### Delete Todo itemId = 1, should return 204 No Content
DELETE {{host}}/api/todo-lists/1
5 changes: 5 additions & 0 deletions requests/TodoLists/ExportTodoLists.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
@host = https://localhost:7098
@todoListId = 2

### Export Todo lists
GET {{host}}/api/todo-lists/{{todoListId}}
40 changes: 40 additions & 0 deletions requests/TodoLists/UpdateTodoItem.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
@host = https://localhost:7098
@todoListId = 1

### Update TodoListId = 1 with title = "Updated TodoList 1"
PUT {{host}}/api/todo-lists/{{todoListId}}
Content-Type: application/json

{
"id": {{todoListId}},
"title": "Updated Todo Item 1"
}

### Update TodoListId = 1 with wrong Id
PUT {{host}}/api/todo-items/0
Content-Type: application/json

{
"id": {{todoListId}},
"title": "Updated Todo Item 1"
}

### Update TodoListId = 1 with empty title
PUT {{host}}/api/todo-lists/{{todoListId}}
Content-Type: application/json

{
"id": {{todoListId}},
"title": ""
}

### Update TodoListId = 1 with title more than 200 characters
PUT {{host}}/api/todo-lists/{{todoListId}}
Content-Type: application/json

{
"id": {{todoListId}},
"title": "Lorem ipsum dolor sit amet, consectetur adipiscing elit Lorem ipsum dolor sit amet, consectetur adipiscing elit Lorem ipsum dolor sit amet, consectetur adipiscing elit Lorem ipsum dolor sit amet, consectetur adipiscing elit Lorem ipsum dolor sit amet, consectetur adipiscing elit"
}


132 changes: 0 additions & 132 deletions src/Api/Filters/ApiExceptionFilterAttribute.cs

This file was deleted.

42 changes: 42 additions & 0 deletions src/Application/Common/ApiControllerBase.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
using ErrorOr;

using MediatR;

using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.Extensions.DependencyInjection;

namespace VerticalSliceArchitecture.Application.Common;
Expand All @@ -12,4 +16,42 @@ public abstract class ApiControllerBase : ControllerBase
private ISender? _mediator;

protected ISender Mediator => _mediator ??= HttpContext.RequestServices.GetService<ISender>()!;

protected ActionResult Problem(List<Error> errors)
{
if (errors.Count is 0)
{
return Problem();
}

if (errors.All(error => error.Type == ErrorType.Validation))
{
return ValidationProblem(errors);
}

return Problem(errors[0]);
}

private ObjectResult Problem(Error error)
{
var statusCode = error.Type switch
{
ErrorType.Conflict => StatusCodes.Status409Conflict,
ErrorType.Validation => StatusCodes.Status400BadRequest,
ErrorType.NotFound => StatusCodes.Status404NotFound,
ErrorType.Unauthorized => StatusCodes.Status403Forbidden,
_ => StatusCodes.Status500InternalServerError,
};

return Problem(statusCode: statusCode, title: error.Description);
}

private ActionResult ValidationProblem(List<Error> errors)
{
var modelStateDictionary = new ModelStateDictionary();

errors.ForEach(error => modelStateDictionary.AddModelError(error.Code, error.Description));

return ValidationProblem(modelStateDictionary);
}
}
Loading
Loading