¿Cuál es la mejor manera de crear el modelo de respuesta de error API REST y el sistema de códigos de error?

15

Mi implementación REST devolverá errores en JSON con la siguiente estructura:

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

Sugiero crear un modelo de respuesta especial, donde pueda pasar los valores necesarios para las propiedades (dev_message, message_for_user, some_internal_error_code) y devolverlos. En código sería similar a esto:

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

¿Cómo debería ser este modelo? ¿Debo implementar métodos, por ejemplo, successResponse () donde pasaré solo información de texto y el código será 200 por defecto allí? Estoy atrapado con esto. Y esta es la primera parte de mi pregunta: ¿necesito implementar este modelo? ¿Es esta una buena práctica? Porque por ahora, solo estoy devolviendo matrices directamente desde el código.

La segunda parte es sobre el sistema de código de error. Los códigos de error se describirán en la documentación. Pero el problema que estoy encontrando está en el código. ¿Cuál es la mejor manera de administrar códigos de error? ¿Debo escribirlos dentro del modelo? ¿O sería mejor crear un servicio separado para manejar esto?

ACTUALIZACIÓN 1

He implementado la clase de modelo para la respuesta. Es la respuesta similar de Greg, la misma lógica, pero adicionalmente he codificado los errores escritos en el modelo y así es como se ve:

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

     ...some code...
    }

¿Por qué hice esto? ¿Y para qué?

  1. Se ve bien en el código: return new ErrorResponse(ErrorResponse::SOME_ENTITY_NOT_FOUND );
  2. Mensaje de error fácil de cambiar. Todos los mensajes están en un lugar en lugar de controlador / servicio / etc. o lo que sea que lo haya colocado.

Si tiene alguna sugerencia para mejorar esto, por favor, comente.

Grokking
fuente

Respuestas:

13

En esta situación, siempre pienso primero en la interfaz, luego escribo código PHP para admitirla.

  1. Es una API REST, por lo que los códigos de estado HTTP significativos son imprescindibles.
  2. Desea que se envíen estructuras de datos consistentes y flexibles hacia y desde el cliente.

Pensemos en todas las cosas que podrían salir mal y sus códigos de estado HTTP:

  • El servidor arroja un error (500)
  • Fallo de autenticación (401)
  • No se encontró el recurso solicitado (404)
  • Los datos que está modificando han cambiado desde que los cargó (409)
  • Errores de validación al guardar datos (422)
  • El cliente ha excedido su tasa de solicitud (429)
  • Tipo de archivo no compatible (415)

Tenga en cuenta que hay otros que puede investigar más adelante.

Para la mayoría de las condiciones de falla, solo se devuelve un mensaje de error. La 422 Unprocessable Entityrespuesta, que he usado para "errores de validación" podría devolver más de un error --- Uno o más errores por campo de formulario.

Necesitamos una estructura de datos flexible para respuestas de error.

Tomemos como ejemplo, el 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 eso con simples errores de validación cuando intente enviar algo al 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"
        ]
    }
}

La clave aquí es el tipo de contenido text/json. Esto le dice a las aplicaciones cliente que pueden decodificar el cuerpo de respuesta con un decodificador JSON. Si, por ejemplo, no se detecta un error interno del servidor y se entrega su página web genérica "Algo salió mal", el tipo de contenido debería ser text/html; charset=utf-8para que las aplicaciones cliente no intenten decodificar el cuerpo de respuesta como JSON.

Esto parece todo encontrar y dandy, hasta que necesite admitir respuestas JSONP . Debe devolver una 200 OKrespuesta, incluso para fallas. En este caso, deberá detectar que el cliente solicita una respuesta JSONP (generalmente detectando un parámetro de solicitud de URL llamado callback) y cambiar un poco la estructura de datos:

(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!"
            ]
        }
    }
});

Luego, el controlador de respuestas en el cliente (en un navegador web) debe tener una función global de JavaScript llamada displayBlogPostque acepte un único argumento. Esta función debería determinar si la respuesta fue exitosa:

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

Así que nos hemos ocupado del cliente. Ahora, cuidemos el 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;
    }
}

Y para usar esto en el caso de un error del servidor:

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

O al validar la entrada del usuario:

// 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;

Después de eso, solo necesita algo que tome el objeto de respuesta devuelto y lo convierta en JSON y envíe la respuesta de manera feliz.

Greg Burghardt
fuente
¡Gracias por una respuesta! He implementado una solución similar. La única diferencia es que no paso ningún mensaje por mí mismo, ya están configurados (vea mi pregunta actualizada).
Grokking
-2

Estaba enfrentando algo similar, hice 3 cosas,

  1. Creé un ExceptionHandler para mí llamado ABCException.

Como estoy usando Java y Spring,

Lo definí como

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

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

        }
    }

Luego lo llamé donde sea necesario, así,

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

Y sí, necesita hacer un ExceptionHandler en su controlador si está utilizando un servicio web basado en REST.

Anótelo con @ExceptionHandlersi usa Spring

Dave Ranjan
fuente
Los programadores tratan sobre preguntas conceptuales y se espera que las respuestas expliquen las cosas . Lanzar volcados de código en lugar de una explicación es como copiar el código del IDE a la pizarra: puede parecer familiar e incluso a veces comprensible, pero se siente extraño ... simplemente extraño. Whiteboard no tiene compilador
mosquito