Synchronizer error factory implementation (#645)

* Added sync error factories for outlook and gmail.

* Implement ObjectCannotBeDeletedHandler for OutlookSynchronizer.

* Remove debug code.

* Implement del key to delete on mail list.

* Revert debug code.
This commit is contained in:
Burak Kaan Köse
2025-04-26 10:49:55 +02:00
committed by GitHub
parent 5b44cf03ce
commit 9feb3f35c3
13 changed files with 289 additions and 39 deletions

View File

@@ -0,0 +1,39 @@
using System.Threading.Tasks;
using Microsoft.Kiota.Abstractions;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Errors;
using Wino.Core.Domain.Models.Requests;
using Wino.Core.Requests.Bundles;
namespace Wino.Core.Synchronizers.Errors.Outlook;
public class ObjectCannotBeDeletedHandler : ISynchronizerErrorHandler
{
private readonly IMailService _mailService;
public ObjectCannotBeDeletedHandler(IMailService mailService)
{
_mailService = mailService;
}
public bool CanHandle(SynchronizerErrorContext error)
{
return error.ErrorMessage.Contains("ErrorCannotDeleteObject") && error.RequestBundle is HttpRequestBundle<RequestInformation>;
}
public async Task<bool> HandleAsync(SynchronizerErrorContext error)
{
var castedBundle = error.RequestBundle as HttpRequestBundle<RequestInformation>;
if (castedBundle?.Request is MailRequestBase mailRequest)
{
var request = castedBundle.Request;
await _mailService.DeleteMailAsync(error.Account.Id, mailRequest.Item.Id);
return true;
}
return false;
}
}

View File

@@ -25,6 +25,7 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Exceptions;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Accounts;
using Wino.Core.Domain.Models.Errors;
using Wino.Core.Domain.Models.Folders;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Synchronization;
@@ -57,6 +58,7 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
private readonly PeopleServiceService _peopleService;
private readonly IGmailChangeProcessor _gmailChangeProcessor;
private readonly IGmailSynchronizerErrorHandlerFactory _gmailSynchronizerErrorHandlerFactory;
private readonly ILogger _logger = Log.ForContext<GmailSynchronizer>();
// Keeping a reference for quick access to the virtual archive folder.
@@ -64,7 +66,8 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
public GmailSynchronizer(MailAccount account,
IGmailAuthenticator authenticator,
IGmailChangeProcessor gmailChangeProcessor) : base(account)
IGmailChangeProcessor gmailChangeProcessor,
IGmailSynchronizerErrorHandlerFactory gmailSynchronizerErrorHandlerFactory) : base(account)
{
var messageHandler = new GmailClientMessageHandler(authenticator, account);
@@ -79,6 +82,7 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
_calendarService = new CalendarService(initializer);
_gmailChangeProcessor = gmailChangeProcessor;
_gmailSynchronizerErrorHandlerFactory = gmailSynchronizerErrorHandlerFactory;
}
public ConfigurableHttpClient CreateHttpClient(CreateHttpClientArgs args) => _googleHttpClient;
@@ -1106,30 +1110,50 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
}
}
private void ProcessGmailRequestError(RequestError error, IRequestBundle<IClientServiceRequest> bundle)
private async Task ProcessGmailRequestErrorAsync(RequestError error, IRequestBundle<IClientServiceRequest> bundle)
{
if (error == null) return;
// OutOfMemoryException is a known bug in Gmail SDK.
if (error.Code == 0)
// Create error context
var errorContext = new SynchronizerErrorContext
{
bundle?.UIChangeRequest?.RevertUIChanges();
throw new OutOfMemoryException(error.Message);
}
ErrorCode = error.Code,
ErrorMessage = error.Message,
RequestBundle = bundle,
AdditionalData = new Dictionary<string, object>
{
{ "Account", Account },
{ "Error", error }
}
};
// Entity not found.
if (error.Code == 404)
// Try to handle the error with registered handlers
var handled = await _gmailSynchronizerErrorHandlerFactory.HandleErrorAsync(errorContext);
// If not handled by any specific handler, apply default error handling
if (!handled)
{
bundle?.UIChangeRequest?.RevertUIChanges();
throw new SynchronizerEntityNotFoundException(error.Message);
}
// OutOfMemoryException is a known bug in Gmail SDK.
if (error.Code == 0)
{
bundle?.UIChangeRequest?.RevertUIChanges();
throw new OutOfMemoryException(error.Message);
}
if (!string.IsNullOrEmpty(error.Message))
{
bundle?.UIChangeRequest?.RevertUIChanges();
error.Errors?.ForEach(error => _logger.Error("Unknown Gmail SDK error for {Name}\n{Error}", Account.Name, error));
// Entity not found.
if (error.Code == 404)
{
bundle?.UIChangeRequest?.RevertUIChanges();
throw new SynchronizerEntityNotFoundException(error.Message);
}
throw new SynchronizerException(error.Message);
if (!string.IsNullOrEmpty(error.Message))
{
bundle?.UIChangeRequest?.RevertUIChanges();
error.Errors?.ForEach(error => _logger.Error("Unknown Gmail SDK error for {Name}\n{Error}", Account.Name, error));
throw new SynchronizerException(error.Message);
}
}
}
@@ -1148,7 +1172,7 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
{
try
{
ProcessGmailRequestError(error, null);
await ProcessGmailRequestErrorAsync(error, null);
}
catch (OutOfMemoryException)
{
@@ -1209,7 +1233,7 @@ public class GmailSynchronizer : WinoSynchronizer<IClientServiceRequest, Message
HttpResponseMessage httpResponseMessage,
CancellationToken cancellationToken = default)
{
ProcessGmailRequestError(error, bundle);
await ProcessGmailRequestErrorAsync(error, bundle);
if (bundle is HttpRequestBundle<IClientServiceRequest, Message> messageBundle)
{

View File

@@ -30,6 +30,7 @@ using Wino.Core.Domain.Enums;
using Wino.Core.Domain.Exceptions;
using Wino.Core.Domain.Interfaces;
using Wino.Core.Domain.Models.Accounts;
using Wino.Core.Domain.Models.Errors;
using Wino.Core.Domain.Models.Folders;
using Wino.Core.Domain.Models.MailItem;
using Wino.Core.Domain.Models.Synchronization;
@@ -83,9 +84,12 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
private readonly ILogger _logger = Log.ForContext<OutlookSynchronizer>();
private readonly IOutlookChangeProcessor _outlookChangeProcessor;
private readonly GraphServiceClient _graphClient;
private readonly IOutlookSynchronizerErrorHandlerFactory _errorHandlingFactory;
public OutlookSynchronizer(MailAccount account,
IAuthenticator authenticator,
IOutlookChangeProcessor outlookChangeProcessor) : base(account)
IOutlookChangeProcessor outlookChangeProcessor,
IOutlookSynchronizerErrorHandlerFactory errorHandlingFactory) : base(account)
{
var tokenProvider = new MicrosoftTokenProvider(Account, authenticator);
@@ -106,6 +110,7 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
_graphClient = new GraphServiceClient(httpClient, new BaseBearerTokenAuthenticationProvider(tokenProvider));
_outlookChangeProcessor = outlookChangeProcessor;
_errorHandlingFactory = errorHandlingFactory;
}
#region MS Graph Handlers
@@ -990,14 +995,37 @@ public class OutlookSynchronizer : WinoSynchronizer<RequestInformation, Message,
HttpResponseMessage response,
List<string> errors)
{
bundle.UIChangeRequest?.RevertUIChanges();
var content = await response.Content.ReadAsStringAsync();
var errorJson = JsonNode.Parse(content);
var errorString = $"[{response.StatusCode}] {errorJson["error"]["code"]} - {errorJson["error"]["message"]}\n";
var errorCode = errorJson["error"]["code"].GetValue<string>();
var errorMessage = errorJson["error"]["message"].GetValue<string>();
var errorString = $"[{response.StatusCode}] {errorCode} - {errorMessage}\n";
Debug.WriteLine(errorString);
errors.Add(errorString);
// Create error context
var errorContext = new SynchronizerErrorContext
{
Account = Account,
ErrorCode = (int)response.StatusCode,
ErrorMessage = errorMessage,
RequestBundle = bundle,
AdditionalData = new Dictionary<string, object>
{
{ "ErrorCode", errorCode },
{ "HttpResponse", response },
{ "Content", content }
}
};
// Try to handle the error with registered handlers
var handled = await _errorHandlingFactory.HandleErrorAsync(errorContext);
// If not handled by any specific handler, revert UI changes and add to error list
if (!handled)
{
bundle.UIChangeRequest?.RevertUIChanges();
Debug.WriteLine(errorString);
errors.Add(errorString);
}
}
private void ThrowBatchExecutionException(List<string> errors)