initial prototype based on nat-as-server
Some checks failed
PHPUnit / test (push) Has been cancelled
Some checks failed
PHPUnit / test (push) Has been cancelled
This commit is contained in:
89
lib/errors.php
Normal file
89
lib/errors.php
Normal file
@ -0,0 +1,89 @@
|
||||
<?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);
|
||||
}
|
||||
Reference in New Issue
Block a user