Skip to content

Commit

Permalink
Create ErrorOr.AspNetCore NuGet
Browse files Browse the repository at this point in the history
  • Loading branch information
amantinband committed May 15, 2024
1 parent 1294ebb commit 3cf2087
Show file tree
Hide file tree
Showing 64 changed files with 566 additions and 111 deletions.
49 changes: 25 additions & 24 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,24 +1,25 @@
name: publish ErrorOr to nuget
on:
workflow_dispatch:
push:
branches:
- main # Default release branch
paths:
- "src/**"
jobs:
publish:
name: build, pack & publish
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Setup dotnet
uses: actions/setup-dotnet@v1
with:
dotnet-version: 8.0.100

# Publish
- name: Package
run: dotnet pack -c Release src/ErrorOr.csproj
- name: Publish
run: dotnet nuget push .\artifacts\*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
name: publish ErrorOr to nuget
on:
workflow_dispatch:
push:
branches:
- main # Default release branch
paths:
- "src/**"
jobs:
publish:
name: Build, Pack & Publish
runs-on: windows-latest
strategy:
matrix:
project: ['ErrorOr', 'ErrorOr.AspNetCore']
steps:
- uses: actions/checkout@v2
- name: Setup dotnet
uses: actions/setup-dotnet@v1
with:
dotnet-version: '8.0.100'
- name: Package
run: dotnet pack -c Release src/${{ matrix.project }}/${{ matrix.project }}.csproj
- name: Publish
run: dotnet nuget push .\artifacts\*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate
14 changes: 13 additions & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,26 @@
<Project>

<PropertyGroup>
<TargetFrameworks>net8.0</TargetFrameworks>
<LangVersion>12.0</LangVersion>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>True</TreatWarningsAsErrors>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

<PropertyGroup>
<Authors>Amichai Mantinband</Authors>
<PackageIcon>icon-square.png</PackageIcon>
<PackageTags>Result,Results,ErrorOr,Error,Handling</PackageTags>
<PackageProjectUrl>https://github.com/amantinband/error-or</PackageProjectUrl>
<RepositoryType>git</RepositoryType>
<licenses>https://opensource.org/licenses/MIT</licenses>
<RepositoryUrl>https://github.com/amantinband/error-or</RepositoryUrl>
<PackageOutputPath>../../artifacts/</PackageOutputPath>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
<PrivateAssets>all</PrivateAssets>
Expand Down
78 changes: 50 additions & 28 deletions ErrorOr.sln
Original file line number Diff line number Diff line change
@@ -1,28 +1,50 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ErrorOr", "src\ErrorOr.csproj", "{19633D81-11FE-4C55-AA8D-452D20EEEF98}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tests", "tests\Tests.csproj", "{0E84EC69-4E4C-4195-907D-06C96D0140A6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{19633D81-11FE-4C55-AA8D-452D20EEEF98}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{19633D81-11FE-4C55-AA8D-452D20EEEF98}.Debug|Any CPU.Build.0 = Debug|Any CPU
{19633D81-11FE-4C55-AA8D-452D20EEEF98}.Release|Any CPU.ActiveCfg = Release|Any CPU
{19633D81-11FE-4C55-AA8D-452D20EEEF98}.Release|Any CPU.Build.0 = Release|Any CPU
{0E84EC69-4E4C-4195-907D-06C96D0140A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0E84EC69-4E4C-4195-907D-06C96D0140A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0E84EC69-4E4C-4195-907D-06C96D0140A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0E84EC69-4E4C-4195-907D-06C96D0140A6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0760DBE1-5C60-46E2-AD25-69A60BEEAF54}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ErrorOr.AspNetCore", "src\ErrorOr.AspNetCore\ErrorOr.AspNetCore.csproj", "{0DCA7BE6-183F-4463-B58E-69289524F8AC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ErrorOr", "src\ErrorOr\ErrorOr.csproj", "{FD4E1A4E-1C52-4EC3-9E11-FD6EF1034FA2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{FA64735B-30ED-4C17-BBF8-741193714B07}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ErrorOr.IntegrationTests", "tests\ErrorOr.IntegrationTests\ErrorOr.IntegrationTests.csproj", "{50DC39C4-7022-4B2E-A490-AE5EFC0568FD}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "tests\ErrorOr.UnitTests\Tests.csproj", "{EF41696D-1130-4613-B213-B92E455E4AB6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0DCA7BE6-183F-4463-B58E-69289524F8AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0DCA7BE6-183F-4463-B58E-69289524F8AC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0DCA7BE6-183F-4463-B58E-69289524F8AC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0DCA7BE6-183F-4463-B58E-69289524F8AC}.Release|Any CPU.Build.0 = Release|Any CPU
{FD4E1A4E-1C52-4EC3-9E11-FD6EF1034FA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FD4E1A4E-1C52-4EC3-9E11-FD6EF1034FA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FD4E1A4E-1C52-4EC3-9E11-FD6EF1034FA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FD4E1A4E-1C52-4EC3-9E11-FD6EF1034FA2}.Release|Any CPU.Build.0 = Release|Any CPU
{50DC39C4-7022-4B2E-A490-AE5EFC0568FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{50DC39C4-7022-4B2E-A490-AE5EFC0568FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{50DC39C4-7022-4B2E-A490-AE5EFC0568FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{50DC39C4-7022-4B2E-A490-AE5EFC0568FD}.Release|Any CPU.Build.0 = Release|Any CPU
{EF41696D-1130-4613-B213-B92E455E4AB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EF41696D-1130-4613-B213-B92E455E4AB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EF41696D-1130-4613-B213-B92E455E4AB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EF41696D-1130-4613-B213-B92E455E4AB6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{0DCA7BE6-183F-4463-B58E-69289524F8AC} = {0760DBE1-5C60-46E2-AD25-69A60BEEAF54}
{FD4E1A4E-1C52-4EC3-9E11-FD6EF1034FA2} = {0760DBE1-5C60-46E2-AD25-69A60BEEAF54}
{50DC39C4-7022-4B2E-A490-AE5EFC0568FD} = {FA64735B-30ED-4C17-BBF8-741193714B07}
{EF41696D-1130-4613-B213-B92E455E4AB6} = {FA64735B-30ED-4C17-BBF8-741193714B07}
EndGlobalSection
EndGlobal
File renamed without changes.
4 changes: 4 additions & 0 deletions src/ErrorOr.AspNetCore/.editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[*.cs]

# SA1649: File name should match first type name
dotnet_diagnostic.SA1649.severity = none
19 changes: 19 additions & 0 deletions src/ErrorOr.AspNetCore/DependencyInjection/ErrorOrOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace ErrorOr;

public class ErrorOrOptions
{
public static readonly ErrorOrOptions Instance = new();

public bool IncludeMetadataInProblemDetails { get; set; } = false;
public List<Func<List<Error>, IResult?>> ErrorListToResultMapper { get; set; } = [];
public List<Func<Error, IResult?>> ErrorToResultMapper { get; set; } = [];
public List<Func<List<Error>, IActionResult?>> ErrorListToActionResultMapper { get; set; } = [];
public List<Func<Error, IActionResult?>> ErrorToActionResultMapper { get; set; } = [];
public List<Func<List<Error>, ProblemDetails?>> ErrorListToProblemDetailsMapper { get; set; } = [];
public List<Func<Error, ProblemDetails?>> ErrorToProblemDetailsMapper { get; set; } = [];
public List<Func<Error, int?>> ErrorToStatusCodeMapper { get; set; } = [];
public List<Func<Error, string?>> ErrorToTitleMapper { get; set; } = [];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using ErrorOr;

namespace Microsoft.Extensions.DependencyInjection;

public static class ServiceCollectionExtensions
{
public static IServiceCollection AddErrorOr(this IServiceCollection services, Action<ErrorOrOptions> options)
{
options.Invoke(ErrorOrOptions.Instance);

services.AddSingleton(ErrorOrOptions.Instance);

return services;
}
}
25 changes: 25 additions & 0 deletions src/ErrorOr.AspNetCore/ErrorOr.AspNetCore.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<PackageId>ErrorOr.AspNetCore</PackageId>
<Version>3.0.0-alpha1</Version>
<PackageTags>Result,Results,ErrorOr,Error,Handling,AspNetCore</PackageTags>
<Description>A simple, fluent discriminated union of an error or a result and ASP.NET specific utilities.</Description>
</PropertyGroup>

<ItemGroup>
<None Include="../../assets/icon-square.png" Pack="true" Visible="false" PackagePath="" />
<None Include="../../README.md" Pack="true" PackagePath="" />
<AdditionalFiles Include="../../Stylecop.json" />
</ItemGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" Version="2.2.8" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Abstractions" Version="2.2.0" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\ErrorOr\ErrorOr.csproj" />
</ItemGroup>

</Project>
30 changes: 30 additions & 0 deletions src/ErrorOr.AspNetCore/Extensions/ProblemDetailsExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Microsoft.AspNetCore.Mvc;

namespace ErrorOr;

public static class ProblemDetailsExtensions
{
public static ProblemDetails AddExtensions(
this ProblemDetails problemDetails,
List<Error> errors)
{
foreach (var error in errors)
{
problemDetails.AddExtensions(error);
}

return problemDetails;
}

public static ProblemDetails AddExtensions(
this ProblemDetails problemDetails,
Error error)
{
foreach (var metadata in error.Metadata)
{
problemDetails.Extensions.Add(metadata.Key, metadata.Value);
}

return problemDetails;
}
}
51 changes: 51 additions & 0 deletions src/ErrorOr.AspNetCore/Extensions/ToActionResult.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.Extensions.DependencyInjection;

namespace ErrorOr;

public static class ErrorToActionResultExtensions
{
public static IActionResult ToActionResult(this Error error, HttpContext? httpContext = null)
{
foreach (var mapping in ErrorOrOptions.Instance.ErrorToActionResultMapper)
{
if (mapping(error) is IActionResult actionResult)
{
return actionResult;
}
}

return ToActionResult([error], httpContext);
}

public static IActionResult ToActionResult(this List<Error> errors, HttpContext? httpContext = null)
{
foreach (var mapping in ErrorOrOptions.Instance.ErrorListToActionResultMapper)
{
if (mapping(errors) is IActionResult actionResult)
{
return actionResult;
}
}

var problemDetails = errors.ToProblemDetails();

if (httpContext?.RequestServices.GetService<ProblemDetailsFactory>() is ProblemDetailsFactory factory)
{
problemDetails = factory.CreateProblemDetails(
httpContext,
problemDetails.Status,
problemDetails.Title,
problemDetails.Type,
problemDetails.Detail,
problemDetails.Instance);
}

return new ObjectResult(problemDetails)
{
StatusCode = problemDetails.Status,
};
}
}
26 changes: 26 additions & 0 deletions src/ErrorOr.AspNetCore/Extensions/ToHttpStatusCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Http;

namespace ErrorOr;

public static class ErrorToHttpStatusCodeExtensions
{
public static int ToHttpStatsCode(this Error error)
{
foreach (var mapping in ErrorOrOptions.Instance.ErrorToStatusCodeMapper)
{
if (mapping(error) is int statusCode)
{
return statusCode;
}
}

return error.Type switch
{
ErrorType.Conflict => StatusCodes.Status409Conflict,
ErrorType.Validation => StatusCodes.Status400BadRequest,
ErrorType.NotFound => StatusCodes.Status404NotFound,
ErrorType.Unauthorized => StatusCodes.Status403Forbidden,
_ => StatusCodes.Status500InternalServerError,
};
}
}
55 changes: 55 additions & 0 deletions src/ErrorOr.AspNetCore/Extensions/ToProblemDetails.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace ErrorOr;

public static class ErrorToProblemDetailsExtensions
{
public static ProblemDetails ToProblemDetails(this List<Error> errors)
{
foreach (var mapping in ErrorOrOptions.Instance.ErrorListToProblemDetailsMapper)
{
if (mapping(errors) is ProblemDetails problemDetails)
{
return ErrorOrOptions.Instance.IncludeMetadataInProblemDetails
? problemDetails.AddExtensions(errors)
: problemDetails;
}
}

return errors switch
{
{ Count: 0 } => new ProblemDetails { Status = StatusCodes.Status500InternalServerError, Title = "Something went wrong" },
var _ when errors.All(error => error.Type == ErrorType.Validation) => errors.ToValidationProblemDetails(),
_ => errors[0].ToProblemDetails(),
};
}

public static ProblemDetails ToProblemDetails(this Error error)
{
foreach (var mapping in ErrorOrOptions.Instance.ErrorToProblemDetailsMapper)
{
if (mapping(error) is ProblemDetails problemDetails)
{
return problemDetails;
}
}

return new ProblemDetails { Status = error.ToHttpStatsCode(), Title = error.ToTitle() }.AddExtensions(error);
}

public static ProblemDetails ToValidationProblemDetails(this List<Error> errors)
{
var problemDetails = new HttpValidationProblemDetails
{
Errors = errors
.GroupBy(error => error.Code)
.ToDictionary(
group => group.Key,
group => group.Select(error => error.Description)
.ToArray()),
};

return problemDetails.AddExtensions(errors);
}
}
Loading

0 comments on commit 3cf2087

Please sign in to comment.