Ловите ошибки SDK

PHP

Что несёт ошибка SDK, как ловить её по типу и ветвиться по коду и какие ошибки говорят, что запуск мог стоить денег.

5 мин чтения

Вызов SDK, который API отклоняет или который остаётся без ответа, выбрасывает ошибку. У всех ошибок SDK один корневой тип, и каждая хранит код API, поэтому один catch ловит их все, а код их различает.

Что несёт ошибка

ЧтоPHPNode.js
Код APIerrorCodecode
HTTP-статусstatusCodestatus
Фраза для человекаgetMessage()message
Подробности, где код их несётdetailsdetails
Идентификатор запросаrequestIdrequestId
Сколько секунд просил подождать отклонённый 429 или 503retryAfterSecondsretryAfterSeconds
  • Ветвитесь по коду. Сообщение — для человека, и его формулировка может меняться.

  • У ошибки соединения и у таймаута клиента нет ни кода, ни статуса: ответ API не прочитан. У ошибок, которые SDK выбрасывает сам — до отправки запроса или на ответе, который он не может прочитать, — свой код SDK; их перечисляет статья Типы ошибок.

  • Идентификатор запроса — то, что спросит поддержка. Это заголовок X-Request-Id ответа.

Ловить по типу, ветвиться по коду

PHP
use Slen\Sdk\Exceptions\OperationUncertainException;
use Slen\Sdk\Exceptions\SlenException;
use Slen\Sdk\Exceptions\SpendRefusedException;
use Slen\Sdk\Modules\Operations\Options\ExecuteOptions;

$input = ['messages' => [['role' => 'user', 'content' => 'Write a short follow-up email.']]];

try {
    $execution = $slen->operations()->execute('generate_email', $input, new ExecuteOptions(idempotencyKey: 'order-42:email'));

    echo $execution->status->value, PHP_EOL;
} catch (SpendRefusedException $error) {
    // The spend was refused, and the code says what refused it.
    echo 'refused: ', $error->errorCode, PHP_EOL;
} catch (OperationUncertainException $error) {
    // A provider was called and its answer was lost: the call may have been paid for.
    echo 'uncertain, request ', $error->requestId, PHP_EOL;
} catch (SlenException $error) {
    echo $error->statusCode, ' ', $error->errorCode, ': ', $error->getMessage(), PHP_EOL;
}
  • Ловите узкий тип раньше широкого: catch корневого типа ловит и отклонённую трату.

  • Отказы в трате — одно семейство; что делать с каждым, говорит статья Когда трата отклонена.

  • Какой тип получает каждый код, называет статья Типы ошибок.

  • Ошибка вашего собственного кода — не ошибка SDK. Пропустите её дальше.

SDK сам отправляет запрос ещё раз, только когда ответа не было, после 429 или 503 — Повторы и таймауты. Выбрасывает он ошибку последней попытки. Любой другой ответ API, который является ошибкой, выбрасывается сразу. Ожидание само читает статус ещё раз после неудачного чтения — как описывает статья Асинхронные запуски и ожидание.

Стоил ли запуск денег

Деньги запуска — это деньги вызовов моделей, которые сделал его флоу. Тип говорит, могли ли быть потрачены деньги и, значит, можно ли отправить вызов ещё раз.

  • Отклонено, прежде чем что-то выполнилось — SpendRefusedException, PolicyDeniedException, AuthenticationException, InvalidRequestException, CustomerNotFoundException, RateLimitException и их двойники в Node.js. Ничего не выполнилось и ничего не зарезервировано. Тот же запрос отклоняется снова, пока не изменится то, что его отклонило: бюджет начнёт следующее окно, клиента пополнят, ключ получит разрешение. Лимит запросов пропускает после паузы, о которой просит, а concurrency_limit_exceeded — когда идущий вызов закончится.

  • Занятый ключ — IdempotencyConflictException, IdempotencyConflictError. При idempotency_in_progress первый запрос под этим ключом ещё не закончился: отправьте вызов ещё раз позже под тем же ключом. При idempotency_conflict ключ принадлежит другому запросу: отправьте свой под новым ключом.

  • Сам Slen — ServiceUnavailableException, ServiceUnavailableError. Запрос не выполнен, и провайдер не вызывался. Отправьте его ещё раз позже под тем же ключом.

  • Нет ответа — TimeoutException, ConnectionException и их двойники в Node.js, без кода. Неизвестно, сделал ли Slen работу. SDK уже отправил запрос ещё раз под тем же ключом; отправляйте его ещё раз только под этим ключом, и Slen сделает работу один раз.

  • Неизвестный исход — OperationUncertainException, OperationUncertainError. Провайдер был вызван, а его ответ потерян, поэтому за вызов могли заплатить. Не отправляйте запуск ещё раз под новым ключом — Когда исход неизвестен.

  • Провайдер — ProviderException, ProviderError. С кодом provider_error, provider_unavailable, provider_rate_limit, provider_timeout, provider_not_configured или pool_exhausted вызов модели, который не удался, вернул свои деньги. С любым другим кодом или без кода ошибка не говорит, как далеко зашёл запуск: считайте это неизвестным исходом.

  • Флоу — FlowRuntimeException, FlowRuntimeError и любая ошибка из operations.execute() после того, как флоу начал выполняться: отказ или сбой на более позднем узле, ledger_unavailable, execution_timeout и background_hold_expired. Вызовы моделей, сделанные до ошибки, оплачены. Повтор под новым ключом платит за них снова; под тем же ключом Slen отвечает выполнением таким, каким оно закончилось.

  • Нечитаемый ответ — InvalidResponseException, InvalidResponseError. API ответил 2xx, который нельзя прочитать, поэтому вызов мог пройти. Отправляйте его ещё раз только под тем же ключом.

Движение кредитов, оставшееся без ответа, под своим ключом проходит один раз: отправьте его ещё раз под тем же ключом.

Отказ, прочитанный позже

operations.execute() выбрасывает ошибку, которой ответили на его запрос. Выполнение, которое открыл запрос, закрыто и хранит код. Если прочитать его позже, это уже не ошибка: повтор запроса под его ключом, ожидание и чтение статуса возвращают выполнение, а его статус и error_code говорят, чем оно закончилось.

PHP
$again = $slen->operations()->execute('generate_email', $input, new ExecuteOptions(idempotencyKey: 'order-42:email'));

if ($again->isRejected()) {
    echo 'rejected: ', $again->errorCode, PHP_EOL;
}

isRejected() возвращает true, когда статус — rejected, а errorCode хранит код.

Флоу может отказать и намеренно. Узел Отклонить запрос отвечает выполнением со статусом rejected, а не ошибкой. Поэтому код, который реагирует на отказ, обрабатывает и то и другое: catch и статус того, что вернулось. Статусы выполнения перечисляет статья Быстрый старт.

Что читать дальше