Skip to content

Commit

Permalink
Merge pull request #95 from leslie-qiwa/error-struct
Browse files Browse the repository at this point in the history
return formated error struct for status code 400
  • Loading branch information
stmcginnis authored Oct 30, 2020
2 parents a6bfe4a + 3c9421a commit 6d84309
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 1 deletion.
6 changes: 5 additions & 1 deletion client.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
90 changes: 90 additions & 0 deletions client_test.go
Original file line number Diff line number Diff line change
@@ -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())
}
}
46 changes: 46 additions & 0 deletions common/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

0 comments on commit 6d84309

Please sign in to comment.