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)
+}