90 lines
2.7 KiB
PHP
90 lines
2.7 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Shared API error helpers for handlers and db_init.
|
|
*/
|
|
|
|
function qdb_is_lock_exception(Throwable $e): bool
|
|
{
|
|
$msg = $e->getMessage();
|
|
return str_contains($msg, 'Could not acquire lock')
|
|
|| str_contains($msg, 'Could not open lock file');
|
|
}
|
|
|
|
/**
|
|
* Map internal exceptions to safe, identifiable API error messages (details go to error_log).
|
|
*/
|
|
function qdb_public_error_message(Throwable $e, string $contextLabel = 'Operation'): string
|
|
{
|
|
$msg = $e->getMessage();
|
|
|
|
if (qdb_is_lock_exception($e)) {
|
|
return 'Database is busy because another update is in progress. Please try again in a moment.';
|
|
}
|
|
if (str_contains($msg, 'decrypt') || str_contains($msg, 'QDB_MASTER_KEY')) {
|
|
return 'Database configuration error. Contact the server administrator.';
|
|
}
|
|
if (str_contains($msg, 'Could not read') || str_contains($msg, 'Could not write')
|
|
|| str_contains($msg, 'Could not save')) {
|
|
return 'Database storage error. Contact the server administrator.';
|
|
}
|
|
if ($e instanceof PDOException) {
|
|
return $contextLabel . ' failed due to a database error.';
|
|
}
|
|
|
|
return $contextLabel . ' failed. If this persists, contact support.';
|
|
}
|
|
|
|
/**
|
|
* @return array{0: string, 1: string, 2: int} code, message, http status
|
|
*/
|
|
function qdb_api_error_for_exception(Throwable $e, string $contextLabel = 'Operation'): array
|
|
{
|
|
if (qdb_is_lock_exception($e)) {
|
|
return ['LOCKED', qdb_public_error_message($e, $contextLabel), 503];
|
|
}
|
|
return ['SERVER_ERROR', qdb_public_error_message($e, $contextLabel), 500];
|
|
}
|
|
|
|
/**
|
|
* Standard handler catch: rollback, discard DB temp file, log, JSON error (never returns).
|
|
*/
|
|
function qdb_handler_fail(
|
|
Throwable $e,
|
|
string $contextLabel,
|
|
?PDO $pdo = null,
|
|
?string $tmpDb = null,
|
|
$lockFp = null
|
|
): never {
|
|
if (defined('QDB_TESTING') && qdb_test_is_control_flow_exception($e)) {
|
|
throw $e;
|
|
}
|
|
if ($pdo !== null && $pdo->inTransaction()) {
|
|
try {
|
|
$pdo->rollBack();
|
|
} catch (Throwable) {
|
|
}
|
|
}
|
|
if ($tmpDb !== null && $lockFp !== null) {
|
|
require_once __DIR__ . '/../db_init.php';
|
|
qdb_discard($tmpDb, $lockFp);
|
|
}
|
|
qdb_emit_api_error($e, $contextLabel);
|
|
}
|
|
|
|
/**
|
|
* Log exception and exit with unified JSON error (never returns).
|
|
*/
|
|
function qdb_emit_api_error(Throwable $e, string $contextLabel): never
|
|
{
|
|
if (defined('QDB_TESTING') && qdb_test_is_control_flow_exception($e)) {
|
|
throw $e;
|
|
}
|
|
if (function_exists('qdb_api_log_note_exception')) {
|
|
qdb_api_log_note_exception($e);
|
|
}
|
|
error_log($contextLabel . ': ' . $e->getMessage());
|
|
[$code, $message, $status] = qdb_api_error_for_exception($e, $contextLabel);
|
|
json_error($code, $message, $status);
|
|
}
|