Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 40 additions & 67 deletions response/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ import (

// APIError represents an api error response.
type APIError struct {
StatusCode int `json:"status_code"` // HTTP status code
Type string `json:"type"` // Type of error
Message string `json:"message"` // Human-readable message
Detail string `json:"detail,omitempty"` // Detailed error message
Errors map[string]interface{} `json:"errors,omitempty"` // Additional error details
Raw string `json:"raw"` // Raw response body for debugging
StatusCode int `json:"status_code"` // HTTP status code
Method string `json:"method"` // HTTP method used for the request
URL string `json:"url"` // The URL of the HTTP request
Message string `json:"message"` // Summary of the error
Details []string `json:"details"` // Detailed error messages, if any
RawResponse string `json:"raw_response"` // Raw response body for debugging
}

// Error returns a string representation of the APIError, making it compatible with the error interface.
Expand All @@ -39,21 +39,22 @@ func (e *APIError) Error() string {
}

// Fallback to a simpler error message format if JSON marshaling fails.
return fmt.Sprintf("API Error: StatusCode=%d, Type=%s, Message=%s", e.StatusCode, e.Type, e.Message)
return fmt.Sprintf("API Error: StatusCode=%d, Message=%s", e.StatusCode, e.Message)
}

// HandleAPIErrorResponse handles the HTTP error response from an API and logs the error.
func HandleAPIErrorResponse(resp *http.Response, log logger.Logger) *APIError {
apiError := &APIError{
StatusCode: resp.StatusCode,
Type: "API Error Response",
Message: "An error occurred",
Method: resp.Request.Method,
URL: resp.Request.URL.String(),
Message: "API Error Response",
}

bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
apiError.Raw = "Failed to read response body"
log.LogError("error_reading_response_body", resp.Request.Method, resp.Request.URL.String(), apiError.StatusCode, resp.Status, err, apiError.Raw)
apiError.RawResponse = "Failed to read response body"
log.LogError("error_reading_response_body", resp.Request.Method, resp.Request.URL.String(), apiError.StatusCode, resp.Status, err, apiError.RawResponse)
return apiError
}

Expand All @@ -68,9 +69,9 @@ func HandleAPIErrorResponse(resp *http.Response, log logger.Logger) *APIError {
case "text/plain":
parseTextResponse(bodyBytes, apiError, log, resp)
default:
apiError.Raw = string(bodyBytes)
apiError.RawResponse = string(bodyBytes)
apiError.Message = "Unknown content type error"
log.LogError("unknown_content_type_error", resp.Request.Method, resp.Request.URL.String(), apiError.StatusCode, "Unknown content type", nil, apiError.Raw)
log.LogError("unknown_content_type_error", resp.Request.Method, resp.Request.URL.String(), apiError.StatusCode, "Unknown content type", nil, apiError.RawResponse)
}

return apiError
Expand All @@ -93,28 +94,26 @@ func ParseContentTypeHeader(header string) (string, map[string]string) {
// parseJSONResponse attempts to parse the JSON error response and update the APIError structure.
func parseJSONResponse(bodyBytes []byte, apiError *APIError, log logger.Logger, resp *http.Response) {
if err := json.Unmarshal(bodyBytes, apiError); err != nil {
apiError.Raw = string(bodyBytes)
logError(log, apiError, "json_parsing_error", resp.Request.Method, resp.Request.URL.String(), resp.Status, err)
apiError.RawResponse = string(bodyBytes)
log.LogError("json_parsing_error", resp.Request.Method, resp.Request.URL.String(), apiError.StatusCode, resp.Status, err, apiError.RawResponse)
} else {
if apiError.Message == "" {
apiError.Message = "An unknown error occurred"
}

// Log the detected JSON error with all the context information.
logError(log, apiError, "json_error_detected", resp.Request.Method, resp.Request.URL.String(), resp.Status, nil)
log.LogError("json_error_detected", resp.Request.Method, resp.Request.URL.String(), apiError.StatusCode, resp.Status, err, apiError.RawResponse)
}
}

// parseXMLResponse dynamically parses XML error responses and accumulates potential error messages.
func parseXMLResponse(bodyBytes []byte, apiError *APIError, log logger.Logger, resp *http.Response) {
// Always set the Raw field to the entire XML content for debugging purposes.
apiError.Raw = string(bodyBytes)
apiError.RawResponse = string(bodyBytes)

// Parse the XML document.
doc, err := xmlquery.Parse(bytes.NewReader(bodyBytes))
if err != nil {
// Log the XML parsing error with all the context information.
logError(log, apiError, "xml_parsing_error", resp.Request.Method, resp.Request.URL.String(), resp.Status, err)
log.LogError("xml_parsing_error", resp.Request.Method, resp.Request.URL.String(), apiError.StatusCode, resp.Status, err, apiError.RawResponse)
return
}

Expand All @@ -141,37 +140,41 @@ func parseXMLResponse(bodyBytes []byte, apiError *APIError, log logger.Logger, r
// Determine the error to log based on whether a message was found.
var logErr error
if apiError.Message == "" {
logErr = fmt.Errorf("No error message extracted from XML")
logErr = fmt.Errorf("no error message extracted from XML")
}

// Log the error or the lack of extracted messages using the centralized logger.
logError(log, apiError, "xml_error_detected", resp.Request.Method, resp.Request.URL.String(), resp.Status, logErr)
log.LogError("html_parsing_error", resp.Request.Method, resp.Request.URL.String(), apiError.StatusCode, resp.Status, logErr, apiError.RawResponse)
}

// parseTextResponse updates the APIError structure based on a plain text error response and logs it.
func parseTextResponse(bodyBytes []byte, apiError *APIError, log logger.Logger, resp *http.Response) {
// Convert the body bytes to a string and assign it to both the message and RawResponse fields of APIError.
bodyText := string(bodyBytes)
apiError.Raw = bodyText

// Check if the 'Message' field of APIError is empty and use the body text as the message.
if apiError.Message == "" {
apiError.Message = bodyText
}

// Use the updated logError function with the additional parameters.
logError(log, apiError, "text_error_detected", resp.Request.Method, resp.Request.URL.String(), resp.Status, nil)
apiError.RawResponse = bodyText

// Directly use the body text as the error message if the Message field is empty.
apiError.Message = bodyText

log.LogError(
"text_error_detected", // Event
resp.Request.Method, // HTTP method
resp.Request.URL.String(), // Request URL
apiError.StatusCode, // HTTP status code
resp.Status, // HTTP status message from the response
nil, // Error (nil because text parsing is unlikely to fail)
apiError.RawResponse, // Raw response text
)
}

// parseHTMLResponse extracts meaningful information from an HTML error response and concatenates all text within <p> tags.
func parseHTMLResponse(bodyBytes []byte, apiError *APIError, log logger.Logger, resp *http.Response) {
// Always set the Raw field to the entire HTML content for debugging purposes.
apiError.Raw = string(bodyBytes)
apiError.RawResponse = string(bodyBytes)

reader := bytes.NewReader(bodyBytes)
doc, err := html.Parse(reader)
if err != nil {
// Log HTML parsing error using centralized logger with context.
logError(log, apiError, "html_parsing_error", resp.Request.Method, resp.Request.URL.String(), resp.Status, err)
log.LogError("html_parsing_error", resp.Request.Method, resp.Request.URL.String(), apiError.StatusCode, resp.Status, err, apiError.RawResponse)
return
}

Expand Down Expand Up @@ -212,40 +215,10 @@ func parseHTMLResponse(bodyBytes []byte, apiError *APIError, log logger.Logger,
// Determine the error to log based on whether a message was found.
var logErr error
if apiError.Message == "" {
logErr = fmt.Errorf("No error message extracted from HTML")
logErr = fmt.Errorf("no error message extracted from HTML")
}

// Log the extracted error message or the fallback message using the centralized logger.
logError(log, apiError, "html_error_detected", resp.Request.Method, resp.Request.URL.String(), resp.Status, logErr)
}

// logError logs the error details using the provided logger instance.
// func logError(log logger.Logger, apiError *APIError, event string, resp *http.Response) {
// // Prepare the error message. If apiError.Message is empty, use a default message.
// errorMessage := apiError.Message
// if errorMessage == "" {
// errorMessage = "An unspecified error occurred"
// }

// // Use LogError method from the logger package for error logging.
// log.LogError(
// event,
// resp.Request.Method,
// resp.Request.URL.String(),
// apiError.StatusCode,
// resp.Status,
// fmt.Errorf(errorMessage),
// apiError.Raw,
// )
// }

func logError(log logger.Logger, apiError *APIError, event, method, url, statusMessage string, err error) {
// Prepare the error message. If apiError.Message is empty, use a default message.
errorMessage := apiError.Message
if errorMessage == "" {
errorMessage = "An unspecified error occurred"
}
log.LogError("html_error_detected", resp.Request.Method, resp.Request.URL.String(), apiError.StatusCode, resp.Status, logErr, apiError.RawResponse)

// Call the LogError method from the logger package for error logging.
log.LogError(event, method, url, apiError.StatusCode, statusMessage, err, apiError.Raw)
}