The Supabase UE5 Plugin implements comprehensive, production-ready error handling across all operations. This system ensures robust operation in production environments with graceful failure handling, detailed error reporting, and automatic recovery mechanisms.
USupabaseSubsystem (Top Level)
├── Connection Health Monitoring
├── Token Refresh Error Recovery
└── Global Error Broadcasting
USupabaseManager (Static Interface)
├── Subsystem Validation
├── Input Sanitization
└── Operation Routing
Async Operations (Individual Nodes)
├── Input Validation
├── HTTP Error Handling
├── JSON Parsing Errors
└── Memory Management
The USupabaseSubsystem provides centralized error handling and recovery:
// Automatic health monitoring
void USupabaseSubsystem::CheckConnectionHealth()
{
if (!IsConnectionHealthy())
{
OnConnectionLost.Broadcast();
AttemptReconnection();
}
}
// Token refresh error recovery
void USupabaseSubsystem::HandleTokenRefreshError(const FString& Error)
{
UE_LOG(LogSupabase, Warning, TEXT("Token refresh failed: %s"), *Error);
OnTokenRefreshFailed.Broadcast(Error);
// Attempt re-authentication if token is completely invalid
if (Error.Contains(TEXT("invalid_token")))
{
ClearAuthSession();
OnAuthenticationRequired.Broadcast();
}
}
All async operations implement comprehensive input validation:
// Example from AsyncExecuteSQL
bool UAsyncExecuteSQL::ValidateInputs()
{
if (SQLQuery.IsEmpty())
{
BroadcastError(TEXT("SQL query cannot be empty"));
return false;
}
// SQL injection protection
if (ContainsDangerousSQL(SQLQuery))
{
BroadcastError(TEXT("SQL query contains prohibited operations"));
return false;
}
// Length validation (DoS protection)
if (SQLQuery.Len() > MAX_SQL_QUERY_LENGTH)
{
BroadcastError(TEXT("SQL query exceeds maximum length"));
return false;
}
return true;
}
// Security validation
bool UAsyncExecuteSQL::ContainsDangerousSQL(const FString& SQL)
{
static const TArray<FString> ProhibitedOperations = {
TEXT("DROP"), TEXT("TRUNCATE"), TEXT("ALTER"), TEXT("CREATE"),
TEXT("DELETE"), TEXT("GRANT"), TEXT("REVOKE")
};
FString UpperSQL = SQL.ToUpper();
for (const FString& Operation : ProhibitedOperations)
{
if (UpperSQL.Contains(Operation))
{
return true;
}
}
return false;
}
The plugin categorizes HTTP errors for appropriate handling:
void UAsyncApiRequest::OnRequestComplete(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
if (!bWasSuccessful || !Response.IsValid())
{
HandleNetworkError(Request, Response);
return;
}
int32 StatusCode = Response->GetResponseCode();
// Classify error types
if (StatusCode >= 400 && StatusCode < 500)
{
HandleClientError(StatusCode, Response->GetContentAsString());
}
else if (StatusCode >= 500)
{
HandleServerError(StatusCode, Response->GetContentAsString());
}
else if (StatusCode == 200 || StatusCode == 201)
{
HandleSuccess(Response->GetContentAsString());
}
}
void UAsyncApiRequest::HandleClientError(int32 StatusCode, const FString& Content)
{
FString ErrorMessage;
switch (StatusCode)
{
case 400:
ErrorMessage = ExtractErrorFromResponse(Content, TEXT("Bad Request"));
break;
case 401:
ErrorMessage = TEXT("Authentication required");
// Trigger re-authentication
TriggerAuthenticationRequired();
break;
case 403:
ErrorMessage = TEXT("Insufficient permissions");
break;
case 404:
ErrorMessage = TEXT("Resource not found");
break;
case 409:
ErrorMessage = TEXT("Conflict - resource already exists");
break;
case 422:
ErrorMessage = ExtractValidationErrors(Content);
break;
case 429:
ErrorMessage = TEXT("Rate limit exceeded");
// Implement exponential backoff
ScheduleRetryWithBackoff();
return;
default:
ErrorMessage = FString::Printf(TEXT("Client error %d: %s"), StatusCode, *Content);
}
BroadcastError(ErrorMessage, StatusCode);
}
Connection Failures:
Handling Strategy:
void HandleNetworkError(FHttpRequestPtr Request, FHttpResponsePtr Response)
{
FString ErrorMsg = TEXT("Network error: ");
if (!Response.IsValid())
{
ErrorMsg += TEXT("No response received - check internet connection");
}
else if (Response->GetResponseCode() == 0)
{
ErrorMsg += TEXT("Connection timeout or DNS failure");
}
// Implement retry logic for network errors
if (RetryCount < MAX_RETRY_ATTEMPTS)
{
ScheduleRetry();
}
else
{
BroadcastError(ErrorMsg);
}
}
Token Issues:
Handling Strategy:
void HandleAuthenticationError(const FString& Error)
{
UE_LOG(LogSupabase, Warning, TEXT("Authentication error: %s"), *Error);
// Clear invalid session
ClearAuthSession();
// Notify UI for re-authentication
OnAuthenticationRequired.Broadcast();
// Attempt automatic token refresh if possible
if (HasValidRefreshToken())
{
AttemptTokenRefresh();
}
}
Input Validation:
Example Implementation:
bool ValidateUserRegistration(const FString& Email, const FString& Password)
{
// Email validation
if (!IsValidEmailFormat(Email))
{
BroadcastError(TEXT("Invalid email format"));
return false;
}
// Password strength validation
if (Password.Len() < MIN_PASSWORD_LENGTH)
{
BroadcastError(FString::Printf(TEXT("Password must be at least %d characters"), MIN_PASSWORD_LENGTH));
return false;
}
// Security checks
if (ContainsProhibitedCharacters(Password))
{
BroadcastError(TEXT("Password contains prohibited characters"));
return false;
}
return true;
}
JSON Parsing:
Handling Strategy:
bool TryParseJsonResponse(const FString& ResponseContent, FJsonObjectWrapper& OutJsonWrapper)
{
if (ResponseContent.IsEmpty())
{
UE_LOG(LogSupabase, Error, TEXT("Empty JSON response"));
return false;
}
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(ResponseContent);
if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid())
{
UE_LOG(LogSupabase, Error, TEXT("Failed to parse JSON: %s"), *ResponseContent.Left(200));
// Try alternative parsing strategies
return TryParseAsArray(ResponseContent, OutJsonWrapper);
}
OutJsonWrapper.JsonObject = JsonObject;
return true;
}
class FRetryManager
{
private:
int32 RetryCount = 0;
float BaseDelay = 1.0f;
static constexpr int32 MAX_RETRIES = 3;
public:
bool ShouldRetry(int32 StatusCode)
{
if (RetryCount >= MAX_RETRIES) return false;
// Retry on server errors and rate limits
return StatusCode == 429 || (StatusCode >= 500 && StatusCode < 600);
}
float GetRetryDelay()
{
// Exponential backoff with jitter
float Delay = BaseDelay * FMath::Pow(2.0f, RetryCount);
float Jitter = FMath::RandRange(0.0f, 0.5f);
return Delay + Jitter;
}
void IncrementRetry() { RetryCount++; }
void Reset() { RetryCount = 0; }
};
void USupabaseSubsystem::AttemptReconnection()
{
if (bIsReconnecting) return;
bIsReconnecting = true;
// Stop current operations
CancelActiveOperations();
// Reset connection state
ResetConnection();
// Attempt to restore connection
GetWorld()->GetTimerManager().SetTimer(
ReconnectionTimerHandle,
this,
&USupabaseSubsystem::PerformReconnection,
GetReconnectionDelay(),
false
);
}
void USupabaseSubsystem::PerformReconnection()
{
if (TestConnection())
{
bIsReconnecting = false;
OnConnectionRestored.Broadcast();
// Resume pending operations
ResumePendingOperations();
}
else
{
// Retry with exponential backoff
ReconnectionAttempts++;
if (ReconnectionAttempts < MAX_RECONNECTION_ATTEMPTS)
{
AttemptReconnection();
}
else
{
OnConnectionFailed.Broadcast(TEXT("Failed to reconnect after maximum attempts"));
}
}
}
DECLARE_LOG_CATEGORY_EXTERN(LogSupabase, Log, All);
DECLARE_LOG_CATEGORY_EXTERN(LogSupabaseAuth, Log, All);
DECLARE_LOG_CATEGORY_EXTERN(LogSupabaseRealtime, Log, All);
// Usage examples
UE_LOG(LogSupabase, Error, TEXT("Connection failed: %s"), *ErrorMessage);
UE_LOG(LogSupabaseAuth, Warning, TEXT("Token refresh required for user %s"), *UserId);
UE_LOG(LogSupabaseRealtime, Verbose, TEXT("WebSocket message received: %s"), *Message);
// Global error events
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnSupabaseError, FString, ErrorMessage);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOnSupabaseHttpError, FString, ErrorMessage, int32, StatusCode);
// Subsystem-level events
UPROPERTY(BlueprintAssignable)
FOnSupabaseError OnConnectionLost;
UPROPERTY(BlueprintAssignable)
FOnSupabaseError OnConnectionRestored;
UPROPERTY(BlueprintAssignable)
FOnSupabaseHttpError OnApiError;
void USupabaseSubsystem::LogDebugInfo()
{
UE_LOG(LogSupabase, Log, TEXT("=== Supabase Debug Info ==="));
UE_LOG(LogSupabase, Log, TEXT("Connection State: %s"), *GetConnectionStateString());
UE_LOG(LogSupabase, Log, TEXT("Auth State: %s"), *GetAuthStateString());
UE_LOG(LogSupabase, Log, TEXT("Active Operations: %d"), ActiveOperations.Num());
UE_LOG(LogSupabase, Log, TEXT("Last Health Check: %s"), *LastHealthCheck.ToString());
UE_LOG(LogSupabase, Log, TEXT("Token Expires: %s"), *TokenExpirationTime.ToString());
UE_LOG(LogSupabase, Log, TEXT("==========================="));
}
// Blueprint-accessible error information
USTRUCT(BlueprintType)
struct FSupabaseError
{
GENERATED_BODY()
UPROPERTY(BlueprintReadOnly)
FString Message;
UPROPERTY(BlueprintReadOnly)
int32 StatusCode;
UPROPERTY(BlueprintReadOnly)
FString Category; // "Network", "Auth", "Validation", etc.
UPROPERTY(BlueprintReadOnly)
bool bIsRetryable;
};
UCLASS(BlueprintType)
class SUPABASE_API USupabaseErrorUtils : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintPure, Category = "Supabase|Error Handling")
static bool IsNetworkError(const FSupabaseError& Error);
UFUNCTION(BlueprintPure, Category = "Supabase|Error Handling")
static bool IsAuthenticationError(const FSupabaseError& Error);
UFUNCTION(BlueprintPure, Category = "Supabase|Error Handling")
static FString GetUserFriendlyErrorMessage(const FSupabaseError& Error);
UFUNCTION(BlueprintCallable, Category = "Supabase|Error Handling")
static void LogError(const FSupabaseError& Error);
};
Problem: User loses internet connection during operation
Solution:
// Detect network loss
void HandleNetworkLoss()
{
// Pause all operations
PauseActiveOperations();
// Cache operations for retry
CacheFailedOperations();
// Monitor for connection restoration
StartNetworkMonitoring();
// Notify user
ShowOfflineMode();
}
// Resume when connection restored
void HandleNetworkRestoration()
{
// Resume cached operations
ResumeCachedOperations();
// Update UI
ShowOnlineMode();
}
Problem: User’s session expires during active use
Solution:
// Handle token expiry
void HandleTokenExpiry()
{
// Cache current operation
CacheCurrentOperation();
// Attempt silent refresh
if (HasValidRefreshToken())
{
AttemptSilentRefresh();
}
else
{
// Require user re-authentication
ShowLoginPrompt();
}
}
// Resume after re-authentication
void HandleAuthenticationSuccess()
{
// Resume cached operation
ResumeCachedOperation();
}
Problem: API rate limits exceeded
Solution:
// Handle rate limiting
void HandleRateLimit(int32 RetryAfterSeconds)
{
// Show user-friendly message
ShowRateLimitMessage(RetryAfterSeconds);
// Schedule retry
ScheduleRetryAfterDelay(RetryAfterSeconds);
// Reduce operation frequency
ImplementBackoffStrategy();
}
USTRUCT(BlueprintType)
struct FSupabaseErrorSettings
{
GENERATED_BODY()
// Retry configuration
UPROPERTY(EditAnywhere, BlueprintReadWrite)
int32 MaxRetryAttempts = 3;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float BaseRetryDelay = 1.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float MaxRetryDelay = 30.0f;
// Timeout configuration
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float ConnectionTimeout = 30.0f;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float RequestTimeout = 60.0f;
// Logging configuration
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool bEnableVerboseLogging = false;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
bool bLogSensitiveData = false;
};
This documentation covers the comprehensive error handling system implemented in the Supabase UE5 Plugin. For specific implementation details, refer to the individual class documentation and code examples.