Skip to content

Commit 6e99576

Browse files
authored
Merge pull request #99 from deploymenttheory/dev
Add error handling for non-JSON and HTML responses
2 parents 22a260e + f410d50 commit 6e99576

File tree

1 file changed

+50
-16
lines changed

1 file changed

+50
-16
lines changed

httpclient/httpclient_error_response.go

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"fmt"
88
"io"
99
"net/http"
10+
"strings"
1011

1112
"github.com/deploymenttheory/go-api-http-client/logger"
1213
)
@@ -44,37 +45,70 @@ func handleAPIErrorResponse(resp *http.Response, log logger.Logger) *APIError {
4445

4546
bodyBytes, err := io.ReadAll(resp.Body)
4647
if err != nil {
47-
// Log and return an error if reading the body fails
48-
log.LogError("api_response_read_error", "READ", resp.Request.URL.String(), resp.StatusCode, err, "")
48+
log.LogError("api_response_read_failure", resp.Request.Method, resp.Request.URL.String(), resp.StatusCode, err, "Failed to read the API error response body, which might contain further details about the error.")
4949
return apiError
5050
}
5151

52-
if err := json.Unmarshal(bodyBytes, &apiError); err == nil && apiError.Message != "" {
53-
// Log the structured error
54-
log.LogError("api_structured_error", "API", resp.Request.URL.String(), resp.StatusCode, fmt.Errorf(apiError.Message), "")
55-
return apiError
56-
}
52+
// Check if the response is JSON
53+
if isJSONResponse(resp) {
54+
// Attempt to parse the response into a StructuredError
55+
if err := json.Unmarshal(bodyBytes, &apiError); err == nil && apiError.Message != "" {
56+
log.LogError("api_structured_error_detected", resp.Request.Method, resp.Request.URL.String(), resp.StatusCode, fmt.Errorf(apiError.Message), "")
57+
return apiError
58+
}
5759

58-
// If structured parsing fails, attempt to parse into a generic error map
59-
var genericErr map[string]interface{}
60-
if err := json.Unmarshal(bodyBytes, &genericErr); err == nil {
61-
apiError.updateFromGenericError(genericErr)
62-
// Log the error with extracted details
63-
log.LogError("api_generic_error", "API", resp.Request.URL.String(), resp.StatusCode, fmt.Errorf(apiError.Message), "")
60+
// If structured parsing fails, attempt to parse into a generic error map
61+
var genericErr map[string]interface{}
62+
if err := json.Unmarshal(bodyBytes, &genericErr); err == nil {
63+
apiError.updateFromGenericError(genericErr)
64+
log.LogError("api_generic_error_detected", resp.Request.Method, resp.Request.URL.String(), resp.StatusCode, fmt.Errorf(apiError.Message), "")
65+
return apiError
66+
}
67+
} else if isHTMLResponse(resp) {
68+
// Handle HTML response
69+
apiError.Raw = string(bodyBytes)
70+
apiError.Message = "HTML error page received"
71+
log.LogError("api_html_error", resp.Request.Method, resp.Request.URL.String(), resp.StatusCode, fmt.Errorf("HTML error page received"), "")
72+
return apiError
73+
} else {
74+
// Handle other non-JSON responses
75+
apiError.Raw = string(bodyBytes)
76+
apiError.Message = "Non-JSON error response received"
77+
log.LogError("api_non_json_error", resp.Request.Method, resp.Request.URL.String(), resp.StatusCode, fmt.Errorf("Non-JSON error response received"), "")
6478
return apiError
6579
}
6680

67-
// If all parsing attempts fail, log the raw response
68-
log.LogError("api_unexpected_error", "API", resp.Request.URL.String(), resp.StatusCode, fmt.Errorf("failed to parse API error response"), string(bodyBytes))
6981
return apiError
7082
}
7183

84+
// isJSONResponse checks if the response Content-Type indicates JSON
85+
func isJSONResponse(resp *http.Response) bool {
86+
contentType := resp.Header.Get("Content-Type")
87+
return strings.Contains(contentType, "application/json")
88+
}
89+
90+
// isHTMLResponse checks if the response Content-Type indicates HTML
91+
func isHTMLResponse(resp *http.Response) bool {
92+
contentType := resp.Header.Get("Content-Type")
93+
return strings.Contains(contentType, "text/html")
94+
}
95+
96+
// updateFromGenericError updates the APIError fields based on a generic error map extracted from an API response.
97+
// This function is useful for cases where the error response does not match the predefined StructuredError format,
98+
// and instead, a more generic error handling approach is needed. It extracts known fields such as 'message' and 'detail'
99+
// from the generic error map and updates the corresponding fields in the APIError instance.
100+
//
101+
// Parameters:
102+
// - genericErr: A map[string]interface{} representing the generic error structure extracted from an API response.
103+
//
104+
// The function checks for the presence of 'message' and 'detail' keys in the generic error map. If these keys are present,
105+
// their values are used to update the 'Message' and 'Detail' fields of the APIError instance, respectively.
72106
func (e *APIError) updateFromGenericError(genericErr map[string]interface{}) {
73107
if msg, ok := genericErr["message"].(string); ok {
74108
e.Message = msg
75109
}
76110
if detail, ok := genericErr["detail"].(string); ok {
77111
e.Detail = detail
78112
}
79-
// Add more fields as needed
113+
// Optionally add more fields if necessary
80114
}

0 commit comments

Comments
 (0)