Erros
Todas as falhas lançam uma exceção. Uma classe para uma recusa, outra para a falta de resposta, e um id de pedido em cada erro da API.
Apanhar um
use OpenEmail\Exception\ApiException;use OpenEmail\Exception\NetworkException; $message = [ 'from' => '[email protected]', 'to' => '[email protected]', 'subject' => 'Your September invoice', 'text' => 'Invoice attached.',]; try { $client->emails->send($message);} catch (ApiException $error) { if ($error->isValidation()) { error_log($error->errorCode . ' ' . $error->param . ' ' . $error->getMessage()); } if ($error->isPermission()) { $book = $client->addresses->listAll(); error_log('this key may send as ' . implode(', ', array_column($book->addresses, 'address'))); } if ($error->isRateLimited()) { error_log('try again in ' . $error->retryAfterSeconds . ' seconds'); } error_log($error->status . ' ' . $error->requestId); throw $error;} catch (NetworkException $error) { if ($error->isTimeout()) { error_log('no answer in time'); } throw $error;}Um permission_error num envio é normalmente o âmbito de envio da CHAVE, um domínio ou um endereço que não lhe foi concedido, e não o espaço de trabalho, e é por isso que o exemplo regista o que addresses->listAll() diz que esta chave pode usar para enviar.
Cada tipo de recusa tem a sua própria subclasse, por isso um catch pode escolher pela classe as que trata e deixar as restantes subir.
use OpenEmail\Exception\AuthenticationException;use OpenEmail\Exception\OpenEmailException;use OpenEmail\Exception\PermissionException;use OpenEmail\Exception\ValidationException; $message = [ 'from' => '[email protected]', 'to' => '[email protected]', 'subject' => 'Your September invoice', 'text' => 'Invoice attached.',]; try { $client->emails->send($message);} catch (ValidationException $error) { error_log($error->param . ': ' . $error->getMessage());} catch (AuthenticationException|PermissionException $error) { error_log('the key cannot do this: ' . $error->errorCode); throw $error;} catch (OpenEmailException $error) { error_log($error::class . ': ' . $error->getMessage()); throw $error;}As classes
Todas as classes ficam em OpenEmail\Exception.
| Classe | Quando |
|---|---|
| OpenEmailException | A interface que todas as exceções que o pacote lança implementam, por isso catch (OpenEmailException $error) apanha-as todas, incluindo InvalidArgumentException. |
| ApiException | A API respondeu, e não com um sucesso. Traz status, type, errorCode, param, docUrl, requestId, retryAfterSeconds, fields e body. É lançado tal como está quando type é api_error, como acontece com uma falha do servidor, e como a subclasse do seu type nos restantes casos. Estende RuntimeException. |
| InvalidRequestException, AuthenticationException, PermissionException, NotFoundException, ConflictException, ValidationException e RateLimitException | Subclasses de ApiException, uma para cada type: invalid_request_error, authentication_error, permission_error, not_found_error, conflict_error, validation_error e rate_limit_error. |
| NetworkException | Não chegou resposta: DNS, TLS, uma ligação recusada ou caída, ou o timeout. getPrevious() guarda a exceção subjacente, e isTimeout() é true quando o timeout foi a razão. Estende RuntimeException. |
| WebhookSignatureException | OpenEmail::verifyWebhookSignature() recusou uma entrega. Estende UnexpectedValueException. |
| InvalidArgumentException | Lançado antes de qualquer envio: uma chave em falta ou mal formada, um baseUrl: inutilizável, um id vazio. Estende a própria InvalidArgumentException do PHP, porque significa que a própria chamada está errada. |
O que um ApiException transporta
getMessage()string- A frase da própria API, escrita para uma pessoa e que indica o valor problemático quando existe. Não é um identificador estável, por isso baseie a lógica em `errorCode`.
statusint or null- O estado HTTP da resposta, que `getCode()` também devolve. É null apenas quando um sucesso chegou num formato que o cliente não conseguiu ler.
typestring- Um dos oito valores de `OpenEmail\Constants\ErrorTypes`, um conjunto fixo que não vai crescer. Quando o corpo não indica nenhum, é inferido a partir do estado.
errorCodestring- A falha concreta, como `from_address_forbidden` ou `invalid_email_address`. Chama-se `errorCode` porque o PHP reserva `code` para o número que `getCode()` devolve. É aberto e aditivo, por isso trate um que não reconheça como o seu `type`. É `unrecognised_response` quando o corpo não era o envelope de erro da API.
paramstring or null- O campo recusado, como caminho com pontos, por exemplo `to.0`, quando a falha indica um.
docUrlstring or null- Uma página sobre esta falha, quando a API indica uma.
requestIdstring or null- O id com que o servidor registou o pedido, vindo do corpo ou do cabeçalho `x-request-id`.
retryAfterSecondsint, float or null- A espera que o servidor pediu em `Retry-After`, em segundos, quer tenha enviado um número ou uma data. null quando não enviou nenhuma.
fieldsarray or null- Um array por problema, cada um com `key` e `error`, como `['key' => 'email', 'error' => 'email']` quando `forms->subscribe` recusou as respostas com um 422 `invalid_form_submission`. null quando o erro não lista nenhum.
bodymixed- A resposta de erro completa, descodificada. null quando estava vazia ou não era JSON.
| Método | True quando |
|---|---|
| isAuth() | type é authentication_error, um 401: sem chave, o tipo errado de credencial, ou uma chave que não emitimos. |
| isPermission() | permission_error, um 403: uma chave real sem o âmbito ou o endereço From de que precisa. |
| isScopeMissing() | errorCode é insufficient_scope, o 403 que nomeia um âmbito em falta. |
| isInvalidRequest() | invalid_request_error, um 400: um pedido que não pôde ser compreendido. Uma mensagem acima do limite de tamanho volta como um 422 message_too_large, por isso isValidation() é o método que a apanha. |
| isValidation() | validation_error, um 422: o esquema recusou-o, e param nomeia o campo. |
| isNotFound() | not_found_error, um 404: não existe tal recurso. |
| isConflict() | conflict_error, um 409: o recurso já passou o ponto em que isto lhe podia ser feito. |
| isRateLimited() | rate_limit_error, um 429. retryAfterSeconds contém a espera quando o servidor indicou uma. |
| isServerError() | status é 500 ou superior. Cite requestId se contactar o suporte. |
| isRetryable() | status é 408, 429, 500, 502, 503 ou 504. |
| isStepUpRequired() | errorCode é step_up_required, o 403 que um token de acesso OAuth recebe antes de uma alteração sensível até a pessoa ter verificado um código. |
A maioria destes lê type, a metade fixa do envelope, e cada subclasse representa um type. errorCode continua a ser uma string, porque a API garante que é aberto e aditivo, por isso trate um que não reconheça como o seu type. Uma lista fechada faria de uma atualização do pacote o preço de ler um novo modo de falha.
Um corpo que não seja o envelope de erro da API lança na mesma um ApiException, com type inferido a partir do estado e errorCode definido como unrecognised_response. Um sucesso cujo corpo não seja JSON lança um também.
isRetryable() descreve o estado, não a sua chamada. Uma chamada que é seguro repetir já foi repetida quando lança a exceção, e um 429 como send_quota_exceeded ou ai_quota_exceeded falha da mesma forma até a sua quota ser reposta, por isso mostre-o a uma pessoa em vez de o repetir em ciclo.
Uma exceção que o seu cliente HTTP lance torna-se um NetworkException depois de esgotadas as repetições que a chamada permite, com a original como getPrevious(). Um LogicException e qualquer Error, como um TypeError, significam um bug no cliente, por isso são lançados sem alterações e nunca são repetidos. O Psr18HttpClient guarda apenas a mensagem de uma exceção do cliente que envolve, porque essa exceção contém o pedido e o seu cabeçalho Authorization.
requestId
Todos os ApiException transportam o id de pedido que o servidor enviou, vindo do corpo do erro ou do cabeçalho x-request-id, e é a única coisa que liga a sua falha a uma linha no log do servidor. Um sucesso devolve apenas o corpo descodificado, por isso não há nele nenhum id de pedido para ler.