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 a801742 Separate out parameter related functions from general parser. (#625) a801742 is described below commit a8017420e749f8aebdfa521e1d93c51399d8f574 Author: Matt Rutkowski <mrutk...@us.ibm.com> AuthorDate: Tue Oct 24 10:21:35 2017 -0500 Separate out parameter related functions from general parser. (#625) * Separate out parameter related functions from general parser. * Separate out parameter related functions from general parser. --- parsers/manifest_parser.go | 525 ++++++++++----------------------------------- parsers/parameters.go | 345 +++++++++++++++++++++++++++++ 2 files changed, 454 insertions(+), 416 deletions(-) diff --git a/parsers/manifest_parser.go b/parsers/manifest_parser.go index 62098b8..c38eaaa 100644 --- a/parsers/manifest_parser.go +++ b/parsers/manifest_parser.go @@ -22,11 +22,9 @@ import ( "io/ioutil" "os" "path" - "reflect" "strings" "encoding/base64" - "encoding/json" "fmt" "github.com/apache/incubator-openwhisk-client-go/whisk" @@ -105,26 +103,27 @@ func (dm *YAMLParser) ParseManifest(manifestPath string) (*YAML, error) { func (dm *YAMLParser) ComposeDependenciesFromAllPackages(manifest *YAML, projectPath string, filePath string) (map[string]utils.DependencyRecord, error) { dependencies := make(map[string]utils.DependencyRecord) - packages := make(map[string]Package) + packages := make(map[string]Package) if manifest.Package.Packagename != "" { return dm.ComposeDependencies(manifest.Package, projectPath, filePath, manifest.Package.Packagename) } else { - if manifest.Packages != nil { - packages = manifest.Packages - } else { - packages = manifest.Application.Packages - } - } - for n, p := range packages { - d, err := dm.ComposeDependencies(p, projectPath, filePath, n) - if err == nil { - for k, v := range d { - dependencies[k] = v - } - } else { - return nil, err - } - } + if manifest.Packages != nil { + packages = manifest.Packages + } else { + packages = manifest.Application.Packages + } + } + + for n, p := range packages { + d, err := dm.ComposeDependencies(p, projectPath, filePath, n) + if err == nil { + for k, v := range d { + dependencies[k] = v + } + } else { + return nil, err + } + } return dependencies, nil } @@ -194,7 +193,8 @@ func (dm *YAMLParser) ComposeDependencies(pkg Package, projectPath string, fileP func (dm *YAMLParser) ComposeAllPackages(manifest *YAML, filePath string) (map[string]*whisk.Package, error) { packages := map[string]*whisk.Package{} - manifestPackages := make(map[string]Package) + manifestPackages := make(map[string]Package) + if manifest.Package.Packagename != "" { fmt.Println("WARNING: using package inside of manifest file will soon be deprecated, please use packages instead.") s, err := dm.ComposePackage(manifest.Package, manifest.Package.Packagename, filePath) @@ -204,21 +204,22 @@ func (dm *YAMLParser) ComposeAllPackages(manifest *YAML, filePath string) (map[s return nil, err } } else { - if manifest.Packages != nil { - manifestPackages = manifest.Packages - } else { - manifestPackages = manifest.Application.Packages - } - } - - for n, p := range manifestPackages { - s, err := dm.ComposePackage(p, n, filePath) - if err == nil { - packages[n] = s - } else { - return nil, err - } - } + if manifest.Packages != nil { + manifestPackages = manifest.Packages + } else { + manifestPackages = manifest.Application.Packages + } + } + + for n, p := range manifestPackages { + s, err := dm.ComposePackage(p, n, filePath) + + if err == nil { + packages[n] = s + } else { + return nil, err + } + } return packages, nil } @@ -281,29 +282,33 @@ func (dm *YAMLParser) ComposePackage(pkg Package, packageName string, filePath s func (dm *YAMLParser) ComposeSequencesFromAllPackages(namespace string, mani *YAML) ([]utils.ActionRecord, error) { var s1 []utils.ActionRecord = make([]utils.ActionRecord, 0) - manifestPackages := make(map[string]Package) + + manifestPackages := make(map[string]Package) + if mani.Package.Packagename != "" { return dm.ComposeSequences(namespace, mani.Package.Sequences, mani.Package.Packagename) } else { - if mani.Packages != nil { - manifestPackages = mani.Packages - } else { - manifestPackages = mani.Application.Packages - } - } - for n, p := range manifestPackages { - s, err := dm.ComposeSequences(namespace, p.Sequences, n) - if err == nil { - s1 = append(s1, s...) - } else { - return nil, err - } - } + if mani.Packages != nil { + manifestPackages = mani.Packages + } else { + manifestPackages = mani.Application.Packages + } + } + + for n, p := range manifestPackages { + s, err := dm.ComposeSequences(namespace, p.Sequences, n) + if err == nil { + s1 = append(s1, s...) + } else { + return nil, err + } + } return s1, nil } func (dm *YAMLParser) ComposeSequences(namespace string, sequences map[string]Sequence, packageName string) ([]utils.ActionRecord, error) { var s1 []utils.ActionRecord = make([]utils.ActionRecord, 0) + for key, sequence := range sequences { wskaction := new(whisk.Action) wskaction.Exec = new(whisk.Exec) @@ -347,6 +352,7 @@ 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) + if manifest.Package.Packagename != "" { return dm.ComposeActions(filePath, manifest.Package.Actions, manifest.Package.Packagename) } else { @@ -588,30 +594,32 @@ func (dm *YAMLParser) ComposeActions(filePath string, actions map[string]Action, func (dm *YAMLParser) ComposeTriggersFromAllPackages(manifest *YAML, filePath string) ([]*whisk.Trigger, error) { var triggers []*whisk.Trigger = make([]*whisk.Trigger, 0) - manifestPackages := make(map[string]Package) + manifestPackages := make(map[string]Package) + if manifest.Package.Packagename != "" { return dm.ComposeTriggers(filePath, manifest.Package) } else { - if manifest.Packages != nil { - manifestPackages = manifest.Packages - } else { - manifestPackages = manifest.Application.Packages - } - } - for _, p := range manifestPackages { - t, err := dm.ComposeTriggers(filePath, p) - if err == nil { - triggers = append(triggers, t...) - } else { - return nil, err - } - } + if manifest.Packages != nil { + manifestPackages = manifest.Packages + } else { + manifestPackages = manifest.Application.Packages + } + } + for _, p := range manifestPackages { + t, err := dm.ComposeTriggers(filePath, p) + if err == nil { + triggers = append(triggers, t...) + } else { + return nil, err + } + } return triggers, nil } func (dm *YAMLParser) ComposeTriggers(filePath string, pkg Package) ([]*whisk.Trigger, error) { var errorParser error var t1 []*whisk.Trigger = make([]*whisk.Trigger, 0) + for _, trigger := range pkg.GetTriggerList() { wsktrigger := new(whisk.Trigger) wsktrigger.Name = trigger.Name @@ -667,30 +675,33 @@ func (dm *YAMLParser) ComposeTriggers(filePath string, pkg Package) ([]*whisk.Tr func (dm *YAMLParser) ComposeRulesFromAllPackages(manifest *YAML) ([]*whisk.Rule, error) { var rules []*whisk.Rule = make([]*whisk.Rule, 0) - manifestPackages := make(map[string]Package) + manifestPackages := make(map[string]Package) + if manifest.Package.Packagename != "" { return dm.ComposeRules(manifest.Package, manifest.Package.Packagename) } else { - if manifest.Packages != nil { - manifestPackages = manifest.Packages - } else { - manifestPackages = manifest.Application.Packages - } - } - for n, p := range manifestPackages { - r, err := dm.ComposeRules(p, n) - if err == nil { - rules = append(rules, r...) - } else { - return nil, err - } - } + if manifest.Packages != nil { + manifestPackages = manifest.Packages + } else { + manifestPackages = manifest.Application.Packages + } + } + + for n, p := range manifestPackages { + r, err := dm.ComposeRules(p, n) + if err == nil { + rules = append(rules, r...) + } else { + return nil, err + } + } return rules, nil } func (dm *YAMLParser) ComposeRules(pkg Package, packageName string) ([]*whisk.Rule, error) { var r1 []*whisk.Rule = make([]*whisk.Rule, 0) + for _, rule := range pkg.GetRuleList() { wskrule := rule.ComposeWskRule() act := strings.TrimSpace(wskrule.Action.(string)) @@ -705,30 +716,33 @@ func (dm *YAMLParser) ComposeRules(pkg Package, packageName string) ([]*whisk.Ru func (dm *YAMLParser) ComposeApiRecordsFromAllPackages(manifest *YAML) ([]*whisk.ApiCreateRequest, error) { var requests []*whisk.ApiCreateRequest = make([]*whisk.ApiCreateRequest, 0) - manifestPackages := make(map[string]Package) + manifestPackages := make(map[string]Package) + if manifest.Package.Packagename != "" { return dm.ComposeApiRecords(manifest.Package) } else { - if manifest.Packages != nil { - manifestPackages = manifest.Packages - } else { - manifestPackages = manifest.Application.Packages - } - } - for _, p := range manifestPackages { - r, err := dm.ComposeApiRecords(p) - if err == nil { - requests = append(requests, r...) - } else { - return nil, err - } - } + if manifest.Packages != nil { + manifestPackages = manifest.Packages + } else { + manifestPackages = manifest.Application.Packages + } + } + + for _, p := range manifestPackages { + r, err := dm.ComposeApiRecords(p) + if err == nil { + requests = append(requests, r...) + } else { + return nil, err + } + } return requests, nil } func (dm *YAMLParser) ComposeApiRecords(pkg Package) ([]*whisk.ApiCreateRequest, error) { var acq []*whisk.ApiCreateRequest = make([]*whisk.ApiCreateRequest, 0) apis := pkg.GetApis() + for _, api := range apis { acr := new(whisk.ApiCreateRequest) acr.ApiDoc = api @@ -736,324 +750,3 @@ func (dm *YAMLParser) ComposeApiRecords(pkg Package) ([]*whisk.ApiCreateRequest, } return acq, nil } - -// TODO(): Support other valid Package Manifest types -// TODO(): i.e., timestamp, version, string256, string64, string16 -// TODO(): Support JSON schema validation for type: json -// TODO(): Support OpenAPI schema validation - -var validParameterNameMap = map[string]string{ - "string": "string", - "int": "integer", - "float": "float", - "bool": "boolean", - "int8": "integer", - "int16": "integer", - "int32": "integer", - "int64": "integer", - "float32": "float", - "float64": "float", - "json": "json", - "map": "json", -} - -var typeDefaultValueMap = map[string]interface{}{ - "string": "", - "integer": 0, - "float": 0.0, - "boolean": false, - "json": make(map[string]interface{}), - // TODO() Support these types + their validation - // timestamp - // null - // version - // string256 - // string64 - // string16 - // scalar-unit - // schema - // object -} - -func isValidParameterType(typeName string) bool { - _, isValid := typeDefaultValueMap[typeName] - return isValid -} - -// TODO(): throw errors -func getTypeDefaultValue(typeName string) interface{} { - - if val, ok := typeDefaultValueMap[typeName]; ok { - return val - } else { - // TODO() throw an error "type not found" - } - return nil -} - -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 - - if value != nil { - actualType := reflect.TypeOf(value).Kind().String() - - // See if the actual type of the value is valid - if normalizedTypeName, found := validParameterNameMap[actualType]; found { - // use the full spec. name - paramType = normalizedTypeName - - } else { - // 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) - } - } - return paramType, err -} - - -/* - resolveSingleLineParameter assures that a Parameter's Type is correctly identified and set from its Value. - - Additionally, this function: - - - detects if the parameter value contains the name of a valid OpenWhisk parameter types. if so, the - - param.Type is set to detected OpenWhisk parameter type. - - param.Value is set to the zero (default) value for that OpenWhisk parameter type. - */ -func resolveSingleLineParameter(paramName string, param *Parameter, filePath string) (interface{}, error) { - var errorParser error - - if !param.multiline { - // We need to identify parameter Type here for later validation - 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. - if param.Value != nil && param.Type == "string" { - // The value is a <string>; now we must test if is the name of a known Type - if isValidParameterType(param.Value.(string)) { - // If the value is indeed the name of a Type, we must change BOTH its - // Type to be that type and its value to that Type's default value - param.Type = param.Value.(string) - param.Value = getTypeDefaultValue(param.Type) - fmt.Printf("EXIT: Parameter [%s] type=[%v] value=[%v]\n", paramName, param.Type, param.Value) - } - } - - } else { - msgs := []string{"Parameter [" + paramName + "] is not single-line format."} - return param.Value, utils.NewParserErr(filePath, nil, msgs) - } - - return param.Value, errorParser -} - -/* - resolveMultiLineParameter assures that the values for Parameter Type and Value are properly set and are valid. - - Additionally, this function: - - uses param.Default as param.Value if param.Value is not provided - - uses the actual param.Value data type for param.type if param.Type is not provided - - */ -func resolveMultiLineParameter(paramName string, param *Parameter, filePath string) (interface{}, error) { - var errorParser error - - if param.multiline { - var valueType string - - // if we do not have a value, but have a default, use it for the value - if param.Value == nil && param.Default != nil { - param.Value = param.Default - } - - // Note: if either the value or default is in conflict with the type then this is an error - valueType, errorParser = ResolveParamTypeFromValue(paramName, param.Value, filePath) - - // if we have a declared parameter Type, assure that it is a known value - if param.Type != "" { - if !isValidParameterType(param.Type) { - // TODO() - move string to i18n - msgs := []string{"Parameter [" + paramName + "] has an invalid Type. [" + param.Type + "]"} - return param.Value, utils.NewParserErr(filePath, nil, msgs) - } - } else { - // if we do not have a value for the Parameter Type, use the Parameter Value's Type - param.Type = valueType - } - - // TODO{} if the declared and actual parameter type conflict, generate TypeMismatch error - //if param.Type != valueType{ - // errorParser = utils.NewParameterTypeMismatchError("", param.Type, valueType ) - //} - } else { - msgs := []string{"Parameter [" + paramName + "] is not multiline format."} - return param.Value, utils.NewParserErr(filePath, nil, msgs) - } - - - return param.Value, errorParser -} - - -/* - resolveJSONParameter assure JSON data is converted to a map[string]{interface*} type. - - This function handles the forms JSON data appears in: - 1) a string containing JSON, which needs to be parsed into map[string]interface{} - 2) is a map of JSON (but not a map[string]interface{} - */ -func resolveJSONParameter(paramName string, param *Parameter, value interface{}, filePath string) (interface{}, error) { - var errorParser error - - if param.Type == "json" { - // Case 1: if user set parameter type to 'json' and the value's type is a 'string' - if str, ok := value.(string); ok { - var parsed interface{} - errParser := json.Unmarshal([]byte(str), &parsed) - if errParser == nil { - //fmt.Printf("EXIT: Parameter [%s] type=[%v] value=[%v]\n", paramName, param.Type, parsed) - return parsed, errParser - } - } - - // 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 [%s] type=[%v] value=[%v]\n", paramName, param.Type, temp) - return temp, errorParser - } - } // else TODO{} - } else { - msgs := []string{"Parameter [" + paramName + "] is not JSON format."} - return param.Value, utils.NewParserErr(filePath, nil, msgs) - } - - return param.Value, errorParser -} - -/* - ResolveParameter assures that the Parameter structure's values are correctly filled out for - further processing. This includes special processing for - - - single-line format parameters - - deriving missing param.Type from param.Value - - resolving case where param.Value contains a valid Parameter type name - - multi-line format parameters: - - assures that param.Value is set while taking into account param.Default - - validating param.Type - - Note: parameter values may set later (overridden) by an (optional) Deployment file - - */ -func ResolveParameter(paramName string, param *Parameter, filePath string) (interface{}, error) { - - var errorParser error - // default parameter value to empty string - var value interface{} = "" - - // Trace Parameter struct before any resolution - //dumpParameter(paramName, param, "BEFORE") - - // Parameters can be single OR multi-line declarations which must be processed/validated differently - if !param.multiline { - - // This function will assure that param.Value and param.Type are correctly set - value, errorParser = resolveSingleLineParameter(paramName, param, filePath) - - } else { - - value, errorParser = resolveMultiLineParameter(paramName, param, filePath) - } - - // String value pre-processing (interpolation) - // See if we have any Environment Variable replacement within the parameter's value - - // Make sure the parameter's value is a valid, non-empty string - if ( param.Value != nil && param.Type == "string") { - // perform $ notation replacement on string if any exist - value = utils.GetEnvVar(param.Value) - } - - // JSON - Handle both cases, where value 1) is a string containing JSON, 2) is a map of JSON - if param.Type == "json" { - value, errorParser = resolveJSONParameter(paramName, param, value, filePath) - } - - // Default value to zero value for the Type - // Do NOT error/terminate as Value may be provided later by a Deployment file. - if value == nil { - value = getTypeDefaultValue(param.Type) - // @TODO(): Need warning message here to warn of default usage, support for warnings (non-fatal) - //msgs := []string{"Parameter [" + paramName + "] is not multiline format."} - //return param.Value, utils.NewParserErr(filePath, nil, msgs) - } - - // Trace Parameter struct after resolution - //dumpParameter(paramName, param, "AFTER") - //fmt.Printf("EXIT: Parameter [%s] type=[%v] value=[%v]\n", paramName, param.Type, value) - return value, errorParser -} - -// Provide custom Parameter marshalling and unmarshalling - -type ParsedParameter Parameter - -func (n *Parameter) UnmarshalYAML(unmarshal func(interface{}) error) error { - var aux ParsedParameter - - // Attempt to unmarshall the multi-line schema - if err := unmarshal(&aux); err == nil { - n.multiline = true - n.Type = aux.Type - n.Description = aux.Description - n.Value = aux.Value - n.Required = aux.Required - n.Default = aux.Default - n.Status = aux.Status - n.Schema = aux.Schema - return nil - } - - // If we did not find the multi-line schema, assume in-line (or single-line) schema - var inline interface{} - if err := unmarshal(&inline); err != nil { - return err - } - - n.Value = inline - n.multiline = false - return nil -} - -func (n *Parameter) MarshalYAML() (interface{}, error) { - if _, ok := n.Value.(string); len(n.Type) == 0 && len(n.Description) == 0 && ok { - if !n.Required && len(n.Status) == 0 && n.Schema == nil { - return n.Value.(string), nil - } - } - - return n, nil -} - -// Provides debug/trace support for Parameter type -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.Description: [%s]\n", param.Description) - fmt.Printf("\t\tParameter.Type: [%s]\n", param.Type) - fmt.Printf("\t\t--> Actual Type: [%T]\n", param.Value) - fmt.Printf("\t\tParameter.Value: [%v]\n", param.Value) - fmt.Printf("\t\tParameter.Default: [%v]\n", param.Default) - } -} diff --git a/parsers/parameters.go b/parsers/parameters.go new file mode 100644 index 0000000..2c82060 --- /dev/null +++ b/parsers/parameters.go @@ -0,0 +1,345 @@ +/* + * 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 parsers + +import ( + "fmt" + "reflect" + "encoding/json" + "github.com/apache/incubator-openwhisk-wskdeploy/utils" +) + + +// TODO(): Support other valid Package Manifest types +// TODO(): i.e., timestamp, version, string256, string64, string16 +// TODO(): Support JSON schema validation for type: json +// TODO(): Support OpenAPI schema validation + +var validParameterNameMap = map[string]string{ + "string": "string", + "int": "integer", + "float": "float", + "bool": "boolean", + "int8": "integer", + "int16": "integer", + "int32": "integer", + "int64": "integer", + "float32": "float", + "float64": "float", + "json": "json", + "map": "json", +} + +var typeDefaultValueMap = map[string]interface{}{ + "string": "", + "integer": 0, + "float": 0.0, + "boolean": false, + "json": make(map[string]interface{}), + // TODO() Support these types + their validation + // timestamp + // null + // version + // string256 + // string64 + // string16 + // scalar-unit + // schema + // object +} + +func isValidParameterType(typeName string) bool { + _, isValid := typeDefaultValueMap[typeName] + return isValid +} + +// TODO(): throw errors +func getTypeDefaultValue(typeName string) interface{} { + + if val, ok := typeDefaultValueMap[typeName]; ok { + return val + } else { + // TODO() throw an error "type not found" + } + return nil +} + +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 + + if value != nil { + actualType := reflect.TypeOf(value).Kind().String() + + // See if the actual type of the value is valid + if normalizedTypeName, found := validParameterNameMap[actualType]; found { + // use the full spec. name + paramType = normalizedTypeName + + } else { + // 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) + } + } + return paramType, err +} + + +/* + resolveSingleLineParameter assures that a Parameter's Type is correctly identified and set from its Value. + + Additionally, this function: + + - detects if the parameter value contains the name of a valid OpenWhisk parameter types. if so, the + - param.Type is set to detected OpenWhisk parameter type. + - param.Value is set to the zero (default) value for that OpenWhisk parameter type. + */ +func resolveSingleLineParameter(paramName string, param *Parameter, filePath string) (interface{}, error) { + var errorParser error + + if !param.multiline { + // We need to identify parameter Type here for later validation + 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. + if param.Value != nil && param.Type == "string" { + // The value is a <string>; now we must test if is the name of a known Type + if isValidParameterType(param.Value.(string)) { + // If the value is indeed the name of a Type, we must change BOTH its + // Type to be that type and its value to that Type's default value + param.Type = param.Value.(string) + param.Value = getTypeDefaultValue(param.Type) + fmt.Printf("EXIT: Parameter [%s] type=[%v] value=[%v]\n", paramName, param.Type, param.Value) + } + } + + } else { + msgs := []string{"Parameter [" + paramName + "] is not single-line format."} + return param.Value, utils.NewParserErr(filePath, nil, msgs) + } + + return param.Value, errorParser +} + +/* + resolveMultiLineParameter assures that the values for Parameter Type and Value are properly set and are valid. + + Additionally, this function: + - uses param.Default as param.Value if param.Value is not provided + - uses the actual param.Value data type for param.type if param.Type is not provided + + */ +func resolveMultiLineParameter(paramName string, param *Parameter, filePath string) (interface{}, error) { + var errorParser error + + if param.multiline { + var valueType string + + // if we do not have a value, but have a default, use it for the value + if param.Value == nil && param.Default != nil { + param.Value = param.Default + } + + // Note: if either the value or default is in conflict with the type then this is an error + valueType, errorParser = ResolveParamTypeFromValue(paramName, param.Value, filePath) + + // if we have a declared parameter Type, assure that it is a known value + if param.Type != "" { + if !isValidParameterType(param.Type) { + // TODO() - move string to i18n + msgs := []string{"Parameter [" + paramName + "] has an invalid Type. [" + param.Type + "]"} + return param.Value, utils.NewParserErr(filePath, nil, msgs) + } + } else { + // if we do not have a value for the Parameter Type, use the Parameter Value's Type + param.Type = valueType + } + + // TODO{} if the declared and actual parameter type conflict, generate TypeMismatch error + //if param.Type != valueType{ + // errorParser = utils.NewParameterTypeMismatchError("", param.Type, valueType ) + //} + } else { + msgs := []string{"Parameter [" + paramName + "] is not multiline format."} + return param.Value, utils.NewParserErr(filePath, nil, msgs) + } + + + return param.Value, errorParser +} + + +/* + resolveJSONParameter assure JSON data is converted to a map[string]{interface*} type. + + This function handles the forms JSON data appears in: + 1) a string containing JSON, which needs to be parsed into map[string]interface{} + 2) is a map of JSON (but not a map[string]interface{} + */ +func resolveJSONParameter(paramName string, param *Parameter, value interface{}, filePath string) (interface{}, error) { + var errorParser error + + if param.Type == "json" { + // Case 1: if user set parameter type to 'json' and the value's type is a 'string' + if str, ok := value.(string); ok { + var parsed interface{} + errParser := json.Unmarshal([]byte(str), &parsed) + if errParser == nil { + //fmt.Printf("EXIT: Parameter [%s] type=[%v] value=[%v]\n", paramName, param.Type, parsed) + return parsed, errParser + } + } + + // 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 [%s] type=[%v] value=[%v]\n", paramName, param.Type, temp) + return temp, errorParser + } + } // else TODO{} + } else { + msgs := []string{"Parameter [" + paramName + "] is not JSON format."} + return param.Value, utils.NewParserErr(filePath, nil, msgs) + } + + return param.Value, errorParser +} + +/* + ResolveParameter assures that the Parameter structure's values are correctly filled out for + further processing. This includes special processing for + + - single-line format parameters + - deriving missing param.Type from param.Value + - resolving case where param.Value contains a valid Parameter type name + - multi-line format parameters: + - assures that param.Value is set while taking into account param.Default + - validating param.Type + + Note: parameter values may set later (overridden) by an (optional) Deployment file + + */ +func ResolveParameter(paramName string, param *Parameter, filePath string) (interface{}, error) { + + var errorParser error + // default parameter value to empty string + var value interface{} = "" + + // Trace Parameter struct before any resolution + //dumpParameter(paramName, param, "BEFORE") + + // Parameters can be single OR multi-line declarations which must be processed/validated differently + if !param.multiline { + + // This function will assure that param.Value and param.Type are correctly set + value, errorParser = resolveSingleLineParameter(paramName, param, filePath) + + } else { + + value, errorParser = resolveMultiLineParameter(paramName, param, filePath) + } + + // String value pre-processing (interpolation) + // See if we have any Environment Variable replacement within the parameter's value + + // Make sure the parameter's value is a valid, non-empty string + if ( param.Value != nil && param.Type == "string") { + // perform $ notation replacement on string if any exist + value = utils.GetEnvVar(param.Value) + } + + // JSON - Handle both cases, where value 1) is a string containing JSON, 2) is a map of JSON + if param.Type == "json" { + value, errorParser = resolveJSONParameter(paramName, param, value, filePath) + } + + // Default value to zero value for the Type + // Do NOT error/terminate as Value may be provided later by a Deployment file. + if value == nil { + value = getTypeDefaultValue(param.Type) + // @TODO(): Need warning message here to warn of default usage, support for warnings (non-fatal) + //msgs := []string{"Parameter [" + paramName + "] is not multiline format."} + //return param.Value, utils.NewParserErr(filePath, nil, msgs) + } + + // Trace Parameter struct after resolution + //dumpParameter(paramName, param, "AFTER") + //fmt.Printf("EXIT: Parameter [%s] type=[%v] value=[%v]\n", paramName, param.Type, value) + return value, errorParser +} + +// Provide custom Parameter marshalling and unmarshalling +type ParsedParameter Parameter + +func (n *Parameter) UnmarshalYAML(unmarshal func(interface{}) error) error { + var aux ParsedParameter + + // Attempt to unmarshall the multi-line schema + if err := unmarshal(&aux); err == nil { + n.multiline = true + n.Type = aux.Type + n.Description = aux.Description + n.Value = aux.Value + n.Required = aux.Required + n.Default = aux.Default + n.Status = aux.Status + n.Schema = aux.Schema + return nil + } + + // If we did not find the multi-line schema, assume in-line (or single-line) schema + var inline interface{} + if err := unmarshal(&inline); err != nil { + return err + } + + n.Value = inline + n.multiline = false + return nil +} + +func (n *Parameter) MarshalYAML() (interface{}, error) { + if _, ok := n.Value.(string); len(n.Type) == 0 && len(n.Description) == 0 && ok { + if !n.Required && len(n.Status) == 0 && n.Schema == nil { + return n.Value.(string), nil + } + } + + return n, nil +} + +// Provides debug/trace support for Parameter type +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.Description: [%s]\n", param.Description) + fmt.Printf("\t\tParameter.Type: [%s]\n", param.Type) + fmt.Printf("\t\t--> Actual Type: [%T]\n", param.Value) + fmt.Printf("\t\tParameter.Value: [%v]\n", param.Value) + fmt.Printf("\t\tParameter.Default: [%v]\n", param.Default) + } +} -- To stop receiving notification emails like this one, please contact ['"commits@openwhisk.apache.org" <commits@openwhisk.apache.org>'].