Qual é a melhor maneira de criar o modelo de resposta a erros da API REST e o sistema de códigos de erro?

15

Minha implementação REST retornará erros no JSON com a próxima estrutura:

{
 "http_response":400,
 "dev_message":"There is a problem",
 "message_for_user":"Bad request",
 "some_internal_error_code":12345
}

Sugiro criar um modelo de resposta especial, onde eu possa passar os valores necessários para as propriedades (dev_message, message_for_user, some_internal_error_code) e retorná-los. No código, seria semelhante a este:

$responseModel = new MyResponseModel(400,"Something is bad", etc...);

Como esse modelo deve ser? Devo implementar métodos, por exemplo, successResponse (), onde passarei apenas informações de texto e o código será o padrão 200 lá? Eu estou preso com isso. E esta é a primeira parte da minha pergunta: preciso implementar esse modelo, é uma boa prática? Porque, por enquanto, estou retornando matrizes diretamente do código.

A segunda parte é sobre o sistema de código de erro. Os códigos de erro serão descritos na documentação. Mas o problema que estou encontrando está no código. Qual é a melhor maneira de gerenciar códigos de erro? Devo escrevê-los dentro do modelo? Ou seria melhor criar um serviço separado para lidar com isso?

ATUALIZAÇÃO 1

Eu implementei classe de modelo para resposta. É a resposta de Greg semelhante, a mesma lógica, mas adicionalmente eu codifiquei erros escritos no modelo e aqui está como ele se parece:

    class ErrorResponse
    {
     const SOME_ENTITY_NOT_FOUND = 100;
     protected $errorMessages = [100 => ["error_message" => "That entity doesn't exist!"]];

     ...some code...
    }

Por que eu fiz isso? E para que?

  1. Parece legal no código: return new ErrorResponse(ErrorResponse::SOME_ENTITY_NOT_FOUND );
  2. Fácil de mudar a mensagem de erro. Todas as mensagens estão em um só lugar, em vez de controller / service / etc ou o que você colocar.

Se você tem alguma sugestão para melhorar isso, por favor, comente.

Grokking
fonte

Respostas:

13

Nessa situação, eu sempre penso na interface primeiro e depois escrevo o código PHP para suportá-la.

  1. É uma API REST, portanto, códigos de status HTTP significativos são uma obrigação.
  2. Você deseja que estruturas de dados flexíveis e consistentes sejam enviadas para e a partir do cliente.

Vamos pensar em todas as coisas que podem dar errado e em seus códigos de status HTTP:

  • O servidor gera um erro (500)
  • Falha na autenticação (401)
  • O recurso solicitado não foi encontrado (404)
  • Os dados que você está modificando foram alterados desde que você o carregou (409)
  • Erros de validação ao salvar dados (422)
  • O cliente excedeu a taxa de solicitações (429)
  • Tipo de arquivo não suportado (415)

Observe que existem outros que você pode pesquisar mais tarde.

Para a maioria das condições de falha, há apenas uma mensagem de erro a ser retornada. o422 Unprocessable Entity resposta que eu usei para "erros de validação" pode retornar mais de um erro --- Um ou mais erros por campo de formulário.

Precisamos de uma estrutura de dados flexível para respostas a erros.

Tome como exemplo, o 500 Internal Server Error:

HTTP/1.1 500 Internal Server Error
Content-Type: text/json
Date: Fri, 16 Jan 2015 17:44:25 GMT
... other headers omitted ...

{
    "errors": {
        "general": [
            "Something went catastrophically wrong on the server! BWOOP! BWOOP! BWOOP!"
        ]
    }
}

Compare isso com erros simples de validação ao tentar postar algo no servidor:

HTTP/1.1 422 Unprocessable Entity
Content-Type: text/json
Date: Fri, 16 Jan 2015 17:44:25 GMT
... other headers omitted ...

{
    "errors": {
        "first_name": [
            "is required"
        ],
        "telephone": [
            "should not exceed 12 characters",
            "is not in the correct format"
        ]
    }
}

A chave aqui é o tipo de conteúdo text/json. Isso informa aos aplicativos clientes que eles podem decodificar o corpo da resposta com um decodificador JSON. Se, por exemplo, um erro interno do servidor não for detectado e a página da Web genérica "Algo deu errado" for entregue, o tipo de conteúdo deverá ser text/html; charset=utf-8para que os aplicativos clientes não tentem decodificar o corpo da resposta como JSON.

Parece tudo achado e elegante, até que você precise suportar respostas JSONP . Você deve retornar uma 200 OKresposta, mesmo para falhas. Nesse caso, você precisará detectar que o cliente está solicitando uma resposta JSONP (geralmente detectando um parâmetro de solicitação de URL chamadocallback ) e alterar um pouco a estrutura de dados:

(GET / posts / 123? Callback = displayBlogPost)

<script type="text/javascript" src="/posts/123?callback=displayBlogPost"></script>

HTTP/1.1 200 OK
Content-Type: text/javascript
Date: Fri, 16 Jan 2015 17:44:25 GMT
... other headers omitted ...

displayBlogPost({
    "status": 500,
    "data": {
        "errors": {
            "general": [
                "Something went catastrophically wrong on the server! BWOOP! BWOOP! BWOOP!"
            ]
        }
    }
});

Em seguida, o manipulador de respostas no cliente (em um navegador da web) deve ter uma função JavaScript global chamada displayBlogPostque aceita um único argumento. Esta função precisaria determinar se a resposta foi bem-sucedida:

function displayBlogPost(response) {
    if (response.status == 500) {
        alert(response.data.errors.general[0]);
    }
}

Então, nós cuidamos do cliente. Agora, vamos cuidar do servidor.

<?php

class ResponseError
{
    const STATUS_INTERNAL_SERVER_ERROR = 500;
    const STATUS_UNPROCESSABLE_ENTITY = 422;

    private $status;
    private $messages;

    public function ResponseError($status, $message = null)
    {
        $this->status = $status;

        if (isset($message)) {
            $this->messages = array(
                'general' => array($message)
            );
        } else {
            $this->messages = array();
        }
    }

    public function addMessage($key, $message)
    {
        if (!isset($message)) {
            $message = $key;
            $key = 'general';
        }

        if (!isset($this->messages[$key])) {
            $this->messages[$key] = array();
        }

        $this->messages[$key][] = $message;
    }

    public function getMessages()
    {
        return $this->messages;
    }

    public function getStatus()
    {
        return $this->status;
    }
}

E para usar isso no caso de um erro do servidor:

try {
    // some code that throws an exception
}
catch (Exception $ex) {
    return new ResponseError(ResponseError::STATUS_INTERNAL_SERVER_ERROR, $ex->message);
}

Ou ao validar a entrada do usuário:

// Validate some input from the user, and it is invalid:

$response = new ResponseError(ResponseError::STATUS_UNPROCESSABLE_ENTITY);
$response->addMessage('first_name', 'is required');
$response->addMessage('telephone', 'should not exceed 12 characters');
$response->addMessage('telephone', 'is not in the correct format');

return $response;

Depois disso, você só precisa de algo que pegue o objeto de resposta retornado e o converta em JSON e envie a resposta de maneira alegre.

Greg Burghardt
fonte
Obrigado por uma resposta! Eu implementei solução semelhante. A única diferença de que eu não passo nenhuma mensagem sozinha, elas já estão definidas (consulte minha pergunta atualizada).
Grokking
-2

Eu estava enfrentando algo parecido, eu fiz 3 coisas,

  1. Criei um ExceptionHandler para mim chamado ABCException.

Desde que eu estou usando Java e Spring,

Eu defini como

 public class ABCException extends Exception {
private String errorMessage;
private HttpStatus statusCode;

    public ABCException(String errorMessage,HttpStatus statusCode){
            super(errorMessage);
            this.statusCode = statusCode;

        }
    }

Em seguida, chame-o sempre que necessário, como este,

throw new ABCException("Invalid User",HttpStatus.CONFLICT);

E sim, você precisa criar um ExceptionHandler no seu controlador se estiver usando o serviço da Web baseado em REST.

Faça anotações se @ExceptionHandlerestiver usando o Spring

Dave Ranjan
fonte
Programadores trata de perguntas conceituais e espera- se que as respostas expliquem as coisas . Jogar dumps de código em vez de explicação é como copiar código do IDE para o quadro branco: pode parecer familiar e até às vezes compreensível, mas parece estranho ... apenas estranho. O quadro branco não tem compilador
gnat