diff --git a/client.go b/client.go index 599e9720..7672d44e 100644 --- a/client.go +++ b/client.go @@ -303,7 +303,11 @@ func (c *APIClient) runRequest(method string, url string, payload interface{}) ( return nil, err } defer resp.Body.Close() - return nil, fmt.Errorf("%d: %s", resp.StatusCode, string(payload)) + if resp.StatusCode != 400 { + return nil, fmt.Errorf("%d: %s", resp.StatusCode, string(payload)) + } + + return nil, common.ConstructError(resp.StatusCode, payload) } return resp, err diff --git a/client_test.go b/client_test.go new file mode 100644 index 00000000..1bf4cb2a --- /dev/null +++ b/client_test.go @@ -0,0 +1,90 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// + +package gofish + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stmcginnis/gofish/common" +) + +const ( + errMsg = `{ + "code": "Base.1.0.GeneralError", + "message": "A general error has occurred. See ExtendedInfo for more information.", + "@Message.ExtendedInfo": [ + { + "MessageId": "Base.1.0.PropertyValueNotInList", + "Message": "The value Red for the property IndicatorLED is not in the list of acceptable values", + "MessageArgs": [ + "RED", + "IndicatorLED" + ], + "Severity": "Warning", + "Resolution": "Remove the property from the request body and resubmit the request if the operation failed" + }, + { + "MessageId": "Base.1.0.PropertyNotWriteable", + "Message": "The property SKU is a read only property and cannot be assigned a value", + "MessageArgs": [ + "SKU" + ], + "Severity": "Warning", + "Resolution": "Remove the property from the request body and resubmit the request if the operation failed" + } + ] + }` + expectErrorStatus400 = `{"error": ` + errMsg + "}" + expectErrorStatus404 = `404: {"error": ` + errMsg + "}" +) + +// TestError400 tests the parsing of error reply. +func TestError400(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(400) + w.Write([]byte(expectErrorStatus400)) + })) + defer ts.Close() + + _, err := Connect(ClientConfig{Endpoint: ts.URL, HTTPClient: ts.Client()}) + if err == nil { + t.Error("Update call should fail") + } + errStruct, ok := err.(*common.Error) + if !ok { + t.Errorf("400 should return known error type: %v", err) + } + errBody, err := json.MarshalIndent(errStruct, " ", " ") + if err != nil { + t.Errorf("Marshall error %v got: %s", errStruct, err) + } + if errMsg != string(errBody) { + t.Errorf("Expect:\n%s\nGot:\n%s", errMsg, string(errBody)) + } +} + +// TestErrorNon400 tests the parsing of error reply for non 400 reply. +func TestErrorNon400(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(404) + w.Write([]byte(expectErrorStatus400)) + })) + defer ts.Close() + + _, err := Connect(ClientConfig{Endpoint: ts.URL, HTTPClient: ts.Client()}) + if err == nil { + t.Error("Update call should fail") + } + _, ok := err.(*common.Error) + if ok { + t.Errorf("404 should not return known error type: %v", err) + } + if expectErrorStatus404 != err.Error() { + t.Errorf("Expect:\n%s\nGot:\n%s", expectErrorStatus404, err.Error()) + } +} diff --git a/common/types.go b/common/types.go index 488ac083..d38bc23e 100644 --- a/common/types.go +++ b/common/types.go @@ -706,3 +706,49 @@ type Settings struct { // resource. Time string } + +// ConstructError tries to create error if body is defined as redfish spec +func ConstructError(statusCode int, b []byte) error { + var err struct { + Error *Error + } + if e := json.Unmarshal(b, &err); e != nil || err.Error == nil{ + // return normal error + return fmt.Errorf("%d: %s", statusCode, string(b)) + } + err.Error.rawData = b + return err.Error +} + +// Error is redfish error response object for 400 +type Error struct { + rawData []byte + // A string indicating a specific MessageId from the message registry. + Code string `json:"code"` + // A human readable error message corresponding to the message in the message registry. + Message string `json:"message"` + // An array of message objects describing one or more error message(s). + ExtendedInfos []ErrExtendedInfo `json:"@Message.ExtendedInfo"` +} + +func (e *Error) Error() string { + return string(e.rawData) +} + +// ErrExtendedInfo is for redfish ExtendedInfo error response +// TODO: support RelatedProperties +type ErrExtendedInfo struct { + // Indicating a specific error or message (not to be confused with the HTTP status code). + // This code can be used to access a detailed message from a message registry. + MessageID string `json:"MessageId"` + // A human readable error message indicating the semantics associated with the error. + // This shall be the complete message, and not rely on substitution variables. + Message string + // An optional array of strings representing the substitution parameter values for the message. + // This shall be included in the response if a MessageId is specified for a parameterized message. + MessageArgs []string + // An optional string representing the severity of the error. + Severity string + //An optional string describing recommended action(s) to take to resolve the error. + Resolution string +}