You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwhisk.apache.org by ho...@apache.org on 2017/10/13 15:27:19 UTC

[incubator-openwhisk-wskdeploy] branch master updated: Add Json support for Input parameters (#601)

This is an automated email from the ASF dual-hosted git repository.

houshengbo pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-openwhisk-wskdeploy.git


The following commit(s) were added to refs/heads/master by this push:
     new a079773  Add Json support for Input parameters (#601)
a079773 is described below

commit a079773c5ef4dabc7c09301f79d10ef0562df4cd
Author: Matt Rutkowski <mr...@us.ibm.com>
AuthorDate: Fri Oct 13 11:27:17 2017 -0400

    Add Json support for Input parameters (#601)
    
    * Initial test manifest and enable json on list of valid types.
    
    * Initial test manifest and enable json on list of valid types.
    
    * Add json type as a Golang map.
    
    * Enhance error line number reporting on parameter type errors.
    
    * Add json as valid type.
    
    * Add json as valid type.
    
    * Add json as valid type.
    
    * Add json as valid type.
    
    * Add json as valid type.
    
    * Add json as valid type.
    
    * Add json as valid type.
    
    * Handle bothe cases of Map values for JSON type.
    
    * Handle bothe cases of Map values for JSON type.
    
    * Handle bothe cases of Map values for JSON type.
    
    * Handle bothe cases of Map values for JSON type.
    
    * Handle bothe cases of Map values for JSON type.
    
    * Handle bothe cases of Map values for JSON type.
    
    * Handle both cases of Map values for JSON type.
    
    * Handle both cases of Map values for JSON type.
    
    * Handle both cases of Map values for JSON type.
    
    * Handle both cases of Map values for JSON type.
    
    * Handle both cases of Map values for JSON type.
    
    * Handle both cases of Map values for JSON type.
    
    * Handle both cases of Map values for JSON type.
    
    * Handle both cases of Map values for JSON type.
    
    * Handle both cases of Map values for JSON type.
    
    * Handle both cases of Map values for JSON type.
    
    * Handle both cases of Map values for JSON type.
    
    * Handle both cases of Map values for JSON type.
    
    * Handle both cases of Map values for JSON type.
    
    * Handle both cases of Map values for JSON type.
    
    * Handle both cases of Map values for JSON type.
    
    * Handle both cases of Map values for JSON type.
---
 cmd/root.go                                  |   7 +-
 deployers/servicedeployer.go                 |  60 ++++++++++-----
 parsers/deploy_parser.go                     |  19 ++---
 parsers/deploy_parser_test.go                |   6 +-
 parsers/manifest_parser.go                   | 111 +++++++++++++++------------
 parsers/manifest_parser_test.go              |  83 +++++++++++++++++++-
 parsers/yamlparser.go                        |   6 +-
 tests/dat/manifest_bad_yaml_2.yaml           |   8 ++
 tests/dat/manifest_bad_yaml_3.yaml           |   8 ++
 tests/dat/manifest_bad_yaml_4.yaml           |   6 ++
 tests/dat/manifest_validate_json_params.yaml |  33 ++++++++
 utils/conversion.go                          |  54 +++++++++++++
 utils/dependencies.go                        |   1 -
 utils/misc.go                                |   9 +--
 utils/wskdeployerror.go                      |  26 ++++---
 15 files changed, 325 insertions(+), 112 deletions(-)

diff --git a/cmd/root.go b/cmd/root.go
index 03ab905..331621c 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -314,9 +314,10 @@ func Undeploy() error {
 		setSupportedRuntimes(clientConfig.Host)
 
 		verifiedPlan, err := deployer.ConstructUnDeploymentPlan()
-        if err != nil {
-            return err
-        }
+		if err != nil {
+		    return err
+		}
+
 		err = deployer.UnDeploy(verifiedPlan)
 		if err != nil {
 			return err
diff --git a/deployers/servicedeployer.go b/deployers/servicedeployer.go
index 509f4d4..c340a3e 100644
--- a/deployers/servicedeployer.go
+++ b/deployers/servicedeployer.go
@@ -30,6 +30,7 @@ import (
 	"github.com/apache/incubator-openwhisk-wskdeploy/parsers"
 	"github.com/apache/incubator-openwhisk-wskdeploy/utils"
 	"github.com/apache/incubator-openwhisk-wskdeploy/wski18n"
+	"reflect"
 )
 
 type DeploymentApplication struct {
@@ -138,12 +139,12 @@ func (deployer *ServiceDeployer) ConstructDeploymentPlan() error {
 		return err
 	}
 
-    applicationName := ""
-    if len(manifest.Application.Packages) != 0 {
-        applicationName = manifest.Application.Name
-    }
+	applicationName := ""
+	if len(manifest.Application.Packages) != 0 {
+		applicationName = manifest.Application.Name
+	}
 
-	// process deploymet file
+	// process deployment file
 	if utils.FileExists(deployer.DeploymentPath) {
 		var deploymentReader = NewDeploymentReader(deployer)
 		err = deploymentReader.HandleYaml()
@@ -151,16 +152,16 @@ func (deployer *ServiceDeployer) ConstructDeploymentPlan() error {
 		if err != nil {
 			return err
 		}
-        // compare the name of the application
-        if len(deploymentReader.DeploymentDescriptor.Application.Packages) != 0 && len(applicationName) != 0 {
-            appNameDeploy := deploymentReader.DeploymentDescriptor.Application.Name
-            if appNameDeploy != applicationName {
-                errorString := wski18n.T("The name of the application {{.appNameDeploy}} in deployment file at [{{.deploymentFile}}] does not match the name of the application {{.appNameManifest}}} in manifest file at [{{.manifestFile}}].",
-                    map[string]interface{}{"appNameDeploy": appNameDeploy, "deploymentFile": deployer.DeploymentPath,
-                        "appNameManifest": applicationName,  "manifestFile": deployer.ManifestPath })
-                return utils.NewInputYamlFormatError(errorString)
-            }
-        }
+		// compare the name of the application
+		if len(deploymentReader.DeploymentDescriptor.Application.Packages) != 0 && len(applicationName) != 0 {
+			appNameDeploy := deploymentReader.DeploymentDescriptor.Application.Name
+			if appNameDeploy != applicationName {
+				errorString := wski18n.T("The name of the application {{.appNameDeploy}} in deployment file at [{{.deploymentFile}}] does not match the name of the application {{.appNameManifest}}} in manifest file at [{{.manifestFile}}].",
+				    map[string]interface{}{"appNameDeploy": appNameDeploy, "deploymentFile": deployer.DeploymentPath,
+					"appNameManifest": applicationName,  "manifestFile": deployer.ManifestPath })
+				return utils.NewInputYamlFormatError(errorString)
+			}
+		}
 
 		deploymentReader.BindAssets()
 	}
@@ -1048,7 +1049,7 @@ func (deployer *ServiceDeployer) printDeploymentAssets(assets *DeploymentApplica
 		for _, p := range pack.Package.Parameters {
 			jsonValue, err := utils.PrettyJSON(p.Value)
 			if err != nil {
-				fmt.Printf("        - %s : %s\n", p.Key, "Unknown value")
+				fmt.Printf("        - %s : %s\n", p.Key, utils.UNKNOWN_VALUE)
 			} else {
 				fmt.Printf("        - %s : %v\n", p.Key, jsonValue)
 			}
@@ -1068,12 +1069,29 @@ func (deployer *ServiceDeployer) printDeploymentAssets(assets *DeploymentApplica
 			utils.PrintOpenWhiskOutputln("  * action: " + action.Action.Name)
 			utils.PrintOpenWhiskOutputln("    bindings: ")
 			for _, p := range action.Action.Parameters {
-				jsonValue, err := utils.PrettyJSON(p.Value)
-				if err != nil {
-					fmt.Printf("        - %s : %s\n", p.Key, "Unknown value")
+
+				if( reflect.TypeOf(p.Value).Kind() == reflect.Map ) {
+                                        if _, ok := p.Value.(map[interface{}]interface{}); ok {
+						var temp map[string]interface{} =
+							utils.ConvertInterfaceMap(p.Value.(map[interface{}]interface{}))
+						fmt.Printf("        - %s : %v\n", p.Key, temp)
+					} else {
+						jsonValue,err := utils.PrettyJSON(p.Value)
+						if err != nil {
+							fmt.Printf("        - %s : %s\n", p.Key, utils.UNKNOWN_VALUE)
+						} else {
+							fmt.Printf("        - %s : %v\n", p.Key, jsonValue)
+						}
+					}
 				} else {
-					fmt.Printf("        - %s : %v\n", p.Key, jsonValue)
+					jsonValue, err := utils.PrettyJSON(p.Value)
+					if err != nil {
+						fmt.Printf("        - %s : %s\n", p.Key, utils.UNKNOWN_VALUE)
+					} else {
+						fmt.Printf("        - %s : %v\n", p.Key, jsonValue)
+					}
 				}
+
 			}
 			utils.PrintOpenWhiskOutputln("    annotations: ")
 			for _, p := range action.Action.Annotations {
@@ -1098,7 +1116,7 @@ func (deployer *ServiceDeployer) printDeploymentAssets(assets *DeploymentApplica
 		for _, p := range trigger.Parameters {
 			jsonValue, err := utils.PrettyJSON(p.Value)
 			if err != nil {
-				fmt.Printf("        - %s : %s\n", p.Key, "Unknown value")
+				fmt.Printf("        - %s : %s\n", p.Key, utils.UNKNOWN_VALUE)
 			} else {
 				fmt.Printf("        - %s : %v\n", p.Key, jsonValue)
 			}
diff --git a/parsers/deploy_parser.go b/parsers/deploy_parser.go
index 316655e..148d6ce 100644
--- a/parsers/deploy_parser.go
+++ b/parsers/deploy_parser.go
@@ -59,19 +59,14 @@ func (dm *YAMLParser) ParseDeployment(deploymentPath string) (*YAML, error) {
 func (dm *YAMLParser) convertErrorToLinesMsgs(errorString string) (lines []string, msgs []string) {
     strs := strings.Split(errorString, "\n")
     for i := 0; i < len(strs); i++ {
-        errMsg := strings.TrimSpace(strs[i])
-        if strings.Contains(errMsg, utils.LINE) {
-            s := strings.Split(errMsg, utils.LINE)
-            lineMsg := s[1]
-            line := strings.Split(lineMsg, ":")
-            if (len(line) == 2) {
-                lines = append(lines, strings.TrimSpace(line[0]))
-                msgs = append(msgs, line[1])
-                continue
-            }
-        }
+        var errorMsg string
+	if strings.Contains(strs[i], utils.LINE) {
+		errorMsg = strings.Replace(strs[i], utils.LINE, "(on or near) "+utils.LINE, 1)
+	} else {
+		errorMsg = strs[i]
+	}
         lines = append(lines, utils.UNKNOWN)
-        msgs = append(msgs, errMsg)
+        msgs = append(msgs, strings.TrimSpace(errorMsg))
     }
     return
 }
diff --git a/parsers/deploy_parser_test.go b/parsers/deploy_parser_test.go
index 2dea01b..e98666f 100644
--- a/parsers/deploy_parser_test.go
+++ b/parsers/deploy_parser_test.go
@@ -55,7 +55,7 @@ func TestInvalidKeyDeploymentYaml(t *testing.T) {
     _, err = p.ParseDeployment(tmpfile.Name())
     assert.NotNil(t, err)
     // go-yaml/yaml prints the wrong line number for mapping values. It should be 3.
-    assert.Contains(t, err.Error(), "field invalidKey not found in struct parsers.Application: Line 2, its neighbor lines, or the lines on the same level")
+    assert.Contains(t, err.Error(), "line 2: field invalidKey not found in struct parsers.Application")
 }
 
 func TestMappingValueDeploymentYaml(t *testing.T) {
@@ -74,7 +74,7 @@ func TestMappingValueDeploymentYaml(t *testing.T) {
     _, err = p.ParseDeployment(tmpfile.Name())
     assert.NotNil(t, err)
     // go-yaml/yaml prints the wrong line number for mapping values. It should be 3.
-    assert.Contains(t, err.Error(), "mapping values are not allowed in this context: Line 2, its neighbor lines, or the lines on the same level")
+    assert.Contains(t, err.Error(), "line 2: mapping values are not allowed in this context")
 }
 
 func TestMissingRootNodeDeploymentYaml(t *testing.T) {
@@ -91,7 +91,7 @@ func TestMissingRootNodeDeploymentYaml(t *testing.T) {
     _, err = p.ParseDeployment(tmpfile.Name())
     assert.NotNil(t, err)
     // go-yaml/yaml prints the wrong line number for mapping values. It should be 3.
-    assert.Contains(t, err.Error(), "field name not found in struct parsers.YAML: Line 1, its neighbor lines, or the lines on the same level")
+    assert.Contains(t, err.Error(), "line 1: field name not found in struct parsers.YAML")
 }
 
 func TestParseDeploymentYAML_Application(t *testing.T) {
diff --git a/parsers/manifest_parser.go b/parsers/manifest_parser.go
index 5ead4b3..08c5c99 100644
--- a/parsers/manifest_parser.go
+++ b/parsers/manifest_parser.go
@@ -344,24 +344,24 @@ func (dm *YAMLParser) ComposeSequences(namespace string, sequences map[string]Se
 
 func (dm *YAMLParser) ComposeActionsFromAllPackages(manifest *YAML, filePath string) ([]utils.ActionRecord, error) {
 	var s1 []utils.ActionRecord = make([]utils.ActionRecord, 0)
-    manifestPackages := make(map[string]Package)
+	manifestPackages := make(map[string]Package)
 	if manifest.Package.Packagename != "" {
 		return dm.ComposeActions(filePath, manifest.Package.Actions, manifest.Package.Packagename)
 	} else {
-        if manifest.Packages != nil {
-            manifestPackages = manifest.Packages
-        } else {
-            manifestPackages = manifest.Application.Packages
-        }
-    }
-    for n, p := range manifestPackages {
-        a, err := dm.ComposeActions(filePath, p.Actions, n)
-        if err == nil {
-            s1 = append(s1, a...)
-        } else {
-            return nil, err
-        }
-    }
+		if manifest.Packages != nil {
+			manifestPackages = manifest.Packages
+		} else {
+			manifestPackages = manifest.Application.Packages
+		}
+    	}
+    	for n, p := range manifestPackages {
+		a, err := dm.ComposeActions(filePath, p.Actions, n)
+		if err == nil {
+			s1 = append(s1, a...)
+		} else {
+			return nil, err
+		}
+	}
 	return s1, nil
 }
 
@@ -372,6 +372,10 @@ func (dm *YAMLParser) ComposeActions(filePath string, actions map[string]Action,
 
 	for key, action := range actions {
 		splitFilePath := strings.Split(filePath, string(os.PathSeparator))
+
+		// set the name of the action (which is the key)
+		action.Name = key
+
 		//set action.Function to action.Location
 		//because Location is deprecated in Action entity
 		if action.Function == "" && action.Location != "" {
@@ -430,7 +434,7 @@ func (dm *YAMLParser) ComposeActions(filePath string, actions map[string]Action,
 					code = base64.StdEncoding.EncodeToString([]byte(dat))
 				}
 				if ext == ".zip" && action.Runtime == "" {
-                    utils.PrintOpenWhiskOutputln("need explicit action Runtime value")
+					utils.PrintOpenWhiskOutputln("need explicit action Runtime value")
 				}
 				wskaction.Exec.Code = &code
 			}
@@ -442,12 +446,12 @@ func (dm *YAMLParser) ComposeActions(filePath string, actions map[string]Action,
 				wskaction.Exec.Kind = action.Runtime
 
 			} else if utils.Flags.Strict {
-                wskaction.Exec.Kind = action.Runtime
-            } else {
-                errStr := wski18n.T("wskdeploy has chosen a particular runtime for the action.\n")
-			    whisk.Debug(whisk.DbgWarn, errStr)
-            }
-        }
+                		wskaction.Exec.Kind = action.Runtime
+            		} else {
+				errStr := wski18n.T("wskdeploy has chosen a particular runtime for the action.\n")
+				whisk.Debug(whisk.DbgWarn, errStr)
+			}
+                }
 
 		// we can specify the name of the action entry point using main
 		if action.Main != "" {
@@ -469,6 +473,7 @@ func (dm *YAMLParser) ComposeActions(filePath string, actions map[string]Action,
 				keyValArr = append(keyValArr, keyVal)
 			}
 		}
+
 		if len(keyValArr) > 0 {
 			wskaction.Parameters = keyValArr
 		}
@@ -484,6 +489,7 @@ func (dm *YAMLParser) ComposeActions(filePath string, actions map[string]Action,
 
 		// only set the webaction when the annotations are not empty.
 		if action.Webexport == "true" {
+			// TODO() why is this commented out?  we should now support annotations...
 			//wskaction.Annotations = keyValArr
 			wskaction.Annotations, errorParser = utils.WebAction("yes", keyValArr, action.Name, false)
 			if errorParser != nil {
@@ -687,7 +693,7 @@ func (dm *YAMLParser) ComposeApiRecords(pkg Package) ([]*whisk.ApiCreateRequest,
 }
 
 // TODO(): Support other valid Package Manifest types
-// TODO(): i.e., json (valid), timestamp, version, string256, string64, string16
+// TODO(): i.e., timestamp, version, string256, string64, string16
 // TODO(): Support JSON schema validation for type: json
 // TODO(): Support OpenAPI schema validation
 
@@ -702,6 +708,8 @@ var validParameterNameMap = map[string]string{
 	"int64":   "integer",
 	"float32": "float",
 	"float64": "float",
+	"json":    "json",
+	"map":     "json",
 }
 
 var typeDefaultValueMap = map[string]interface{}{
@@ -709,6 +717,7 @@ var typeDefaultValueMap = map[string]interface{}{
 	"integer": 0,
 	"float":   0.0,
 	"boolean": false,
+	"json":    make(map[string]interface{}),
 	// TODO() Support these types + their validation
 	// timestamp
 	// null
@@ -716,7 +725,6 @@ var typeDefaultValueMap = map[string]interface{}{
 	// string256
 	// string64
 	// string16
-	// json
 	// scalar-unit
 	// schema
 	// object
@@ -738,8 +746,8 @@ func getTypeDefaultValue(typeName string) interface{} {
 	return nil
 }
 
-func ResolveParamTypeFromValue(value interface{}, filePath string) (string, error) {
-	// Note: string is the default type if not specified.
+func ResolveParamTypeFromValue(name string, value interface{}, filePath string) (string, error) {
+	// Note: 'string' is the default type if not specified and not resolvable.
 	var paramType string = "string"
 	var err error = nil
 
@@ -752,19 +760,11 @@ func ResolveParamTypeFromValue(value interface{}, filePath string) (string, erro
 			paramType = normalizedTypeName
 
 		} else {
-			// raise an error if param is not a known type
-			// TODO(): We have information to display to user on an error or warning here
-			// TODO(): specifically, we have the parameter name, its value to show on error/warning
-			// TODO(): perhaps this is a different Class of error?  e.g., ErrorParameterMismatchError
-			lines := []string{"Line Unknown"}
-			msgs := []string{"Parameter value is not a known type. [" + actualType + "]"}
-			err = utils.NewParserErr(filePath, lines, msgs)
+			// raise an error if parameter's value is not a known type
+			// TODO() - move string to i18n
+			msgs := []string{"Parameter [" + name + "] has a value that is not a known type. [" + actualType + "]"}
+			err = utils.NewParserErr(filePath, nil, msgs)
 		}
-	} else {
-
-		// TODO: The value may be supplied later, we need to support non-fatal warnings
-		// raise an error if param is nil
-		//err = utils.NewParserErr("",-1,"Paramter value is nil.")
 	}
 	return paramType, err
 }
@@ -785,7 +785,7 @@ func ResolveParameter(paramName string, param *Parameter, filePath string) (inte
 	if !param.multiline {
 		// we have a single-line parameter declaration
 		// We need to identify parameter Type here for later validation
-		param.Type, errorParser = ResolveParamTypeFromValue(param.Value, filePath)
+		param.Type, errorParser = ResolveParamTypeFromValue(paramName, param.Value, filePath)
 
 		// In single-line format, the param's <value> can be a "Type name" and NOT an actual value.
 		// if this is the case, we must detect it and set the value to the default for that type name.
@@ -811,13 +811,13 @@ func ResolveParameter(paramName string, param *Parameter, filePath string) (inte
 
 		// if we also have a type at this point, verify value (and/or default) matches type, if not error
 		// Note: if either the value or default is in conflict with the type then this is an error
-		tempType, errorParser = ResolveParamTypeFromValue(param.Value, filePath)
+		tempType, errorParser = ResolveParamTypeFromValue(paramName, param.Value, filePath)
 
 		// if we do not have a value or default, but have a type, find its default and use it for the value
 		if param.Type != "" && !isValidParameterType(param.Type) {
-			lines := []string{"Line Unknown"}
-			msgs := []string{"Invalid Type for parameter. [" + param.Type + "]"}
-			return value, utils.NewParserErr(filePath, lines, msgs)
+			// TODO() - move string to i18n
+			msgs := []string{"Parameter [" + paramName + "] has an invalid Type. [" + param.Type + "]"}
+			return value, utils.NewParserErr(filePath, nil, msgs)
 		} else if param.Type == "" {
 			param.Type = tempType
 		}
@@ -826,19 +826,30 @@ func ResolveParameter(paramName string, param *Parameter, filePath string) (inte
 	// Make sure the parameter's value is a valid, non-empty string and startsWith '$" (dollar) sign
 	value = utils.GetEnvVar(param.Value)
 
-	typ := param.Type
+	// JSON - Handle both cases, where value 1) is a string containing JSON, 2) is a map of JSON
 
-	// TODO(Priti): need to validate type is one of the supported primitive types with unit testing
-	// TODO(): with the new logic, when would the following Unmarhsall() call be used?
-	// if value is of type 'string' and its not empty <OR> if type is not 'string'
-	if str, ok := value.(string); ok && (len(typ) == 0 || typ != "string") {
+	// Case 1: if user set parameter type to 'json' and the value's type is a 'string'
+	if str, ok := value.(string); ok && param.Type == "json" {
 		var parsed interface{}
 		err := json.Unmarshal([]byte(str), &parsed)
 		if err == nil {
+			fmt.Printf("EXIT: Parameter type=[%v] value=[%v]\n", param.Type, parsed)
 			return parsed, err
 		}
 	}
 
+	// Case 2: value contains a map of JSON
+	// We must make sure the map type is map[string]interface{}; otherwise we cannot
+	// marshall it later on to serialize in the body of an HTTP request.
+	if( param.Value != nil && reflect.TypeOf(param.Value).Kind() == reflect.Map ) {
+		if _, ok := param.Value.(map[interface{}]interface{}); ok {
+			var temp map[string]interface{} =
+				utils.ConvertInterfaceMap(param.Value.(map[interface{}]interface{}))
+			fmt.Printf("EXIT: Parameter type=[%v] value=[%v]\n", param.Type, temp)
+			return temp, errorParser
+		}
+	}
+
 	// Default to an empty string, do NOT error/terminate as Value may be provided later bu a Deployment file.
 	if value == nil {
 		value = getTypeDefaultValue(param.Type)
@@ -847,7 +858,7 @@ func ResolveParameter(paramName string, param *Parameter, filePath string) (inte
 
 	// Trace Parameter struct after resolution
 	//dumpParameter(paramName, param, "AFTER")
-	//fmt.Printf("EXIT: value=[%v]\n", value)
+	//fmt.Printf("EXIT: Parameter type=[%v] value=[%v]\n", param.Type, value)
 
 	return value, errorParser
 }
@@ -899,7 +910,7 @@ func dumpParameter(paramName string, param *Parameter, separator string) {
 	fmt.Printf("%s:\n", separator)
 	fmt.Printf("\t%s: (%T)\n", paramName, param)
 	if param != nil {
-		fmt.Printf("\t\tParameter.Descrption: [%s]\n", param.Description)
+		fmt.Printf("\t\tParameter.Description: [%s]\n", param.Description)
 		fmt.Printf("\t\tParameter.Type: [%s]\n", param.Type)
 		fmt.Printf("\t\tParameter.Value: [%v]\n", param.Value)
 		fmt.Printf("\t\tParameter.Default: [%v]\n", param.Default)
diff --git a/parsers/manifest_parser_test.go b/parsers/manifest_parser_test.go
index cd1038c..39c1a6a 100644
--- a/parsers/manifest_parser_test.go
+++ b/parsers/manifest_parser_test.go
@@ -949,6 +949,83 @@ func TestResolveParameterForMultiLineParams(t *testing.T) {
 
 }
 
+// Test 17: validate JSON parameters
+func TestParseManifestForJSONParams(t *testing.T) {
+    // manifest file is located under ../tests folder
+    manifestFile := "../tests/dat/manifest_validate_json_params.yaml"
+    // read and parse manifest.yaml file
+    m, _ := NewYAMLParser().ParseManifest(manifestFile)
+
+    // validate package name should be "validate"
+    packageName := "validate_json"
+    actionName := "validate_json_params"
+    expectedActionsCount := 1
+
+    assert.NotNil(t, m.Packages[packageName],
+        "Expected package named "+ packageName + " but got none")
+
+    // validate this package contains one action
+    actualActionsCount := len(m.Packages[packageName].Actions)
+    assert.Equal(t, expectedActionsCount, actualActionsCount,
+        "Expected " + string(expectedActionsCount) + " but got " + string(actualActionsCount))
+
+    if action, ok := m.Packages[packageName].Actions[actionName]; ok {
+        // validate location/function of an action to be "actions/dump_params.js"
+        expectedResult := "actions/dump_params.js"
+        actualResult := action.Function
+        assert.Equal(t, expectedResult, actualResult, "Expected action function " + expectedResult + " but got " + actualResult)
+
+        // validate runtime of an action to be "nodejs:6"
+        expectedResult = "nodejs:6"
+        actualResult = action.Runtime
+        assert.Equal(t, expectedResult, actualResult, "Expected action runtime " + expectedResult + " but got " + actualResult)
+
+        // validate the number of inputs to this action
+        expectedResult = strconv.FormatInt(6, 10)
+        actualResult = strconv.FormatInt(int64(len(action.Inputs)), 10)
+        assert.Equal(t, expectedResult, actualResult, "Expected " + expectedResult + " but got " + actualResult)
+
+        // validate inputs to this action
+        for input, param := range action.Inputs {
+            // Trace to help debug complex values:
+            // utils.PrintTypeInfo(input, param.Value)
+            switch input {
+            case "member1":
+                actualResult1 := param.Value.(string)
+                expectedResult1 := "{ \"name\": \"Sam\", \"place\": \"Shire\" }"
+                assert.Equal(t, expectedResult1, actualResult1, "Expected " + expectedResult + " but got " + actualResult)
+            case "member2":
+                actualResult2 := param.Value.(map[interface{}]interface{})
+                expectedResult2 := map[interface{}]interface{}{"name": "Sam", "place": "Shire"}
+                assert.Equal(t, expectedResult2, actualResult2, "Expected " + expectedResult + " but got " + actualResult)
+            case "member3":
+                actualResult3 := param.Value.(map[interface{}]interface{})
+                expectedResult3 := map[interface{}]interface{}{"name": "Elrond", "place": "Rivendell"}
+                assert.Equal(t, expectedResult3, actualResult3, "Expected " + expectedResult + " but got " + actualResult)
+            case "member4":
+                actualResult4 := param.Value.(map[interface{}]interface{})
+                expectedResult4 := map[interface{}]interface{}{"name": "Gimli", "place": "Gondor", "age": 139, "children": map[interface{}]interface{}{ "<none>": "<none>" }}
+                assert.Equal(t, expectedResult4, actualResult4, "Expected " + expectedResult + " but got " + actualResult)
+            case "member5":
+                actualResult5 := param.Value.(map[interface{}]interface{})
+                expectedResult5 := map[interface{}]interface{}{"name": "Gloin", "place": "Gondor", "age": 235, "children": map[interface{}]interface{}{ "Gimli": "Son" }}
+                assert.Equal(t, expectedResult5, actualResult5, "Expected " + expectedResult + " but got " + actualResult)
+            case "member6":
+                actualResult6 := param.Value.(map[interface{}]interface{})
+                expectedResult6 := map[interface{}]interface{}{"name": "Frodo", "place": "Undying Lands", "items": []interface{}{"Sting", "Mithril mail"}}
+                assert.Equal(t, expectedResult6, actualResult6, "Expected " + expectedResult + " but got " + actualResult)
+            }
+        }
+
+        // TODO{} We do not yet support json outputs
+        // validate outputs
+        // output payload is of type string and has a description
+        //if payload, ok := action.Outputs["fellowship"]; ok {
+        //    p := payload.(map[interface{}]interface{})
+        //}
+    }
+}
+
 func _createTmpfile(data string, filename string) (f *os.File, err error) {
     dir, _ := os.Getwd()
     tmpfile, err := ioutil.TempFile(dir, filename)
@@ -1256,7 +1333,7 @@ func TestInvalidKeyManifestYaml(t *testing.T) {
     _, err = p.ParseManifest(tmpfile.Name())
     assert.NotNil(t, err)
     // go-yaml/yaml prints the wrong line number for mapping values. It should be 4.
-    assert.Contains(t, err.Error(), "field invalidKey not found in struct parsers.Package: Line 2, its neighbor lines, or the lines on the same level")
+    assert.Contains(t, err.Error(), "line 2: field invalidKey not found in struct parsers.Package")
 }
 
 func TestMappingValueManifestYaml(t *testing.T) {
@@ -1277,7 +1354,7 @@ func TestMappingValueManifestYaml(t *testing.T) {
     _, err = p.ParseManifest(tmpfile.Name())
     assert.NotNil(t, err)
     // go-yaml/yaml prints the wrong line number for mapping values. It should be 5.
-    assert.Contains(t, err.Error(), "mapping values are not allowed in this context: Line 4, its neighbor lines, or the lines on the same level")
+    assert.Contains(t, err.Error(), "line 4: mapping values are not allowed in this context")
 }
 
 func TestMissingRootValueManifestYaml(t *testing.T) {
@@ -1295,7 +1372,7 @@ func TestMissingRootValueManifestYaml(t *testing.T) {
     p := NewYAMLParser()
     _, err = p.ParseManifest(tmpfile.Name())
     assert.NotNil(t, err)
-    assert.Contains(t, err.Error(), "field actions not found in struct parsers.YAML: Line 1, its neighbor lines, or the lines on the same level")
+    assert.Contains(t, err.Error(), "line 1: field actions not found in struct parsers.YAML")
 
 }
 
diff --git a/parsers/yamlparser.go b/parsers/yamlparser.go
index 944f624..4b7a2db 100644
--- a/parsers/yamlparser.go
+++ b/parsers/yamlparser.go
@@ -105,15 +105,15 @@ type Parameter struct {
 
 type Trigger struct {
 	//mapping to ????
-	Feed string `yaml:"feed"` //used in manifest.yaml
+	Feed	   string `yaml:"feed"` //used in manifest.yaml
 	//mapping to wsk.Trigger.Namespace
 	Namespace  string               `yaml:"namespace"`  //used in deployment.yaml
 	Credential string               `yaml:"credential"` //used in deployment.yaml
 	Inputs     map[string]Parameter `yaml:"inputs"`     //used in deployment.yaml
 	//mapping to wsk.Trigger.Name
-	Name        string
+	Name       string
 	Annotations map[string]interface{} `yaml:"annotations,omitempty"`
-	Source      string                 `yaml:source` // deprecated, used in manifest.yaml
+	Source     string                 `yaml:source` // deprecated, used in manifest.yaml
 	//Parameters  map[string]interface{} `yaml:parameters` // used in manifest.yaml
 }
 
diff --git a/tests/dat/manifest_bad_yaml_2.yaml b/tests/dat/manifest_bad_yaml_2.yaml
new file mode 100644
index 0000000..bd4d895
--- /dev/null
+++ b/tests/dat/manifest_bad_yaml_2.yaml
@@ -0,0 +1,8 @@
+package:
+  name: helloWorldTriggerRule
+  version: 1.0
+  invalidKey: test
+  license: Apache-2.0
+  invalidKey2: test
+
+# go-yaml/yaml prints the wrong line number for mapping values. It should be 4.
diff --git a/tests/dat/manifest_bad_yaml_3.yaml b/tests/dat/manifest_bad_yaml_3.yaml
new file mode 100644
index 0000000..bcffb6e
--- /dev/null
+++ b/tests/dat/manifest_bad_yaml_3.yaml
@@ -0,0 +1,8 @@
+packages:
+  helloWorldTriggerRule:
+    version: 1.0
+    license: Apache-2.0
+      actions: test
+
+# go-yaml/yaml prints the wrong line number for mapping values. It should be 5.
+
diff --git a/tests/dat/manifest_bad_yaml_4.yaml b/tests/dat/manifest_bad_yaml_4.yaml
new file mode 100644
index 0000000..da73f3e
--- /dev/null
+++ b/tests/dat/manifest_bad_yaml_4.yaml
@@ -0,0 +1,6 @@
+actions:
+  helloNodejs:
+    function: actions/hello.js
+
+# go-yaml/yaml prints the wrong line number for mapping values. It should be 5.
+
diff --git a/tests/dat/manifest_validate_json_params.yaml b/tests/dat/manifest_validate_json_params.yaml
new file mode 100644
index 0000000..dd773a0
--- /dev/null
+++ b/tests/dat/manifest_validate_json_params.yaml
@@ -0,0 +1,33 @@
+packages:
+  validate_json:
+    actions:
+      validate_json_params:
+        function: actions/dump_params.js
+        runtime: nodejs:6
+        inputs:
+          member1:
+            type: json
+            value: '{ "name": "Sam", "place": "Shire" }'
+          member2: { "name": "Sam", "place": "Shire" }
+          member3:
+            type: json
+            value: { "name": "Elrond", "place": "Rivendell" }
+          member4:
+            type: json
+            value: { "name": "Gimli", "place": "Gondor", "age": 139, "children": { "<none>": "<none>" } }
+          member5:
+            type: json
+            value: {
+              "name": "Gloin",
+              "place": "Gondor",
+              "age": 235,
+              "children": {
+                "Gimli": "Son"
+              }
+            }
+          member6:
+            type: json
+            value: { "name": "Frodo", "place": "Undying Lands", "items": [ "Sting", "Mithril mail" ] }
+        outputs:
+            fellowship:
+              type: json
diff --git a/utils/conversion.go b/utils/conversion.go
new file mode 100644
index 0000000..411cf9c
--- /dev/null
+++ b/utils/conversion.go
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package utils
+
+import "fmt"
+
+func convertInterfaceArray(in []interface{}) []interface{} {
+	res := make([]interface{}, len(in))
+	for i, v := range in {
+		res[i] = convertMapValue(v)
+	}
+	return res
+}
+
+func ConvertInterfaceMap(mapIn map[interface{}]interface{}) map[string]interface{} {
+	mapOut := make(map[string]interface{})
+	for k, v := range mapIn {
+		mapOut[fmt.Sprintf("%v", k)] = convertMapValue(v)
+	}
+	return mapOut
+}
+
+func convertMapValue(value interface{}) interface{} {
+	switch typedVal := value.(type) {
+	case []interface{}:
+		return convertInterfaceArray(typedVal)
+	case map[interface{}]interface{}:
+		return ConvertInterfaceMap(typedVal)
+	case string:
+		return typedVal
+	default:
+		return fmt.Sprintf("%v", typedVal)
+	}
+}
+
+func PrintTypeInfo(name string, value interface{})  {
+	info := fmt.Sprintf("Name=[%s], Value=[%v], Type=[%T]\n",name,value,value)
+	fmt.Print(info)
+}
diff --git a/utils/dependencies.go b/utils/dependencies.go
index ff569dc..5ece712 100644
--- a/utils/dependencies.go
+++ b/utils/dependencies.go
@@ -79,4 +79,3 @@ func LocationIsGithub(location string) bool {
 
 	return false
 }
-
diff --git a/utils/misc.go b/utils/misc.go
index 481407c..5775872 100644
--- a/utils/misc.go
+++ b/utils/misc.go
@@ -100,8 +100,8 @@ func PrettyJSON(j interface{}) (string, error) {
 	formatter := prettyjson.NewFormatter()
 	bytes, err := formatter.Marshal(j)
 	if err != nil {
-        return "", err
-    }
+        	return "", err
+    	}
 	return string(bytes), nil
 }
 
@@ -670,16 +670,15 @@ func (urlReader *URLReader) ReadUrl(url string) (content []byte, err error) {
 type LocalReader struct {
 }
 
-func (localReader *LocalReader) ReadLocal(path string) (content []byte, err error) {
+func (localReader *LocalReader) ReadLocal(path string) ([]byte, error) {
 	cont, err := ioutil.ReadFile(path)
 	return cont, err
 }
 
-func Read(url string) (content []byte, err error) {
+func Read(url string) ([]byte, error) {
 	if strings.HasPrefix(url, "http") {
 		return new(ContentReader).URLReader.ReadUrl(url)
 	} else {
 		return new(ContentReader).LocalReader.ReadLocal(url)
 	}
 }
-
diff --git a/utils/wskdeployerror.go b/utils/wskdeployerror.go
index 3037434..d699184 100644
--- a/utils/wskdeployerror.go
+++ b/utils/wskdeployerror.go
@@ -22,16 +22,17 @@ import (
     "runtime"
     "github.com/apache/incubator-openwhisk-wskdeploy/wski18n"
     "strings"
+    "path/filepath"
 )
 
 const (
     INVALID_YAML_INPUT = "Invalid input of Yaml file"
     INVALID_YAML_FORMAT = "Invalid input of Yaml format"
     OPENWHISK_CLIENT_ERROR = "OpenWhisk Client Error"
-    MANIFEST_NOT_FOUND = INVALID_YAML_INPUT
+    MANIFEST_NOT_FOUND = INVALID_YAML_INPUT  // TODO{} This should be a unique message.
     UNKNOWN = "Unknown"
+    UNKNOWN_VALUE = "Unknown value"
     LINE = "line"
-    PARSING_ERR = "YAML: Parsing errors:"
 )
 
 type TestCaseError struct {
@@ -80,7 +81,8 @@ func NewErrorManifestFileNotFound(errMessage string) *ErrorManifestFileNotFound
     var err = &ErrorManifestFileNotFound{
         errorType: wski18n.T(MANIFEST_NOT_FOUND),
     }
-    err.SetFileName(fn)
+    //err.SetFileName(fn)
+    err.SetFileName(filepath.Base(fn))
     err.SetLineNum(lineNum)
     err.SetMessage(errMessage)
     return err
@@ -100,7 +102,7 @@ func NewInputYamlFileError(errMessage string) *InputYamlFileError {
     var err = &InputYamlFileError{
         errorType: wski18n.T(INVALID_YAML_INPUT),
     }
-    err.SetFileName(fn)
+    err.SetFileName(filepath.Base(fn))
     err.SetLineNum(lineNum)
     err.SetMessage(errMessage)
     return err
@@ -111,7 +113,7 @@ func (e *InputYamlFileError) SetErrorType(errorType string) {
 }
 
 func (e *InputYamlFileError) Error() string {
-    return fmt.Sprintf("%s [%d]: %s =====> %s\n", e.FileName, e.LineNum, e.errorType, e.Message)
+    return fmt.Sprintf("%s [%d]: %s %s\n", e.FileName, e.LineNum, e.errorType, e.Message)
 }
 
 type InputYamlFormatError struct {
@@ -184,14 +186,16 @@ func NewParserErr(yamlFile string, lines []string, msgs []string) *ParserErr {
 
 func (e *ParserErr) Error() string {
     result := make([]string, len(e.msgs))
-    for index, each := range e.msgs {
+    var fn = filepath.Base(e.FileName)
+
+    for index, msg := range e.msgs {
         var s string
-        if e.lines[index] == UNKNOWN {
-            s = fmt.Sprintf("%s", PARSING_ERR)
-        } else {
-            s = fmt.Sprintf("%s: Line %s, its neighbor lines, or the lines on the same level.", each, e.lines[index])
+        if e.lines == nil || e.lines[index] == UNKNOWN {
+            s = fmt.Sprintf("====> %s", msg)
+        } else{
+            s = fmt.Sprintf("====> Line [%v]: %s", e.lines[index], msg)
         }
         result[index] = s
     }
-    return fmt.Sprintf("%s [%d]:\n Failed to parse the yaml file %s\n =====> %s\n", e.FileName, e.LineNum, e.YamlFile, strings.Join(result, "\n "))
+    return fmt.Sprintf("\n==> %s [%d]: Failed to parse the yaml file: %s: \n%s", fn, e.LineNum, e.YamlFile, strings.Join(result, "\n"))
 }

-- 
To stop receiving notification emails like this one, please contact
['"commits@openwhisk.apache.org" <co...@openwhisk.apache.org>'].