feat(api,infra): implement GlobalExceptionHandler and enhance PlaidException for detailed error handling
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
using Microsoft.AspNetCore.Diagnostics;
|
||||
|
||||
internal sealed class GlobalExceptionHandler(
|
||||
IProblemDetailsService problemDetailsService,
|
||||
ILogger<GlobalExceptionHandler> logger
|
||||
) : IExceptionHandler
|
||||
{
|
||||
public async ValueTask<bool> TryHandleAsync(
|
||||
HttpContext httpContext,
|
||||
Exception exception,
|
||||
CancellationToken cancellationToken
|
||||
)
|
||||
{
|
||||
logger.LogError(exception, "An unhandled exception occurred");
|
||||
|
||||
httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
|
||||
|
||||
return await problemDetailsService.TryWriteAsync(new ProblemDetailsContext
|
||||
{
|
||||
HttpContext = httpContext,
|
||||
Exception = exception,
|
||||
ProblemDetails = new ProblemDetails
|
||||
{
|
||||
Type = exception.GetType().Name,
|
||||
Title = "Uh oh something has gone wrong.",
|
||||
Detail = exception.Message
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ var builder = WebApplication.CreateBuilder(args);
|
||||
builder.AddServiceDefaults();
|
||||
|
||||
builder.Services.AddValidation();
|
||||
builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
|
||||
builder.Services.AddProblemDetails();
|
||||
|
||||
builder.Services.ConfigureHttpJsonOptions(static options =>
|
||||
|
||||
@@ -31,7 +31,7 @@ internal sealed class PlaidAccountService : IPlaidAccountService
|
||||
var assemblyName = Assembly.GetExecutingAssembly().GetName().FullName;
|
||||
var environmentName = System.Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
|
||||
|
||||
var ltr = await _client.LinkTokenCreateAsync(new()
|
||||
var linkTokenResponse = await _client.LinkTokenCreateAsync(new()
|
||||
{
|
||||
ClientName = $"{assemblyName}_{environmentName}",
|
||||
Products = [Products.Transactions],
|
||||
@@ -44,56 +44,73 @@ internal sealed class PlaidAccountService : IPlaidAccountService
|
||||
Webhook = _options.Value.Webhook,
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
if (ltr.IsSuccessStatusCode is false)
|
||||
if (linkTokenResponse.IsSuccessStatusCode is false)
|
||||
{
|
||||
throw new PlaidException("Unable to create link token");
|
||||
throw new PlaidException(
|
||||
"Unable to create link token",
|
||||
linkTokenResponse.Error,
|
||||
linkTokenResponse.RequestId,
|
||||
(int?)linkTokenResponse.StatusCode
|
||||
);
|
||||
}
|
||||
|
||||
return ltr.LinkToken;
|
||||
return linkTokenResponse.LinkToken;
|
||||
}
|
||||
|
||||
public async Task<(string ItemId, string AccessToken)> ExchangeTokenAsync(string publicToken)
|
||||
{
|
||||
var ptr = await _client.ItemPublicTokenExchangeAsync(new()
|
||||
var publicTokenResponse = await _client.ItemPublicTokenExchangeAsync(new()
|
||||
{
|
||||
PublicToken = publicToken,
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
if (ptr.IsSuccessStatusCode is false)
|
||||
if (publicTokenResponse.IsSuccessStatusCode is false)
|
||||
{
|
||||
throw new PlaidException("Unable to exchange public token for access token");
|
||||
throw new PlaidException(
|
||||
"Unable to exchange public token for access token",
|
||||
publicTokenResponse.Error,
|
||||
publicTokenResponse.RequestId,
|
||||
(int?)publicTokenResponse.StatusCode
|
||||
);
|
||||
}
|
||||
|
||||
return (ptr.ItemId, ptr.AccessToken);
|
||||
return (publicTokenResponse.ItemId, publicTokenResponse.AccessToken);
|
||||
}
|
||||
|
||||
public async Task<List<Going.Plaid.Entity.Account>> GetAccountsAsync(string accessToken)
|
||||
{
|
||||
var ar = await _client.AccountsGetAsync(new()
|
||||
var accountResponse = await _client.AccountsGetAsync(new()
|
||||
{
|
||||
AccessToken = accessToken,
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
if (ar.IsSuccessStatusCode is false)
|
||||
if (accountResponse.IsSuccessStatusCode is false)
|
||||
{
|
||||
throw new PlaidException("Unable to retrieve accounts");
|
||||
throw new PlaidException(
|
||||
"Unable to retrieve accounts",
|
||||
accountResponse.Error,
|
||||
accountResponse.RequestId,
|
||||
(int?)accountResponse.StatusCode
|
||||
);
|
||||
}
|
||||
|
||||
return [.. ar.Accounts];
|
||||
return [.. accountResponse.Accounts];
|
||||
}
|
||||
|
||||
public async Task<ItemWithConsentFields> GetItemAsync(string accessToken)
|
||||
{
|
||||
var ar = await _client.ItemGetAsync(new()
|
||||
var itemResponse = await _client.ItemGetAsync(new()
|
||||
{
|
||||
AccessToken = accessToken,
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
if (ar.IsSuccessStatusCode is false)
|
||||
if (itemResponse.IsSuccessStatusCode is false)
|
||||
{
|
||||
throw new PlaidException("Unable to retrieve item");
|
||||
throw new PlaidException(
|
||||
"Unable to retrieve item"
|
||||
);
|
||||
}
|
||||
|
||||
return ar.Item;
|
||||
return itemResponse.Item;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,13 @@ namespace FiscalOS.Infra.Common;
|
||||
|
||||
public class PlaidException : Exception
|
||||
{
|
||||
public PlaidError? Error { get; }
|
||||
public string? RequestId { get; }
|
||||
public int? StatusCode { get; }
|
||||
public string? ErrorCode => Error?.ErrorCode;
|
||||
public string? ErrorType => Error?.ErrorType;
|
||||
public string? ErrorMessage => Error?.ErrorMessage;
|
||||
|
||||
public PlaidException()
|
||||
{
|
||||
}
|
||||
@@ -13,4 +20,34 @@ public class PlaidException : Exception
|
||||
public PlaidException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
public PlaidException(
|
||||
string message,
|
||||
PlaidError? error = null,
|
||||
string? plaidRequestId = null,
|
||||
int? statusCode = null,
|
||||
Exception? innerException = null
|
||||
) : base(message, innerException)
|
||||
{
|
||||
Error = error;
|
||||
RequestId = plaidRequestId;
|
||||
StatusCode = statusCode;
|
||||
|
||||
if (error is not null)
|
||||
{
|
||||
Data[nameof(ErrorCode)] = error.ErrorCode;
|
||||
Data[nameof(ErrorType)] = error.ErrorType;
|
||||
Data[nameof(ErrorMessage)] = error.ErrorMessage;
|
||||
}
|
||||
|
||||
if (plaidRequestId is not null)
|
||||
{
|
||||
Data[nameof(RequestId)] = plaidRequestId;
|
||||
}
|
||||
|
||||
if (statusCode is not null)
|
||||
{
|
||||
Data[nameof(StatusCode)] = statusCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user