Parse an action's application error (#133)
* Generically parse the application error
* Update vendor.json to include all dependent packages
* gofmt
* don't display the application error as the action's error
* put back original vendor.json
diff --git a/whisk/client.go b/whisk/client.go
index 91b8a79..9ea01bc 100644
--- a/whisk/client.go
+++ b/whisk/client.go
@@ -586,9 +586,13 @@
// Handle application errors that occur when --result option is false (#5)
if err == nil && whiskErrorResponse != nil && whiskErrorResponse.Response != nil && whiskErrorResponse.Response.Status != nil {
Debug(DbgInfo, "Detected response status `%s` that a whisk.error(\"%#v\") was returned\n",
- *whiskErrorResponse.Response.Status, *whiskErrorResponse.Response.Result)
+ *whiskErrorResponse.Response.Status, whiskErrorResponse.Response.Result)
+
+ errStr := getApplicationErrorMessage(whiskErrorResponse.Response.Result)
+ Debug(DbgInfo, "Application error received: %s\n", errStr)
+
errMsg := wski18n.T("The following application error was received: {{.err}}",
- map[string]interface{}{"err": *whiskErrorResponse.Response.Result})
+ map[string]interface{}{"err": errStr})
whiskErr := MakeWskError(errors.New(errMsg), resp.StatusCode-256, NO_DISPLAY_MSG, NO_DISPLAY_USAGE,
NO_MSG_DISPLAYED, DISPLAY_PREFIX, APPLICATION_ERR)
return parseSuccessResponse(resp, data, v), whiskErr
@@ -600,10 +604,10 @@
// Handle application errors that occur with blocking invocations when --result option is true (#5)
if err == nil && appErrResult.Error != nil {
Debug(DbgInfo, "Error code is null, blocking with result invocation error has occurred\n")
- errMsg := fmt.Sprintf("%v", *appErrResult.Error)
- Debug(DbgInfo, "Application error received: %s\n", errMsg)
+ errStr := getApplicationErrorMessage(*appErrResult.Error)
+ Debug(DbgInfo, "Application error received: %s\n", errStr)
- whiskErr := MakeWskError(errors.New(errMsg), resp.StatusCode-256, NO_DISPLAY_MSG, NO_DISPLAY_USAGE,
+ whiskErr := MakeWskError(errors.New(errStr), resp.StatusCode-256, NO_DISPLAY_MSG, NO_DISPLAY_USAGE,
NO_MSG_DISPLAYED, DISPLAY_PREFIX, APPLICATION_ERR)
return parseSuccessResponse(resp, data, v), whiskErr
}
@@ -616,6 +620,85 @@
return resp, whiskErr
}
+func getApplicationErrorMessage(errResp interface{}) string {
+ var errStr string
+
+ // Handle error results that looks like:
+ //
+ // {
+ // "error": {
+ // "error": "An error string",
+ // "message": "An error message",
+ // "another-message": "Another error message"
+ // }
+ // }
+ // Returns "An error string; An error message; Another error message"
+ //
+ // OR
+ // {
+ // "error": "An error string"
+ // }
+ // Returns "An error string"
+ //
+ // OR
+ // {
+ // "error": {
+ // "custom-err": {
+ // "error": "An error string",
+ // "message": "An error message"
+ // }
+ // }
+ // }
+ // Returns "{"error": { "custom-err": { "error": "An error string", "message": "An error message" } } }"
+
+ errMapIntf, errMapIntfOk := errResp.(map[string]interface{})
+ if !errMapIntfOk {
+ errStr = fmt.Sprintf("%v", errResp)
+ } else {
+ // Check if the "error" field in the response JSON
+ errObjIntf, errObjIntfOk := errMapIntf["error"]
+ if !errObjIntfOk {
+ errStr = fmt.Sprintf("%v", errMapIntf)
+ } else {
+ // Check if the "error" field value is a JSON object
+ errObj, errObjOk := errObjIntf.(map[string]interface{})
+ if !errObjOk {
+ // The "error" field value is not JSON; check if it's a string
+ errorStr, errorStrOk := errObjIntf.(string)
+ if !errorStrOk {
+ errStr = fmt.Sprintf("%v", errObjIntf)
+ } else {
+ errStr = errorStr
+ }
+ } else {
+ Debug(DbgInfo, "Application failure error json: %+v\n", errObj)
+
+ // Concatenate all string field values into a single error string
+ msgSeparator := ""
+ for _, val := range errObj {
+ valStr, valStrOk := val.(string)
+ if valStrOk {
+ errStr = errStr + msgSeparator + valStr
+ msgSeparator = "; "
+ }
+ }
+
+ // If no top level string fields exist, return the entire error object
+ // Return a nice JSON string if possible; otherwise let Go try it's best
+ if len(errStr) == 0 {
+ jsonBytes, err := json.Marshal(errObj)
+ if err != nil {
+ errStr = fmt.Sprintf("%v", errObj)
+ } else {
+ errStr = string(jsonBytes)
+ }
+ }
+ }
+ }
+ }
+
+ return errStr
+}
func parseSuccessResponse(resp *http.Response, data []byte, v interface{}) *http.Response {
Debug(DbgInfo, "Parsing HTTP response into struct type: %s\n", reflect.TypeOf(v))
@@ -660,9 +743,9 @@
}
type WhiskResponse struct {
- Result *WhiskResult `json:"result"`
- Success bool `json:"success"`
- Status *interface{} `json:"status"`
+ Result map[string]interface{} `json:"result"`
+ Success bool `json:"success"`
+ Status *interface{} `json:"status"`
}
type WhiskResult struct {
diff --git a/whisk/client_test.go b/whisk/client_test.go
index ec27b63..b580c97 100644
--- a/whisk/client_test.go
+++ b/whisk/client_test.go
@@ -168,3 +168,22 @@
assert.Equal(t, "Value1", newRequestUrl.Header.Get("Key1"))
assert.Equal(t, "Value2", newRequestUrl.Header.Get("Key2"))
}
+
+func TestParseApplicationError(t *testing.T) {
+ appErr1 := map[string]interface{}{
+ "error": map[string]interface{}{
+ "error": "An error string",
+ "message": "An error message",
+ },
+ }
+
+ appErr2 := map[string]interface{}{
+ "error": "Another error string",
+ }
+
+ errStr := getApplicationErrorMessage(appErr1)
+ assert.Equal(t, "An error string; An error message", errStr)
+
+ errStr = getApplicationErrorMessage(appErr2)
+ assert.Equal(t, "Another error string", errStr)
+}