Skip to content

Commit

Permalink
refactor: ♻️ refactor problem details exception handler (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
mehdihadeli authored Sep 9, 2024
1 parent 848e805 commit 0cf21fa
Show file tree
Hide file tree
Showing 34 changed files with 667 additions and 1,348 deletions.
1,726 changes: 533 additions & 1,193 deletions package-lock.json

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
"name": "Vertical.Slice.Template",
"version": "1.0.0",
"scripts": {
"prepare": "husky install && dotnet tool restore",
"install-dev-cert-bash": "curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v vs2019 -l ~/vsdbg"
"prepare": "husky && dotnet tool restore",
"install-dev-cert-bash": "curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v vs2019 -l ~/vsdbg",
"upgrade-packages": "dotnet outdated Vertical.Slice.Template.sln"
},
"repository": {
"type": "git",
Expand All @@ -16,8 +17,8 @@
},
"homepage": "https://github.com/mehdihadeli/vertical-slice-api-template#readme",
"devDependencies": {
"@commitlint/cli": "^17.4.2",
"@commitlint/config-conventional": "^17.4.2",
"husky": "^8.0.3"
"@commitlint/cli": "^19.4.1",
"@commitlint/config-conventional": "^19.4.1",
"husky": "^9.1.5"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ namespace Vertical.Slice.Template.Shared.Data;

public class CatalogsDbContextDesignFactory()
: DbContextDesignFactoryBase<CatalogsDbContext>(
$"{nameof(PostgresOptions)}:{nameof(PostgresOptions.ConnectionString)}");
$"{nameof(PostgresOptions)}:{nameof(PostgresOptions.ConnectionString)}"
);
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ public partial class InitialCatalogMigration : Migration
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.EnsureSchema(
name: "catalog");
migrationBuilder.EnsureSchema(name: "catalog");

migrationBuilder.CreateTable(
name: "products",
Expand All @@ -23,27 +22,27 @@ protected override void Up(MigrationBuilder migrationBuilder)
price = table.Column<decimal>(type: "numeric(18,2)", nullable: false),
category_id = table.Column<Guid>(type: "uuid", nullable: false),
name = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
description = table.Column<string>(type: "character varying(250)", maxLength: 250, nullable: true)
description = table.Column<string>(type: "character varying(250)", maxLength: 250, nullable: true),
},
constraints: table =>
{
table.PrimaryKey("pk_products", x => x.id);
});
}
);

migrationBuilder.CreateIndex(
name: "ix_products_id",
schema: "catalog",
table: "products",
column: "id",
unique: true);
unique: true
);
}

/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "products",
schema: "catalog");
migrationBuilder.DropTable(name: "products", schema: "catalog");
}
}
}
12 changes: 6 additions & 6 deletions src/Shared/Cache/Behaviours/CachingBehavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ CancellationToken cancellationToken
var expiredTimeSpan =
cacheRequest.AbsoluteExpirationRelativeToNow != TimeSpan.FromMinutes(5)
? cacheRequest.AbsoluteExpirationRelativeToNow
: TimeSpan.FromMinutes(_cacheOptions.ExpirationTimeInMinute) != TimeSpan.FromMinutes(5)
? TimeSpan.FromMinutes(_cacheOptions.ExpirationTimeInMinute)
: cacheRequest.AbsoluteExpirationRelativeToNow;
: TimeSpan.FromMinutes(_cacheOptions.ExpirationTimeInMinute) != TimeSpan.FromMinutes(5)
? TimeSpan.FromMinutes(_cacheOptions.ExpirationTimeInMinute)
: cacheRequest.AbsoluteExpirationRelativeToNow;

await _cacheProvider.SetAsync(cacheKey, response, expiredTimeSpan, cancellationToken);

Expand Down Expand Up @@ -126,9 +126,9 @@ CancellationToken cancellationToken
var expiredTimeSpan =
cacheRequest.AbsoluteExpirationRelativeToNow != TimeSpan.FromMinutes(5)
? cacheRequest.AbsoluteExpirationRelativeToNow
: TimeSpan.FromMinutes(_cacheOptions.ExpirationTimeInMinute) != TimeSpan.FromMinutes(5)
? TimeSpan.FromMinutes(_cacheOptions.ExpirationTimeInMinute)
: cacheRequest.AbsoluteExpirationRelativeToNow;
: TimeSpan.FromMinutes(_cacheOptions.ExpirationTimeInMinute) != TimeSpan.FromMinutes(5)
? TimeSpan.FromMinutes(_cacheOptions.ExpirationTimeInMinute)
: cacheRequest.AbsoluteExpirationRelativeToNow;

await foreach (var response in next().WithCancellation(cancellationToken))
{
Expand Down
4 changes: 2 additions & 2 deletions src/Shared/Cache/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static WebApplicationBuilder AddCustomEasyCaching(this WebApplicationBuil
{
config.DBConfig = new RedisDBOptions
{
Configuration = cacheOptions.RedisCacheOptions.ConnectionString
Configuration = cacheOptions.RedisCacheOptions.ConnectionString,
};
config.SerializerName = cacheOptions.SerializationType;
},
Expand Down Expand Up @@ -71,7 +71,7 @@ public static WebApplicationBuilder AddCustomRedis(this WebApplicationBuilder bu
new ConfigurationOptions
{
EndPoints = { $"{redisOptions.Host}:{redisOptions.Port}" },
AllowAdmin = true
AllowAdmin = true,
}
);
});
Expand Down
14 changes: 5 additions & 9 deletions src/Shared/Core/Exceptions/AppException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,8 @@

namespace Shared.Core.Exceptions;

public class AppException : CustomException
{
public AppException(
string message,
int statusCode = StatusCodes.Status400BadRequest,
Exception? innerException = null
)
: base(message, statusCode, innerException) { }
}
public class AppException(
string message,
int statusCode = StatusCodes.Status400BadRequest,
System.Exception? innerException = null
) : CustomException(message, statusCode, innerException);
7 changes: 2 additions & 5 deletions src/Shared/Core/Exceptions/BadRequestException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,5 @@

namespace Shared.Core.Exceptions;

public class BadRequestException : CustomException
{
public BadRequestException(string message, Exception? innerException = null)
: base(message, StatusCodes.Status400BadRequest, innerException) { }
}
public class BadRequestException(string message, System.Exception? innerException = null, params string[] errors)
: CustomException(message, StatusCodes.Status400BadRequest, innerException, errors);
13 changes: 3 additions & 10 deletions src/Shared/Core/Exceptions/BusinessRuleValidationException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,10 @@

namespace Shared.Core.Exceptions;

public class BusinessRuleValidationException : DomainException
public class BusinessRuleValidationException(IBusinessRule brokenRule) : DomainException(brokenRule.Message)
{
public IBusinessRule BrokenRule { get; }
public string Details { get; }

public BusinessRuleValidationException(IBusinessRule brokenRule)
: base(brokenRule.Message)
{
BrokenRule = brokenRule;
Details = brokenRule.Message;
}
public IBusinessRule BrokenRule { get; } = brokenRule;
public string Details { get; } = brokenRule.Message;

public override string ToString()
{
Expand Down
7 changes: 2 additions & 5 deletions src/Shared/Core/Exceptions/ConflictException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,5 @@

namespace Shared.Core.Exceptions;

public class ConflictException : CustomException
{
public ConflictException(string message, Exception? innerException = null)
: base(message, StatusCodes.Status409Conflict, innerException) { }
}
public class ConflictException(string message, Exception? innerException = null)
: CustomException(message, StatusCodes.Status409Conflict, innerException);
24 changes: 9 additions & 15 deletions src/Shared/Core/Exceptions/CustomException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,15 @@

namespace Shared.Core.Exceptions;

public class CustomException : Exception
public class CustomException(
string message,
int statusCode = StatusCodes.Status500InternalServerError,
Exception? innerException = null,
params string[] errors
)
: Exception(message, innerException)
{
public CustomException(
string message,
int statusCode = StatusCodes.Status500InternalServerError,
Exception? innerException = null,
params string[] errors
)
: base(message, innerException)
{
ErrorMessages = errors;
StatusCode = statusCode;
}
public IEnumerable<string> ErrorMessages { get; protected set; } = errors;

public IEnumerable<string> ErrorMessages { get; protected set; }

public int StatusCode { get; protected set; }
public int StatusCode { get; protected set; } = statusCode;
}
15 changes: 6 additions & 9 deletions src/Shared/Core/Exceptions/DomainException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,9 @@ namespace Shared.Core.Exceptions;
/// <summary>
/// Exception type for domain exceptions.
/// </summary>
public class DomainException : CustomException
{
public DomainException(
string message,
int statusCode = StatusCodes.Status400BadRequest,
Exception? innerException = null
)
: base(message, statusCode, innerException) { }
}
public class DomainException(
string message,
int statusCode = StatusCodes.Status400BadRequest,
Exception? innerException = null
)
: CustomException(message, statusCode, innerException);
7 changes: 2 additions & 5 deletions src/Shared/Core/Exceptions/NotFoundException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,5 @@

namespace Shared.Core.Exceptions;

public class NotFoundException : CustomException
{
public NotFoundException(string message, Exception? innerException = null)
: base(message, StatusCodes.Status404NotFound, innerException) { }
}
public class NotFoundException(string message, Exception? innerException = null)
: CustomException(message, StatusCodes.Status404NotFound, innerException);
9 changes: 2 additions & 7 deletions src/Shared/Core/Exceptions/ValidationException.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,4 @@
using Microsoft.AspNetCore.Http;

namespace Shared.Core.Exceptions;

public class ValidationException : CustomException
{
public ValidationException(string message, Exception? innerException = null, params string[] errors)
: base(message, StatusCodes.Status400BadRequest, innerException, errors) { }
}
public class ValidationException(string message, Exception? innerException = null, params string[] errors)
: BadRequestException(message, innerException, errors);
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ public static void ValidateDependencies(
params Assembly[] assembliesToScan
)
{
var scanAssemblies = assembliesToScan.Length != 0 ? assembliesToScan : [Assembly.GetExecutingAssembly(),];
var scanAssemblies = assembliesToScan.Length != 0 ? assembliesToScan : [Assembly.GetExecutingAssembly()];

// for resolving scoped based dependencies without errors
using var scope = rootServiceProvider.CreateScope();
Expand Down
2 changes: 1 addition & 1 deletion src/Shared/Core/Extensions/ValidationExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Shared.Core.Extensions;
// https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/attributes/caller-information
public static class ValidationExtensions
{
private static readonly HashSet<string> _allowedCurrency = new() { "USD", "EUR", };
private static readonly HashSet<string> _allowedCurrency = ["USD", "EUR",];

public static T NotBeNull<T>(
[NotNull] this T? argument,
Expand Down
8 changes: 3 additions & 5 deletions src/Shared/Core/Reflection/Extensions/TypeExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -589,11 +589,9 @@ public static Type UnwrapEnumType(this Type type)
}

public static Type MakeNullable(this Type type, bool nullable = true) =>
type.IsNullableType() == nullable
? type
: nullable
? typeof(Nullable<>).MakeGenericType(type)
: type.UnwrapNullableType();
type.IsNullableType() == nullable ? type
: nullable ? typeof(Nullable<>).MakeGenericType(type)
: type.UnwrapNullableType();

/// <summary>
/// Helper method use to differentiate behavior between command/query/event handlers.
Expand Down
6 changes: 3 additions & 3 deletions src/Shared/Core/Reflection/ReflectionUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,14 @@ public static TResult CompileMethodInvocation<TResult>(MethodInfo methodInfo)
.Compile();
}

var lambdaArgument = new List<ParameterExpression> { instanceArgument, };
var lambdaArgument = new List<ParameterExpression> { instanceArgument };

var type = methodInfo.DeclaringType;
var instanceVariable = Expression.Variable(type);
var blockVariables = new List<ParameterExpression> { instanceVariable, };
var blockVariables = new List<ParameterExpression> { instanceVariable };
var blockExpressions = new List<Expression>
{
Expression.Assign(instanceVariable, Expression.ConvertChecked(instanceArgument, type))
Expression.Assign(instanceVariable, Expression.ConvertChecked(instanceArgument, type)),
};
var callArguments = new List<ParameterExpression>();

Expand Down
4 changes: 2 additions & 2 deletions src/Shared/Logging/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public static WebApplicationBuilder AddCustomSerilog(
builder.Environment.EnvironmentName
}-{
DateTime.Now
:yyyy-MM}"
:yyyy-MM}",
}
);
}
Expand All @@ -97,7 +97,7 @@ public static WebApplicationBuilder AddCustomSerilog(
serilogOptions.GrafanaLokiUrl,
new[]
{
new LokiLabel { Key = "service", Value = "vertical-slice-api-template" }
new LokiLabel { Key = "service", Value = "vertical-slice-api-template" },
},
new[] { "app" }
);
Expand Down
2 changes: 1 addition & 1 deletion src/Shared/Swagger/ConfigureSwaggerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ private static OpenApiInfo CreateInfoForApiVersion(ApiVersionDescription descrip
Title = "Vertical.Slice.Template",
Version = description.ApiVersion.ToString(),
Contact = new OpenApiContact() { Name = "Bill Mei", Email = "[email protected]" },
License = new OpenApiLicense() { Name = "MIT", Url = new Uri("https://opensource.org/licenses/MIT") }
License = new OpenApiLicense() { Name = "MIT", Url = new Uri("https://opensource.org/licenses/MIT") },
};

if (description.IsDeprecated)
Expand Down
12 changes: 10 additions & 2 deletions src/Shared/Validation/Extensions/ValidatorExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ public static async Task<TRequest> HandleValidationAsync<TRequest>(
{
var validationResult = await validator.ValidateAsync(request, cancellationToken).ConfigureAwait(false);
if (!validationResult.IsValid)
throw new ValidationException(validationResult.ToValidationResultModel().Message);
{
throw new ValidationException(
validationResult.ToValidationResultModel().Errors?.FirstOrDefault()?.Message ??
validationResult.ToValidationResultModel().Message);
}

return request;
}
Expand All @@ -24,7 +28,11 @@ public static TRequest HandleValidation<TRequest>(this IValidator<TRequest> vali
{
var validationResult = validator.Validate(request);
if (!validationResult.IsValid)
throw new ValidationException(validationResult.ToValidationResultModel().Message);
{
throw new ValidationException(
validationResult.ToValidationResultModel().Errors?.FirstOrDefault()?.Message ??
validationResult.ToValidationResultModel().Message);
}

return request;
}
Expand Down
4 changes: 2 additions & 2 deletions src/Shared/Web/Extensions/HeaderDictionaryExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public static T Get<T>(
{
ParameterPick.First => values.FirstOrDefault(),
ParameterPick.Last => values.LastOrDefault(),
_ => value
_ => value,
};
}

Expand All @@ -57,5 +57,5 @@ public static T Get<T>(
public enum ParameterPick
{
First,
Last
Last,
}
2 changes: 1 addition & 1 deletion src/Shared/Web/Extensions/QueryCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public static T Get<T>(
{
ParameterPick.First => values.FirstOrDefault(),
ParameterPick.Last => values.LastOrDefault(),
_ => value
_ => value,
};
}

Expand Down
4 changes: 2 additions & 2 deletions src/Shared/Web/Minimal/Extensions/MinimalApiExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder builder
{
x.GroupName,
x.PrefixRoute,
x.Version
x.Version,
})
.ToDictionary(
x => x.Key,
Expand All @@ -87,7 +87,7 @@ this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder builder
{
Verion = x.Key.Version,
x.Key.GroupName,
Endpoints = x.Select(v => v)
Endpoints = x.Select(v => v),
});

foreach (var endpointVersion in endpointVersions)
Expand Down
Loading

0 comments on commit 0cf21fa

Please sign in to comment.