Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

<ItemGroup>
<ProjectReference Include="..\CR.Exceptions.AspNet\CR.Exceptions.AspNet.csproj" />
<ProjectReference Include="..\CR.Exceptions.Tests.Shared\CR.Exceptions.Tests.Shared.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
13 changes: 7 additions & 6 deletions CR.Exceptions.AspNet.Tests/Component/CrExceptionHandlerTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Diagnostics;
using CR.Exceptions.Tests.Shared;
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.DependencyInjection;
Expand All @@ -19,11 +20,11 @@ public CrExceptionHandlerTests(ITestOutputHelper output)
}

[Fact]
public Task Should_Return_404_For_NotFoundException()
public Task Should_Return_500_For_InternalException()
{
return AssertHandlerResult(
new TestNotFoundException(),
StatusCodes.Status404NotFound,
new TestInternalException(),
StatusCodes.Status500InternalServerError,
canCreateActivity: true);
}

Expand All @@ -37,7 +38,7 @@ public Task Should_Return_500_For_UnhandledException()
}

[Fact]
public Task Should_Return_500_For_UnhandledException_When_Activity_Is_Missing()
public Task Should_Return_500_For_UnhandledException_When_ActivityIsMissing()
{
return AssertHandlerResult(
new InvalidOperationException(),
Expand Down Expand Up @@ -98,7 +99,7 @@ private static ServiceProvider CreateServiceProvider()
{
return new ServiceCollection()
.AddLogging()
.AddCrExceptions()
.AddCrExceptionsCore()
.BuildServiceProvider();
}

Expand Down
32 changes: 18 additions & 14 deletions CR.Exceptions.AspNet.Tests/Component/LogLevelMapTests.cs
Original file line number Diff line number Diff line change
@@ -1,35 +1,39 @@
using CR.Exceptions.AspNet.Mapping;
using CR.Exceptions.Tests.Shared;
using Microsoft.Extensions.Logging;

namespace CR.Exceptions.AspNet.Tests.Component;

public sealed class LogLevelMapTests
{
private const LogLevel ExpectedLogLevel = LogLevel.Warning;
private static readonly TestInternalException ExistentException = new();
private static readonly TestUnknownException NonExistentException = new();

[Fact]
public void TryFind_ShouldReturn_Level_For_NotFoundException()
public void TryFind_ShouldReturn_TrueAndLevel_WhenExceptionExists()
{
var level = LogLevel.Warning;
var map = CreateMap(builder => builder.Map<NotFoundException>(level));

var result = map.TryFind(new TestNotFoundException(), out var actualLevel);
var map = GetDefaultMap();
var result = map.TryFind(ExistentException, out var actualLevel);

Assert.True(result);
Assert.Equal(level, actualLevel);
Assert.Equal(ExpectedLogLevel, actualLevel);
}

[Fact]
public void TryFind_ShouldReturn_False_For_UnregisteredException()
public void TryFind_ShouldReturn_FalseAndDefault_WhenExceptionDoesNotExist()
{
var map = CreateMap();
var map = GetDefaultMap();
var result = map.TryFind(NonExistentException, out var level);

Assert.False(map.TryFind(new TestUnregisteredException(), out var _));
Assert.False(result);
Assert.Equal(default, level);
}

private static LogLevelMap CreateMap(Action<LogLevelMapBuilder>? configurator = null)
private static LogLevelMap GetDefaultMap()
{
var builder = new LogLevelMapBuilder();
configurator?.Invoke(builder);

return builder.Build();
return new LogLevelMapBuilder()
.Map<TestInternalException>(ExpectedLogLevel)
.Build();
}
}
32 changes: 18 additions & 14 deletions CR.Exceptions.AspNet.Tests/Component/StatusCodeMapTests.cs
Original file line number Diff line number Diff line change
@@ -1,35 +1,39 @@
using CR.Exceptions.AspNet.Mapping;
using CR.Exceptions.Tests.Shared;
using Microsoft.AspNetCore.Http;

namespace CR.Exceptions.AspNet.Tests.Component;

public sealed class StatusCodeMapTests
{
private const int ExpectedStatusCode = StatusCodes.Status500InternalServerError;
private static readonly TestInternalException ExistentException = new();
private static readonly TestUnknownException NonExistentException = new();

[Fact]
public void TryFind_ShouldReturn_404_For_NotFoundException()
public void TryFind_ShouldReturn_TrueAndCode_WhenExceptionExists()
{
var code = StatusCodes.Status404NotFound;
var map = CreateMap(builder => builder.Map<NotFoundException>(code));

var result = map.TryFind(new TestNotFoundException(), out var actualCode);
var map = GetDefaultMap();
var result = map.TryFind(ExistentException, out var actualCode);

Assert.True(result);
Assert.Equal(code, actualCode);
Assert.Equal(ExpectedStatusCode, actualCode);
}

[Fact]
public void TryFind_ShouldReturn_False_For_UnregisteredException()
public void TryFind_ShouldReturn_FalseAndDefault_WhenExceptionDoesNotExist()
{
var map = CreateMap();
var map = GetDefaultMap();
var result = map.TryFind(NonExistentException, out var code);

Assert.False(map.TryFind(new TestUnregisteredException(), out var _));
Assert.False(result);
Assert.Equal(default, code);
}

private static StatusCodeMap CreateMap(Action<StatusCodeMapBuilder>? configurator = null)
private static StatusCodeMap GetDefaultMap()
{
var builder = new StatusCodeMapBuilder();
configurator?.Invoke(builder);

return builder.Build();
return new StatusCodeMapBuilder()
.Map<TestInternalException>(ExpectedStatusCode)
.Build();
}
}
8 changes: 0 additions & 8 deletions CR.Exceptions.AspNet.Tests/TestNotFoundException.cs

This file was deleted.

8 changes: 0 additions & 8 deletions CR.Exceptions.AspNet.Tests/TestUnregisteredException.cs

This file was deleted.

1 change: 1 addition & 0 deletions CR.Exceptions.AspNet/CR.Exceptions.AspNet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>true</IsPackable>
</PropertyGroup>

<PropertyGroup>
Expand Down
4 changes: 2 additions & 2 deletions CR.Exceptions.AspNet/CrExceptionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ namespace CR.Exceptions.AspNet;

public sealed class CrExceptionHandler : IExceptionHandler
{
private static readonly ImmutableArray<CrError> DefaultInternalErrors =
[new("InternalError", "An unexpected internal error occurred.")];
private static readonly ImmutableArray<CrError> DefaultInternalErrors
= [new("InternalError", "An unexpected internal error occurred.")];

private readonly IProblemDetailsService _problemDetailsService;
private readonly ILogger<CrExceptionHandler> _logger;
Expand Down
2 changes: 1 addition & 1 deletion CR.Exceptions.AspNet/CrExceptionHandlerLogExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ private static class LogIds

[LoggerMessage(
EventId = LogIds.MissingLogLevelMapping,
Level = LogLevel.Debug,
Level = LogLevel.Warning,
Message = "No log level mapping found for exception type '{ExceptionType}'. Using fallback log level '{FallbackLogLevel}'.")]
public static partial void LogMissingLogLevelMapping(this ILogger logger, string? exceptionType, LogLevel fallbackLogLevel);
}
13 changes: 8 additions & 5 deletions CR.Exceptions.AspNet/Mapping/LogLevelMap.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
using Microsoft.Extensions.Logging;
using CR.Exceptions.Mapping;
using Microsoft.Extensions.Logging;
using System.Collections.Frozen;
using System.Diagnostics.CodeAnalysis;

namespace CR.Exceptions.AspNet.Mapping;

public sealed class LogLevelMap : TypeMap<LogLevel>
public class LogLevelMap : TypeMap<LogLevel>
{
internal LogLevelMap(FrozenDictionary<Type, LogLevel> dictionary) : base(dictionary)
{
}
internal LogLevelMap(FrozenDictionary<Type, LogLevel> dictionary) : base(dictionary) { }

public bool TryFind(CrException exception, [MaybeNullWhen(false)] out LogLevel level)
=> TryGetByHierarchy(exception.GetType(), out level);
}
25 changes: 16 additions & 9 deletions CR.Exceptions.AspNet/Mapping/LogLevelMapBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
using Microsoft.Extensions.Logging;
using CR.Exceptions.Mapping;
using Microsoft.Extensions.Logging;

namespace CR.Exceptions.AspNet.Mapping;

public sealed class LogLevelMapBuilder : TypeMapBuilder<LogLevel>
public class LogLevelMapBuilder : MapBuilder<Type, LogLevel>
{
public LogLevelMap Build()
public LogLevelMapBuilder Map<TException>(LogLevel level) where TException : CrException
{
return new(BuildFrozenDictionary());
ThrowIfInvalidLevel(level);
AddPair(typeof(TException), level);

return this;
}

protected override void ThrowIfInvalidValue(LogLevel value)
public LogLevelMap Build()
=> new(BuildFrozenDictionary());

private static void ThrowIfInvalidLevel(LogLevel level)
{
if (!Enum.IsDefined(value))
if (!Enum.IsDefined(level))
{
throw new ArgumentOutOfRangeException(
nameof(value), value, $"The value '{value}' is not a valid {nameof(LogLevel)}.");
nameof(level), level, $"The value '{level}' is not a valid {nameof(LogLevel)}.");
}

if (value == LogLevel.None)
if (level is LogLevel.None)
{
throw new ArgumentException(
$"{nameof(LogLevel)}.{nameof(LogLevel.None)} cannot be used for exception mapping.", nameof(value));
$"{nameof(LogLevel)}.{nameof(LogLevel.None)} cannot be used for exception mapping.", nameof(level));
}
}
}
10 changes: 7 additions & 3 deletions CR.Exceptions.AspNet/Mapping/LogLevelMapBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@ public static class LogLevelMapBuilderExtensions
{
public LogLevelMapBuilder AddDefaultMappings()
{
builder
return builder
.Map<ValidationException>(LogLevel.Debug)
.Map<UnauthorizedException>(LogLevel.Debug)
.Map<ForbiddenException>(LogLevel.Debug)
.Map<NotFoundException>(LogLevel.Debug)
.Map<ConflictException>(LogLevel.Debug)
.Map<UnprocessableException>(LogLevel.Debug)
.Map<InternalException>(LogLevel.Error);

return builder;
}
}
}
13 changes: 8 additions & 5 deletions CR.Exceptions.AspNet/Mapping/StatusCodeMap.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
using System.Collections.Frozen;
using CR.Exceptions.Mapping;
using System.Collections.Frozen;
using System.Diagnostics.CodeAnalysis;

namespace CR.Exceptions.AspNet.Mapping;

public sealed class StatusCodeMap : TypeMap<int>
public class StatusCodeMap : TypeMap<int>
{
internal StatusCodeMap(FrozenDictionary<Type, int> dictionary) : base(dictionary)
{
}
internal StatusCodeMap(FrozenDictionary<Type, int> dictionary) : base(dictionary) { }

public bool TryFind(CrException exception, [MaybeNullWhen(false)] out int code)
=> TryGetByHierarchy(exception.GetType(), out code);
}
21 changes: 14 additions & 7 deletions CR.Exceptions.AspNet/Mapping/StatusCodeMapBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
using System.Net;
using CR.Exceptions.Mapping;
using System.Net;

namespace CR.Exceptions.AspNet.Mapping;

public sealed class StatusCodeMapBuilder : TypeMapBuilder<int>
public class StatusCodeMapBuilder : MapBuilder<Type, int>
{
public StatusCodeMap Build()
public StatusCodeMapBuilder Map<TException>(int code) where TException : CrException
{
return new(BuildFrozenDictionary());
ThrowIfInvalidCode(code);
AddPair(typeof(TException), code);

return this;
}

protected override void ThrowIfInvalidValue(int value)
public StatusCodeMap Build()
=> new(BuildFrozenDictionary());

private static void ThrowIfInvalidCode(int code)
{
if (!Enum.IsDefined(typeof(HttpStatusCode), value))
if (!Enum.IsDefined(typeof(HttpStatusCode), code))
{
throw new ArgumentOutOfRangeException(
nameof(value), $"'{value}' is not a standard HTTP status code.");
nameof(code), $"'{code}' is not a standard HTTP status code.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,14 @@ public static class StatusCodeMapBuilderExtensions
{
public StatusCodeMapBuilder AddDefaultMappings()
{
builder
return builder
.Map<ValidationException>(StatusCodes.Status400BadRequest)
.Map<UnauthorizedException>(StatusCodes.Status401Unauthorized)
.Map<ForbiddenException>(StatusCodes.Status403Forbidden)
.Map<NotFoundException>(StatusCodes.Status404NotFound)
.Map<ConflictException>(StatusCodes.Status409Conflict)
.Map<UnprocessableException>(StatusCodes.Status422UnprocessableEntity)
.Map<InternalException>(StatusCodes.Status500InternalServerError);

return builder;
}
}
}
16 changes: 0 additions & 16 deletions CR.Exceptions.AspNet/Mapping/TypeMapBuilder.cs

This file was deleted.

Loading
Loading