From 64306afa24addef9dbd4838397b1205185d4d6a2 Mon Sep 17 00:00:00 2001 From: Joe Clapis Date: Tue, 7 May 2024 13:38:18 -0400 Subject: [PATCH] Added more error checking to the API server response handler --- api/server/handle-response.go | 74 +++++++++++++++++------------------ api/server/queryless.go | 40 +++++++++++++++---- api/server/single-stage.go | 40 +++++++++++++++---- 3 files changed, 101 insertions(+), 53 deletions(-) diff --git a/api/server/handle-response.go b/api/server/handle-response.go index 33a1906..268f24d 100644 --- a/api/server/handle-response.go +++ b/api/server/handle-response.go @@ -20,108 +20,107 @@ const ( ) // Handle routes called with an invalid method -func HandleInvalidMethod(logger *slog.Logger, w http.ResponseWriter) { - writeResponse(w, logger, http.StatusMethodNotAllowed, "", nil, []byte{}) +func HandleInvalidMethod(logger *slog.Logger, w http.ResponseWriter) error { + return writeResponse(w, logger, http.StatusMethodNotAllowed, "", nil, []byte{}) } // Handles an error related to parsing the input parameters of a request -func HandleInputError(logger *slog.Logger, w http.ResponseWriter, err error) { +func HandleInputError(logger *slog.Logger, w http.ResponseWriter, err error) error { msg := err.Error() - writeResponse(w, logger, http.StatusBadRequest, "", err, formatError(msg)) + return writeResponse(w, logger, http.StatusBadRequest, "", err, formatError(msg)) } // The request couldn't complete because the node requires an address but one wasn't present -func HandleAddressNotPresent(logger *slog.Logger, w http.ResponseWriter, err error) { +func HandleAddressNotPresent(logger *slog.Logger, w http.ResponseWriter, err error) error { msg := fmt.Sprintf(addressNotPresentMessage, err.Error()) - writeResponse(w, logger, http.StatusUnprocessableEntity, "Address not present", err, formatError(msg)) + return writeResponse(w, logger, http.StatusUnprocessableEntity, "Address not present", err, formatError(msg)) } // The request couldn't complete because the node requires a wallet but one isn't present or useable -func HandleWalletNotReady(logger *slog.Logger, w http.ResponseWriter, err error) { +func HandleWalletNotReady(logger *slog.Logger, w http.ResponseWriter, err error) error { msg := fmt.Sprintf(walletNotReadyMessage, err.Error()) - writeResponse(w, logger, http.StatusUnprocessableEntity, "Wallet not ready", err, formatError(msg)) + return writeResponse(w, logger, http.StatusUnprocessableEntity, "Wallet not ready", err, formatError(msg)) } // The request couldn't complete because it's trying to create a resource that already exists, or use a resource that conflicts with what's requested -func HandleResourceConflict(logger *slog.Logger, w http.ResponseWriter, err error) { +func HandleResourceConflict(logger *slog.Logger, w http.ResponseWriter, err error) error { msg := fmt.Sprintf(resourceConflictMessage, err.Error()) - writeResponse(w, logger, http.StatusConflict, "Resource conflict", err, formatError(msg)) + return writeResponse(w, logger, http.StatusConflict, "Resource conflict", err, formatError(msg)) } // The request couldn't complete because it's trying to access a resource that didn't exist or couldn't be found -func HandleResourceNotFound(logger *slog.Logger, w http.ResponseWriter, err error) { +func HandleResourceNotFound(logger *slog.Logger, w http.ResponseWriter, err error) error { msg := fmt.Sprintf(resourceNotFoundMessage, err.Error()) - writeResponse(w, logger, http.StatusNotFound, "Resource not found", err, formatError(msg)) + return writeResponse(w, logger, http.StatusNotFound, "Resource not found", err, formatError(msg)) } // The request couldn't complete because the clients aren't synced yet -func HandleClientNotSynced(logger *slog.Logger, w http.ResponseWriter, err error) { +func HandleClientNotSynced(logger *slog.Logger, w http.ResponseWriter, err error) error { msg := clientsNotSyncedMessage - writeResponse(w, logger, http.StatusUnprocessableEntity, "Clients not synced", err, formatError(msg)) + return writeResponse(w, logger, http.StatusUnprocessableEntity, "Clients not synced", err, formatError(msg)) } // The request couldn't complete because the chain state is preventing the request (it will revert if submitted) -func HandleInvalidChainState(logger *slog.Logger, w http.ResponseWriter, err error) { +func HandleInvalidChainState(logger *slog.Logger, w http.ResponseWriter, err error) error { msg := fmt.Sprintf(invalidChainStateMessage, err.Error()) - writeResponse(w, logger, http.StatusUnprocessableEntity, "Invalid chain state", err, formatError(msg)) + return writeResponse(w, logger, http.StatusUnprocessableEntity, "Invalid chain state", err, formatError(msg)) } // The request couldn't complete because of a server error -func HandleServerError(logger *slog.Logger, w http.ResponseWriter, err error) { +func HandleServerError(logger *slog.Logger, w http.ResponseWriter, err error) error { msg := err.Error() - writeResponse(w, logger, http.StatusInternalServerError, "", err, formatError(msg)) + return writeResponse(w, logger, http.StatusInternalServerError, "", err, formatError(msg)) } // The request completed successfully -func HandleSuccess(logger *slog.Logger, w http.ResponseWriter, response any) { +func HandleSuccess(logger *slog.Logger, w http.ResponseWriter, response any) error { // Serialize the response bytes, err := json.Marshal(response) if err != nil { - HandleServerError(logger, w, fmt.Errorf("error serializing response: %w", err)) - return + return HandleServerError(logger, w, fmt.Errorf("error serializing response: %w", err)) } // Write it logger.Debug("Response body", slog.String(log.BodyKey, string(bytes))) - writeResponse(w, logger, http.StatusOK, "", nil, bytes) + return writeResponse(w, logger, http.StatusOK, "", nil, bytes) } // Handles an API response for a request that could not be completed -func HandleFailedResponse(logger *slog.Logger, w http.ResponseWriter, status types.ResponseStatus, err error) { +func HandleFailedResponse(logger *slog.Logger, w http.ResponseWriter, status types.ResponseStatus, err error) error { switch status { case types.ResponseStatus_InvalidArguments: - HandleInputError(logger, w, err) + return HandleInputError(logger, w, err) case types.ResponseStatus_AddressNotPresent: - HandleAddressNotPresent(logger, w, err) + return HandleAddressNotPresent(logger, w, err) case types.ResponseStatus_WalletNotReady: - HandleWalletNotReady(logger, w, err) + return HandleWalletNotReady(logger, w, err) case types.ResponseStatus_ResourceConflict: - HandleResourceConflict(logger, w, err) + return HandleResourceConflict(logger, w, err) case types.ResponseStatus_ResourceNotFound: - HandleResourceNotFound(logger, w, err) + return HandleResourceNotFound(logger, w, err) case types.ResponseStatus_ClientsNotSynced: - HandleClientNotSynced(logger, w, err) + return HandleClientNotSynced(logger, w, err) case types.ResponseStatus_InvalidChainState: - HandleInvalidChainState(logger, w, err) + return HandleInvalidChainState(logger, w, err) case types.ResponseStatus_Error: - HandleServerError(logger, w, err) + return HandleServerError(logger, w, err) default: - HandleServerError(logger, w, fmt.Errorf("unknown response status: %d", status)) + return HandleServerError(logger, w, fmt.Errorf("unknown response status: %d", status)) } } // Handles an API response -func HandleResponse(logger *slog.Logger, w http.ResponseWriter, status types.ResponseStatus, response any, err error) { +func HandleResponse(logger *slog.Logger, w http.ResponseWriter, status types.ResponseStatus, response any, err error) error { switch status { case types.ResponseStatus_Success: - HandleSuccess(logger, w, response) + return HandleSuccess(logger, w, response) default: - HandleFailedResponse(logger, w, status, err) + return HandleFailedResponse(logger, w, status, err) } } // Writes a response to an HTTP request back to the client and logs it -func writeResponse(w http.ResponseWriter, logger *slog.Logger, statusCode int, cause string, err error, message []byte) { +func writeResponse(w http.ResponseWriter, logger *slog.Logger, statusCode int, cause string, err error, message []byte) error { // Prep the log attributes codeMsg := fmt.Sprintf("%d %s", statusCode, http.StatusText(statusCode)) attrs := []any{ @@ -148,7 +147,8 @@ func writeResponse(w http.ResponseWriter, logger *slog.Logger, statusCode int, c // Write it to the client w.Header().Add("Content-Type", "application/json") w.WriteHeader(statusCode) - w.Write(message) + _, writeErr := w.Write(message) + return writeErr } // JSONifies an error for responding to requests diff --git a/api/server/queryless.go b/api/server/queryless.go index c6cef56..167f880 100644 --- a/api/server/queryless.go +++ b/api/server/queryless.go @@ -58,20 +58,29 @@ func RegisterQuerylessGet[ContextType IQuerylessCallContext[DataType], DataType // Check the method if r.Method != http.MethodGet { - HandleInvalidMethod(logger, w) + err := HandleInvalidMethod(logger, w) + if err != nil { + logger.Error("Error handling response", log.Err(err)) + } return } // Create the handler and deal with any input validation errors context, err := factory.Create(args) if err != nil { - HandleInputError(logger, w, err) + err = HandleInputError(logger, w, err) + if err != nil { + logger.Error("Error handling response", log.Err(err)) + } return } // Run the context's processing routine status, response, err := runQuerylessRoute[DataType](context, serviceProvider) - HandleResponse(logger, w, status, response, err) + err = HandleResponse(logger, w, status, response, err) + if err != nil { + logger.Error("Error handling response", log.Err(err)) + } }) } @@ -90,14 +99,20 @@ func RegisterQuerylessPost[ContextType IQuerylessCallContext[DataType], BodyType // Check the method if r.Method != http.MethodPost { - HandleInvalidMethod(logger, w) + err := HandleInvalidMethod(logger, w) + if err != nil { + logger.Error("Error handling response", log.Err(err)) + } return } // Read the body bodyBytes, err := io.ReadAll(r.Body) if err != nil { - HandleInputError(logger, w, fmt.Errorf("error reading request body: %w", err)) + err = HandleInputError(logger, w, fmt.Errorf("error reading request body: %w", err)) + if err != nil { + logger.Error("Error handling response", log.Err(err)) + } return } logger.Debug("Request body:", slog.String(log.BodyKey, string(bodyBytes))) @@ -106,20 +121,29 @@ func RegisterQuerylessPost[ContextType IQuerylessCallContext[DataType], BodyType var body BodyType err = json.Unmarshal(bodyBytes, &body) if err != nil { - HandleInputError(logger, w, fmt.Errorf("error deserializing request body: %w", err)) + err = HandleInputError(logger, w, fmt.Errorf("error deserializing request body: %w", err)) + if err != nil { + logger.Error("Error handling response", log.Err(err)) + } return } // Create the handler and deal with any input validation errors context, err := factory.Create(body) if err != nil { - HandleInputError(logger, w, err) + err = HandleInputError(logger, w, err) + if err != nil { + logger.Error("Error handling response", log.Err(err)) + } return } // Run the context's processing routine status, response, err := runQuerylessRoute[DataType](context, serviceProvider) - HandleResponse(logger, w, status, response, err) + err = HandleResponse(logger, w, status, response, err) + if err != nil { + logger.Error("Error handling response", log.Err(err)) + } }) } diff --git a/api/server/single-stage.go b/api/server/single-stage.go index e27c7d4..8738bd7 100644 --- a/api/server/single-stage.go +++ b/api/server/single-stage.go @@ -62,20 +62,29 @@ func RegisterSingleStageRoute[ContextType ISingleStageCallContext[DataType], Dat // Check the method if r.Method != http.MethodGet { - HandleInvalidMethod(logger, w) + err := HandleInvalidMethod(logger, w) + if err != nil { + logger.Error("Error handling response", log.Err(err)) + } return } // Create the handler and deal with any input validation errors context, err := factory.Create(args) if err != nil { - HandleInputError(logger, w, err) + err = HandleInputError(logger, w, err) + if err != nil { + logger.Error("Error handling response", log.Err(err)) + } return } // Run the context's processing routine status, response, err := runSingleStageRoute[DataType](context, serviceProvider) - HandleResponse(logger, w, status, response, err) + err = HandleResponse(logger, w, status, response, err) + if err != nil { + logger.Error("Error handling response", log.Err(err)) + } }) } @@ -94,14 +103,20 @@ func RegisterSingleStagePost[ContextType ISingleStageCallContext[DataType], Body // Check the method if r.Method != http.MethodPost { - HandleInvalidMethod(logger, w) + err := HandleInvalidMethod(logger, w) + if err != nil { + logger.Error("Error handling response", log.Err(err)) + } return } // Read the body bodyBytes, err := io.ReadAll(r.Body) if err != nil { - HandleInputError(logger, w, fmt.Errorf("error reading request body: %w", err)) + err = HandleInputError(logger, w, fmt.Errorf("error reading request body: %w", err)) + if err != nil { + logger.Error("Error handling response", log.Err(err)) + } return } logger.Debug("Body", slog.String(log.BodyKey, string(bodyBytes))) @@ -110,20 +125,29 @@ func RegisterSingleStagePost[ContextType ISingleStageCallContext[DataType], Body var body BodyType err = json.Unmarshal(bodyBytes, &body) if err != nil { - HandleInputError(logger, w, fmt.Errorf("error deserializing request body: %w", err)) + err = HandleInputError(logger, w, fmt.Errorf("error deserializing request body: %w", err)) + if err != nil { + logger.Error("Error handling response", log.Err(err)) + } return } // Create the handler and deal with any input validation errors context, err := factory.Create(body) if err != nil { - HandleInputError(logger, w, err) + err = HandleInputError(logger, w, err) + if err != nil { + logger.Error("Error handling response", log.Err(err)) + } return } // Run the context's processing routine status, response, err := runSingleStageRoute[DataType](context, serviceProvider) - HandleResponse(logger, w, status, response, err) + err = HandleResponse(logger, w, status, response, err) + if err != nil { + logger.Error("Error handling response", log.Err(err)) + } }) }