/*
 * 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 commands

import (
	"bufio"
	"bytes"
	"errors"
	"fmt"
	"reflect"
	"strconv"
	"strings"

	"github.com/apache/openwhisk-cli/wski18n"
	"github.com/apache/openwhisk-client-go/whisk"

	"encoding/json"
	"github.com/fatih/color"
	"github.com/ghodss/yaml"
	"github.com/spf13/cobra"
	"regexp"
)

const (
	yamlFileExtension = "yaml"
	ymlFileExtension  = "yml"

	formatOptionYaml = "yaml"
	formatOptionJson = "json"

	pathParamRegex        = `\/\{([^\/]+)\}\/|\/\{([^\/]+)\}$|\{([^\/]+)}\/`
	pathSegmentParamRegex = `^\/\{([^\/]+)\}\/$`
)

var apiCmd = &cobra.Command{
	Use:   "api",
	Short: wski18n.T("work with APIs"),
}

var fmtString = "%-30s %7s %20s  %s\n"

// When set, this overrides the default authkey based api context id
var ContextId string

// When set, this overrides the default access token used to authenticate with the api gw
var ApiGwAccessToken string

func IsValidApiVerb(verb string) (error, bool) {
	// Is the API verb valid?
	if _, ok := whisk.ApiVerbs[strings.ToUpper(verb)]; !ok {
		whisk.Debug(whisk.DbgError, "Invalid API verb: '%s'\n", verb)
		errMsg := wski18n.T("'{{.verb}}' is not a valid API verb.  Valid values are: {{.verbs}}",
			map[string]interface{}{
				"verb":  verb,
				"verbs": reflect.ValueOf(whisk.ApiVerbs).MapKeys()})
		whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL,
			whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
		return whiskErr, false
	}
	return nil, true
}

func hasPathPrefix(path string) (error, bool) {
	if !strings.HasPrefix(path, "/") {
		whisk.Debug(whisk.DbgError, "path does not begin with '/': '%s'\n", path)
		errMsg := wski18n.T("'{{.path}}' must begin with '/'.",
			map[string]interface{}{
				"path": path,
			})
		whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL,
			whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
		return whiskErr, false
	}
	return nil, true
}

func isValidBasepath(basepath string) (error, bool) {
	if whiskerr, ok := hasPathPrefix(basepath); !ok {
		return whiskerr, false
	}
	return nil, true
}

func isValidRelpath(relpath string) (error, bool) {
	if whiskerr, ok := hasPathPrefix(relpath); !ok {
		return whiskerr, false
	}
	return nil, true
}

func getPathParameterNames(path string) ([]string, error) {
	var pathParameters []string

	regexObj, err := regexp.Compile(pathSegmentParamRegex)
	if err != nil {
		whisk.Debug(whisk.DbgError, "Failed to match path '%s' to regular expressions `%s`\n", path, pathSegmentParamRegex)
	} else {
		segments := strings.Split(path, "/")
		for _, segment := range segments {
			segment = fmt.Sprintf("/%s/", segment)
			matchedItems := regexObj.FindAllStringSubmatch(segment, -1)
			for _, matchedParam := range matchedItems {
				for idx, paramName := range matchedParam {
					whisk.Debug(whisk.DbgInfo, "Path parameter submatch '%v'; idx %v\n", paramName, idx)
					if idx > 0 && len(paramName) > 0 {
						pathParameters = append(pathParameters, paramName)
					}
				}
			}
		}
	}

	return pathParameters, err
}

func hasPathParameters(path string) (bool, error) {
	pathParams, err := getPathParameterNames(path)
	return len(pathParams) > 0, err
}

func isBasepathParameterized(basepath string) (error, bool) {
	hasParams, err := hasPathParameters(basepath)
	if hasParams || err != nil {
		errMsg := wski18n.T("The base path '{{.path}}' cannot have parameters. Only the relative path supports path parameters.",
			map[string]interface{}{"path": basepath})
		whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL,
			whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
		return whiskErr, false
	}
	return nil, true
}

/*
 * Pull the managedUrl (external API URL) from the API configuration
 */
func getManagedUrl(api *whisk.RetApi, relpath string, operation string) (url string) {
	baseUrl := strings.TrimSuffix(api.BaseUrl, "/")
	whisk.Debug(whisk.DbgInfo, "getManagedUrl: baseUrl = '%s', relpath = '%s', operation = '%s'\n", baseUrl, relpath, operation)
	for path := range api.Swagger.Paths {
		whisk.Debug(whisk.DbgInfo, "getManagedUrl: comparing api relpath: '%s'\n", path)
		if path == relpath {
			whisk.Debug(whisk.DbgInfo, "getManagedUrl: relpath matches '%s'\n", relpath)
			for op := range api.Swagger.Paths[path].MakeOperationMap() {
				whisk.Debug(whisk.DbgInfo, "getManagedUrl: comparing operation: '%s'\n", op)
				if strings.ToLower(op) == strings.ToLower(operation) {
					whisk.Debug(whisk.DbgInfo, "getManagedUrl: operation matches: '%s'\n", operation)
					url = baseUrl + path
				}
			}
		}
	}
	return url
}

//////////////
// Commands //
//////////////
var apiCreateCmd = &cobra.Command{
	Use:           "create ([BASE_PATH] API_PATH API_VERB ACTION] | --config-file CFG_FILE) ",
	Short:         wski18n.T("create a new API"),
	SilenceUsage:  true,
	SilenceErrors: true,
	PreRunE:       SetupClientConfig,
	RunE: func(cmd *cobra.Command, args []string) error {
		var api *whisk.Api
		var err error
		var qname = new(QualifiedName)
		var action *whisk.Action
		var webactionSecret interface{}

		if len(args) == 0 && Flags.api.configfile == "" {
			whisk.Debug(whisk.DbgError, "No swagger file and no arguments\n")
			errMsg := wski18n.T("Invalid argument(s). Specify a swagger file or specify an API base path with an API path, an API verb, and an action name.")
			whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL,
				whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
			return whiskErr
		} else if len(args) == 0 && Flags.api.configfile != "" {
			api, err = parseSwaggerApi(Flags.api.configfile, Client.Config.Namespace)
			if err != nil {
				whisk.Debug(whisk.DbgError, "parseSwaggerApi() error: %s\n", err)
				errMsg := wski18n.T("Unable to parse swagger file: {{.err}}", map[string]interface{}{"err": err})
				whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_GENERAL,
					whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
				return whiskErr
			}
		} else {
			if whiskErr := CheckArgs(args, 3, 4, "Api create",
				wski18n.T("Specify a swagger file or specify an API base path with an API path, an API verb, and an action name.")); whiskErr != nil {
				return whiskErr
			}
			api, qname, err = parseApi(cmd, args)
			if err != nil {
				whisk.Debug(whisk.DbgError, "parseApi(%s, %s) error: %s\n", cmd.Name(), args, err)
				errMsg := wski18n.T("Unable to parse api command arguments: {{.err}}",
					map[string]interface{}{"err": err})
				whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_GENERAL,
					whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
				return whiskErr
			}

			// Confirm that the specified action is a web-action
			action, err = isWebAction(Client, *qname)
			if err != nil {
				whisk.Debug(whisk.DbgError, "isWebAction(%v) is false: %s\n", qname, err)
				whiskErr := whisk.MakeWskError(err, whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
				return whiskErr
			}

			if webactionSecret = action.Annotations.GetValue(WEB_SECURE_ANNOT); webactionSecret != nil {
				whisk.Debug(whisk.DbgInfo, "web action is secured\n")
			}
		}

		apiCreateReq := new(whisk.ApiCreateRequest)
		apiCreateReq.ApiDoc = api

		apiCreateReqOptions := new(whisk.ApiCreateRequestOptions)
		if apiCreateReqOptions.SpaceGuid, err = getUserContextId(); err != nil {
			return err
		}
		if apiCreateReqOptions.AccessToken, err = getAccessToken(); err != nil {
			return err
		}
		apiCreateReqOptions.ResponseType = Flags.api.resptype

		if webactionSecret != nil {
			apiCreateReq.ApiDoc.Action.SecureKey = webactionSecret
		}

		whisk.Debug(whisk.DbgInfo, "AccessToken: %s\nSpaceGuid: %s\nResponseType: %s",
			apiCreateReqOptions.AccessToken, apiCreateReqOptions.SpaceGuid, apiCreateReqOptions.ResponseType)

		retApi, _, err := Client.Apis.Insert(apiCreateReq, apiCreateReqOptions, whisk.DoNotOverwrite)
		if err != nil {
			whisk.Debug(whisk.DbgError, "Client.Apis.Insert(%#v, false) error: %s\n", api, err)
			errMsg := wski18n.T("Unable to create API: {{.err}}", map[string]interface{}{"err": err})
			whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL,
				whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
			return whiskErr
		}

		if api.Swagger == "" {
			baseUrl := retApi.BaseUrl
			fmt.Fprintf(color.Output,
				wski18n.T("{{.ok}} created API {{.path}} {{.verb}} for action {{.name}}\n{{.fullpath}}\n",
					map[string]interface{}{
						"ok":       color.GreenString("ok:"),
						"path":     strings.TrimSuffix(api.GatewayBasePath, "/") + api.GatewayRelPath,
						"verb":     api.GatewayMethod,
						"name":     boldString("/" + api.Action.Namespace + "/" + api.Action.Name),
						"fullpath": strings.TrimSuffix(baseUrl, "/") + api.GatewayRelPath,
					}))
		} else {
			whisk.Debug(whisk.DbgInfo, "Processing swagger based create API response\n")
			baseUrl := retApi.BaseUrl
			for path := range retApi.Swagger.Paths {
				managedUrl := strings.TrimSuffix(baseUrl, "/") + path
				whisk.Debug(whisk.DbgInfo, "Managed path: '%s'\n", managedUrl)
				for op, opv := range retApi.Swagger.Paths[path].MakeOperationMap() {
					whisk.Debug(whisk.DbgInfo, "Path operation: '%s'\n", op)
					var fqActionName string
					if opv.XOpenWhisk == nil {
						fqActionName = ""
					} else if len(opv.XOpenWhisk.Package) > 0 {
						fqActionName = "/" + opv.XOpenWhisk.Namespace + "/" + opv.XOpenWhisk.Package + "/" + opv.XOpenWhisk.ActionName
					} else {
						fqActionName = "/" + opv.XOpenWhisk.Namespace + "/" + opv.XOpenWhisk.ActionName
					}

					whisk.Debug(whisk.DbgInfo, "baseUrl '%s'  Path '%s'  Path obj %+v\n", baseUrl, path, opv)
					if len(fqActionName) > 0 {
						fmt.Fprintf(color.Output,
							wski18n.T("{{.ok}} created API {{.path}} {{.verb}} for action {{.name}}\n{{.fullpath}}\n",
								map[string]interface{}{
									"ok":       color.GreenString("ok:"),
									"path":     strings.TrimSuffix(retApi.Swagger.BasePath, "/") + path,
									"verb":     op,
									"name":     boldString(fqActionName),
									"fullpath": managedUrl,
								}))
					} else {
						fmt.Fprintf(color.Output,
							wski18n.T("{{.ok}} created API {{.path}} {{.verb}}\n{{.fullpath}}\n",
								map[string]interface{}{
									"ok":       color.GreenString("ok:"),
									"path":     strings.TrimSuffix(retApi.Swagger.BasePath, "/") + path,
									"verb":     op,
									"fullpath": managedUrl,
								}))
					}
				}
			}
		}

		return nil
	},
}

var apiGetCmd = &cobra.Command{
	Use:           "get BASE_PATH | API_NAME",
	Short:         wski18n.T("get API details"),
	SilenceUsage:  true,
	SilenceErrors: true,
	PreRunE:       SetupClientConfig,
	RunE: func(cmd *cobra.Command, args []string) error {
		var err error
		var isBasePathArg bool = true

		if whiskErr := CheckArgs(args, 1, 1, "Api get",
			wski18n.T("An API base path or API name is required.")); whiskErr != nil {
			return whiskErr
		}

		if cmd.LocalFlags().Changed("format") &&
			strings.ToLower(Flags.common.format) != formatOptionYaml &&
			strings.ToLower(Flags.common.format) != formatOptionJson {
			errMsg := wski18n.T("Invalid format type: {{.type}}", map[string]interface{}{"type": Flags.common.format})
			whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL,
				whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
			return whiskErr
		}

		apiGetReq := new(whisk.ApiGetRequest)
		apiGetReqOptions := new(whisk.ApiGetRequestOptions)
		apiGetReqOptions.ApiBasePath = args[0]
		if apiGetReqOptions.SpaceGuid, err = getUserContextId(); err != nil {
			return err
		}
		if apiGetReqOptions.AccessToken, err = getAccessToken(); err != nil {
			return err
		}

		retApi, _, err := Client.Apis.Get(apiGetReq, apiGetReqOptions)
		if err != nil {
			whisk.Debug(whisk.DbgError, "Client.Apis.Get(%#v, %#v) error: %s\n", apiGetReq, apiGetReqOptions, err)
			errMsg := wski18n.T("Unable to get API '{{.name}}': {{.err}}", map[string]interface{}{"name": args[0], "err": err})
			whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL,
				whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
			return whiskErr
		}
		whisk.Debug(whisk.DbgInfo, "Client.Apis.Get returned: %#v\n", retApi)

		var displayResult interface{} = nil
		if Flags.common.detail {
			if retApi.Apis != nil && len(retApi.Apis) > 0 &&
				retApi.Apis[0].ApiValue != nil {
				displayResult = retApi.Apis[0].ApiValue
			} else {
				whisk.Debug(whisk.DbgError, "No result object returned\n")
			}
		} else {
			if retApi.Apis != nil && len(retApi.Apis) > 0 &&
				retApi.Apis[0].ApiValue != nil &&
				retApi.Apis[0].ApiValue.Swagger != nil {
				displayResult = retApi.Apis[0].ApiValue.Swagger
			} else {
				whisk.Debug(whisk.DbgError, "No swagger returned\n")
			}
		}
		if displayResult == nil {
			var errMsg string
			if isBasePathArg {
				errMsg = wski18n.T("API does not exist for basepath {{.basepath}}",
					map[string]interface{}{"basepath": args[0]})
			} else {
				errMsg = wski18n.T("API does not exist for API name {{.apiname}}",
					map[string]interface{}{"apiname": args[0]})
			}

			whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL,
				whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
			return whiskErr
		}

		if cmd.LocalFlags().Changed("format") && strings.ToLower(Flags.common.format) == formatOptionYaml {
			var jsonOutputBuffer bytes.Buffer
			var jsonOutputWriter = bufio.NewWriter(&jsonOutputBuffer)
			printJSON(displayResult, jsonOutputWriter)
			jsonOutputWriter.Flush()
			yamlbytes, err := yaml.JSONToYAML(jsonOutputBuffer.Bytes())
			if err != nil {
				whisk.Debug(whisk.DbgError, "yaml.JSONToYAML() error: %s\n", err)
				errMsg := wski18n.T("Unable to convert API into YAML: {{.err}}", map[string]interface{}{"err": err})
				whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL,
					whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
				return whiskErr
			}
			fmt.Println(string(yamlbytes))
		} else {
			printJSON(displayResult)
		}

		return nil
	},
}

var apiDeleteCmd = &cobra.Command{
	Use:           "delete BASE_PATH | API_NAME [API_PATH [API_VERB]]",
	Short:         wski18n.T("delete an API"),
	SilenceUsage:  true,
	SilenceErrors: true,
	PreRunE:       SetupClientConfig,
	RunE: func(cmd *cobra.Command, args []string) error {
		var err error

		if whiskErr := CheckArgs(args, 1, 3, "Api delete",
			wski18n.T("An API base path or API name is required.  An optional API relative path and operation may also be provided.")); whiskErr != nil {
			return whiskErr
		}

		apiDeleteReq := new(whisk.ApiDeleteRequest)
		apiDeleteReqOptions := new(whisk.ApiDeleteRequestOptions)
		if apiDeleteReqOptions.SpaceGuid, err = getUserContextId(); err != nil {
			return err
		}
		if apiDeleteReqOptions.AccessToken, err = getAccessToken(); err != nil {
			return err
		}

		// Is the argument a basepath (must start with /) or an API name
		if _, ok := isValidBasepath(args[0]); !ok {
			whisk.Debug(whisk.DbgInfo, "Treating '%s' as an API name; as it does not begin with '/'\n", args[0])
			apiDeleteReqOptions.ApiBasePath = args[0]
		} else {
			apiDeleteReqOptions.ApiBasePath = args[0]
		}

		if len(args) > 1 {
			// Is the API path valid?
			if whiskErr, ok := isValidRelpath(args[1]); !ok {
				return whiskErr
			}
			apiDeleteReqOptions.ApiRelPath = args[1]
		}
		if len(args) > 2 {
			// Is the API verb valid?
			if whiskErr, ok := IsValidApiVerb(args[2]); !ok {
				return whiskErr
			}
			apiDeleteReqOptions.ApiVerb = strings.ToUpper(args[2])
		}

		_, err = Client.Apis.Delete(apiDeleteReq, apiDeleteReqOptions)
		if err != nil {
			whisk.Debug(whisk.DbgError, "Client.Apis.Delete(%#v, %#v) error: %s\n", apiDeleteReq, apiDeleteReqOptions, err)
			errMsg := wski18n.T("Unable to delete API: {{.err}}", map[string]interface{}{"err": err})
			whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL,
				whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
			return whiskErr
		}

		if len(args) == 1 {
			fmt.Fprintf(color.Output,
				wski18n.T("{{.ok}} deleted API {{.basepath}}\n",
					map[string]interface{}{
						"ok":       color.GreenString("ok:"),
						"basepath": apiDeleteReqOptions.ApiBasePath,
					}))
		} else if len(args) == 2 {
			fmt.Fprintf(color.Output,
				wski18n.T("{{.ok}} deleted {{.path}} from {{.basepath}}\n",
					map[string]interface{}{
						"ok":       color.GreenString("ok:"),
						"path":     apiDeleteReqOptions.ApiRelPath,
						"basepath": apiDeleteReqOptions.ApiBasePath,
					}))
		} else {
			fmt.Fprintf(color.Output,
				wski18n.T("{{.ok}} deleted {{.path}} {{.verb}} from {{.basepath}}\n",
					map[string]interface{}{
						"ok":       color.GreenString("ok:"),
						"path":     apiDeleteReqOptions.ApiRelPath,
						"verb":     apiDeleteReqOptions.ApiVerb,
						"basepath": apiDeleteReqOptions.ApiBasePath,
					}))
		}

		return nil
	},
}

var apiListCmd = &cobra.Command{
	Use:           "list [[BASE_PATH | API_NAME] [API_PATH [API_VERB]]",
	Short:         wski18n.T("list APIs"),
	SilenceUsage:  true,
	SilenceErrors: true,
	PreRunE:       SetupClientConfig,
	RunE: func(cmd *cobra.Command, args []string) error {
		var err error
		var retApiList *whisk.ApiListResponse
		var retApi *whisk.ApiGetResponse
		var retApiArray *whisk.RetApiArray
		var apiPath string
		var apiVerb string
		var orderFilteredList []whisk.ApiFilteredList
		var orderFilteredRow []whisk.ApiFilteredRow

		if whiskErr := CheckArgs(args, 0, 3, "Api list",
			wski18n.T("Optional parameters are: API base path (or API name), API relative path and operation.")); whiskErr != nil {
			return whiskErr
		}

		if len(args) == 0 {
			// List API request query parameters
			apiListReqOptions := new(whisk.ApiListRequestOptions)
			apiListReqOptions.Limit = Flags.common.limit
			apiListReqOptions.Skip = Flags.common.skip
			if apiListReqOptions.SpaceGuid, err = getUserContextId(); err != nil {
				return err
			}
			if apiListReqOptions.AccessToken, err = getAccessToken(); err != nil {
				return err
			}

			retApiList, _, err = Client.Apis.List(apiListReqOptions)
			if err != nil {
				whisk.Debug(whisk.DbgError, "Client.Apis.List(%#v) error: %s\n", apiListReqOptions, err)
				errMsg := wski18n.T("Unable to obtain the API list: {{.err}}", map[string]interface{}{"err": err})
				whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL,
					whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
				return whiskErr
			}
			whisk.Debug(whisk.DbgInfo, "Client.Apis.List returned: %#v (%+v)\n", retApiList, retApiList)
			// Cast to a common type to allow for code to print out apilist response or apiget response
			retApiArray = (*whisk.RetApiArray)(retApiList)
		} else {
			// Get API request body
			apiGetReq := new(whisk.ApiGetRequest)
			apiGetReq.Namespace = Client.Config.Namespace
			// Get API request options
			apiGetReqOptions := new(whisk.ApiGetRequestOptions)
			if apiGetReqOptions.SpaceGuid, err = getUserContextId(); err != nil {
				return err
			}
			if apiGetReqOptions.AccessToken, err = getAccessToken(); err != nil {
				return err
			}

			// The first argument is either a basepath (must start with /) or an API name
			apiGetReqOptions.ApiBasePath = args[0]
			if len(args) > 1 {
				// Is the API path valid?
				if whiskErr, ok := isValidRelpath(args[1]); !ok {
					return whiskErr
				}
				apiPath = args[1]
				apiGetReqOptions.ApiRelPath = apiPath
			}
			if len(args) > 2 {
				// Is the API verb valid?
				if whiskErr, ok := IsValidApiVerb(args[2]); !ok {
					return whiskErr
				}
				apiVerb = strings.ToUpper(args[2])
				apiGetReqOptions.ApiVerb = apiVerb
			}

			retApi, _, err = Client.Apis.Get(apiGetReq, apiGetReqOptions)
			if err != nil {
				whisk.Debug(whisk.DbgError, "Client.Apis.Get(%#v, %#v) error: %s\n", apiGetReq, apiGetReqOptions, err)
				errMsg := wski18n.T("Unable to obtain the API list: {{.err}}", map[string]interface{}{"err": err})
				whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_GENERAL,
					whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
				return whiskErr
			}
			whisk.Debug(whisk.DbgInfo, "Client.Apis.Get returned: %#v\n", retApi)
			// Cast to a common type to allow for code to print out apilist response or apiget response
			retApiArray = (*whisk.RetApiArray)(retApi)
		}
		//Checks for any order flags being passed
		sortByName := Flags.common.nameSort
		// Display the APIs - applying any specified filtering
		if Flags.common.full {
			fmt.Fprintf(color.Output,
				wski18n.T("{{.ok}} APIs\n",
					map[string]interface{}{
						"ok": color.GreenString("ok:"),
					}))
			for i := 0; i < len(retApiArray.Apis); i++ {
				orderFilteredList = append(orderFilteredList, genFilteredList(retApiArray.Apis[i].ApiValue, apiPath, apiVerb)...)
			}
			printList(orderFilteredList, sortByName) // Sends an array of structs that contains specified variables that are not truncated
		} else {
			if len(retApiArray.Apis) > 0 {
				// Dynamically create the output format string based on the maximum size of the
				// fully qualified action name and the API Name.
				maxActionNameSize := min(40, max(len("Action"), getLargestActionNameSize(retApiArray, apiPath, apiVerb)))
				maxApiNameSize := min(30, max(len("API Name"), getLargestApiNameSize(retApiArray, apiPath, apiVerb)))
				fmtString = "%-" + strconv.Itoa(maxActionNameSize) + "s %7s %" + strconv.Itoa(maxApiNameSize+1) + "s  %s\n"
				fmt.Fprintf(color.Output,
					wski18n.T("{{.ok}} APIs\n",
						map[string]interface{}{
							"ok": color.GreenString("ok:"),
						}))
				for i := 0; i < len(retApiArray.Apis); i++ {
					orderFilteredRow = append(orderFilteredRow, genFilteredRow(retApiArray.Apis[i].ApiValue, apiPath, apiVerb, maxActionNameSize, maxApiNameSize)...)
				}
				printList(orderFilteredRow, sortByName) // Sends an array of structs that contains specified variables that are truncated
			} else {
				fmt.Fprintf(color.Output,
					wski18n.T("{{.ok}} APIs\n",
						map[string]interface{}{
							"ok": color.GreenString("ok:"),
						}))
				printList(orderFilteredRow, sortByName) // Sends empty orderFilteredRow so that defaultHeader can be printed
			}
		}

		return nil
	},
}

// genFilteredList(resultApi, api) generates an array of
//      ApiFilteredLists for the purpose of ordering and printing in a list form.
//      NOTE: genFilteredRow() generates entries with one line per configuration
//         property (action name, verb, api name, api gw url)
func genFilteredList(resultApi *whisk.RetApi, apiPath string, apiVerb string) []whisk.ApiFilteredList {
	var orderInfo whisk.ApiFilteredList
	var orderInfoArr []whisk.ApiFilteredList
	baseUrl := strings.TrimSuffix(resultApi.BaseUrl, "/")
	apiName := resultApi.Swagger.Info.Title
	basePath := resultApi.Swagger.BasePath
	if resultApi.Swagger != nil && resultApi.Swagger.Paths != nil {
		for path := range resultApi.Swagger.Paths {
			whisk.Debug(whisk.DbgInfo, "genFilteredApi: comparing api relpath: '%s'\n", path)
			if len(apiPath) == 0 || path == apiPath {
				whisk.Debug(whisk.DbgInfo, "genFilteredList: relpath matches\n")
				for op, opv := range resultApi.Swagger.Paths[path].MakeOperationMap() {
					whisk.Debug(whisk.DbgInfo, "genFilteredList: comparing operation: '%s'\n", op)
					if len(apiVerb) == 0 || strings.ToLower(op) == strings.ToLower(apiVerb) {
						whisk.Debug(whisk.DbgInfo, "genFilteredList: operation matches: %#v\n", opv)
						var actionName string
						if opv.XOpenWhisk == nil {
							actionName = ""
						} else if len(opv.XOpenWhisk.Package) > 0 {
							actionName = "/" + opv.XOpenWhisk.Namespace + "/" + opv.XOpenWhisk.Package + "/" + opv.XOpenWhisk.ActionName
						} else {
							actionName = "/" + opv.XOpenWhisk.Namespace + "/" + opv.XOpenWhisk.ActionName
						}
						orderInfo = AssignListInfo(actionName, op, apiName, basePath, path, baseUrl+path)
						whisk.Debug(whisk.DbgInfo, "Appening to orderInfoArr: %s\n", orderInfo.RelPath)
						orderInfoArr = append(orderInfoArr, orderInfo)
					}
				}
			}
		}
	}
	return orderInfoArr
}

// genFilteredRow(resultApi, api, maxApiNameSize, maxApiNameSize) generates an array of
//      ApiFilteredRows for the purpose of ordering and printing in a list form by parsing and
//      initializing vaules for each individual ApiFilteredRow struct.
//      NOTE: Large action and api name values will be truncated by their associated max size parameters.
func genFilteredRow(resultApi *whisk.RetApi, apiPath string, apiVerb string, maxActionNameSize int, maxApiNameSize int) []whisk.ApiFilteredRow {
	var orderInfo whisk.ApiFilteredRow
	var orderInfoArr []whisk.ApiFilteredRow
	baseUrl := strings.TrimSuffix(resultApi.BaseUrl, "/")
	apiName := resultApi.Swagger.Info.Title
	basePath := resultApi.Swagger.BasePath
	if resultApi.Swagger != nil && resultApi.Swagger.Paths != nil {
		for path := range resultApi.Swagger.Paths {
			whisk.Debug(whisk.DbgInfo, "genFilteredRow: comparing api relpath: '%s'\n", path)
			if len(apiPath) == 0 || path == apiPath {
				whisk.Debug(whisk.DbgInfo, "genFilteredRow: relpath matches\n")
				for op, opv := range resultApi.Swagger.Paths[path].MakeOperationMap() {
					whisk.Debug(whisk.DbgInfo, "genFilteredRow: comparing operation: '%s'\n", op)
					if len(apiVerb) == 0 || strings.ToLower(op) == strings.ToLower(apiVerb) {
						whisk.Debug(whisk.DbgInfo, "genFilteredRow: operation matches: %#v\n", opv)
						var actionName string
						if opv.XOpenWhisk == nil {
							actionName = ""
						} else if len(opv.XOpenWhisk.Package) > 0 {
							actionName = "/" + opv.XOpenWhisk.Namespace + "/" + opv.XOpenWhisk.Package + "/" + opv.XOpenWhisk.ActionName
						} else {
							actionName = "/" + opv.XOpenWhisk.Namespace + "/" + opv.XOpenWhisk.ActionName
						}
						orderInfo = AssignRowInfo(actionName[0:min(len(actionName), maxActionNameSize)], op, apiName[0:min(len(apiName), maxApiNameSize)], basePath, path, baseUrl+path)
						orderInfo.FmtString = fmtString
						whisk.Debug(whisk.DbgInfo, "Appening to orderInfoArr: %s\n", orderInfo.RelPath)
						orderInfoArr = append(orderInfoArr, orderInfo)
					}
				}
			}
		}
	}
	return orderInfoArr
}

// AssignRowInfo(actionName, verb, apiName, basePath, relPath, url) assigns
//      the given vaules to and initializes an ApiFilteredRow struct, then returns it.
func AssignRowInfo(actionName string, verb string, apiName string, basePath string, relPath string, url string) whisk.ApiFilteredRow {
	var orderInfo whisk.ApiFilteredRow

	orderInfo.ActionName = actionName
	orderInfo.Verb = verb
	orderInfo.ApiName = apiName
	orderInfo.BasePath = basePath
	orderInfo.RelPath = relPath
	orderInfo.Url = url

	return orderInfo
}

// AssignListInfo(actionName, verb, apiName, basePath, relPath, url) assigns
//      the given vaules to and initializes an ApiFilteredList struct, then returns it.
func AssignListInfo(actionName string, verb string, apiName string, basePath string, relPath string, url string) whisk.ApiFilteredList {
	var orderInfo whisk.ApiFilteredList

	orderInfo.ActionName = actionName
	orderInfo.Verb = verb
	orderInfo.ApiName = apiName
	orderInfo.BasePath = basePath
	orderInfo.RelPath = relPath
	orderInfo.Url = url

	return orderInfo
}

func getLargestActionNameSize(retApiArray *whisk.RetApiArray, apiPath string, apiVerb string) int {
	var maxNameSize = 0
	for i := 0; i < len(retApiArray.Apis); i++ {
		var resultApi = retApiArray.Apis[i].ApiValue
		if resultApi.Swagger != nil && resultApi.Swagger.Paths != nil {
			for path := range resultApi.Swagger.Paths {
				whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: comparing api relpath: '%s'\n", path)
				if len(apiPath) == 0 || path == apiPath {
					whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: relpath matches\n")
					for op, opv := range resultApi.Swagger.Paths[path].MakeOperationMap() {
						whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: comparing operation: '%s'\n", op)
						if len(apiVerb) == 0 || strings.ToLower(op) == strings.ToLower(apiVerb) {
							whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: operation matches: %#v\n", opv)
							var fullActionName string
							if opv.XOpenWhisk == nil {
								fullActionName = ""
							} else if len(opv.XOpenWhisk.Package) > 0 {
								fullActionName = "/" + opv.XOpenWhisk.Namespace + "/" + opv.XOpenWhisk.Package + "/" + opv.XOpenWhisk.ActionName
							} else {
								fullActionName = "/" + opv.XOpenWhisk.Namespace + "/" + opv.XOpenWhisk.ActionName
							}
							if len(fullActionName) > maxNameSize {
								maxNameSize = len(fullActionName)
							}
						}
					}
				}
			}
		}
	}
	return maxNameSize
}

func getLargestApiNameSize(retApiArray *whisk.RetApiArray, apiPath string, apiVerb string) int {
	var maxNameSize = 0
	for i := 0; i < len(retApiArray.Apis); i++ {
		var resultApi = retApiArray.Apis[i].ApiValue
		apiName := resultApi.Swagger.Info.Title
		if resultApi.Swagger != nil && resultApi.Swagger.Paths != nil {
			for path := range resultApi.Swagger.Paths {
				whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: comparing api relpath: '%s'\n", path)
				if len(apiPath) == 0 || path == apiPath {
					whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: relpath matches\n")
					for op, opv := range resultApi.Swagger.Paths[path].MakeOperationMap() {
						whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: comparing operation: '%s'\n", op)
						if len(apiVerb) == 0 || strings.ToLower(op) == strings.ToLower(apiVerb) {
							whisk.Debug(whisk.DbgInfo, "getLargestActionNameSize: operation matches: %#v\n", opv)
							if len(apiName) > maxNameSize {
								maxNameSize = len(apiName)
							}
						}
					}
				}
			}
		}
	}
	return maxNameSize
}

func generatePathParameters(relativePath string) ([]whisk.ApiParameter, error) {
	pathParams := []whisk.ApiParameter{}

	pathParamNames, err := getPathParameterNames(relativePath)
	if len(pathParamNames) > 0 && err == nil {
		// Only create unique swagger entries
		var uniqueParamNames []string
		for _, name := range pathParamNames {
			if !contains(uniqueParamNames, name) {
				uniqueParamNames = append(uniqueParamNames, name)
			}
		}
		for _, uniqueName := range uniqueParamNames {
			whisk.Debug(whisk.DbgInfo, "Creating api parameter for '%s'\n", uniqueName)
			param := whisk.ApiParameter{Name: uniqueName, In: "path", Required: true, Type: "string",
				Description: wski18n.T("Default description for '{{.name}}'", map[string]interface{}{"name": uniqueName})}
			pathParams = append(pathParams, param)
		}
	}

	return pathParams, err
}

/*
 * if # args = 4
 * args[0] = API base path
 * args[0] = API relative path
 * args[1] = API verb
 * args[2] = Optional.  Action name (may or may not be qualified with namespace and package name)
 *
 * if # args = 3
 * args[0] = API relative path
 * args[1] = API verb
 * args[2] = Optional.  Action name (may or may not be qualified with namespace and package name)
 */
func parseApi(cmd *cobra.Command, args []string) (*whisk.Api, *QualifiedName, error) {
	var err error
	var basepath string = "/"
	var apiname string
	var basepathArgIsApiName = false

	api := new(whisk.Api)

	if len(args) > 3 {
		// Is the argument a basepath (must start with /) or an API name
		if _, ok := isValidBasepath(args[0]); !ok {
			whisk.Debug(whisk.DbgInfo, "Treating '%s' as an API name; as it does not begin with '/'\n", args[0])
			basepathArgIsApiName = true
		}
		if err, _ := isBasepathParameterized(args[0]); err != nil {
			return nil, nil, err
		}
		basepath = args[0]

		// Shift the args so the remaining code works with or without the explicit base path arg
		args = args[1:]
	}

	// Is the API path valid?
	if len(args) > 0 {
		if whiskErr, ok := isValidRelpath(args[0]); !ok {
			return nil, nil, whiskErr
		}
		api.GatewayRelPath = args[0] // Maintain case as URLs may be case-sensitive
	}

	// Attempting to use path parameters, lets validate that they provided them correctly.
	hasPathParams, err := hasPathParameters(api.GatewayRelPath)
	if err != nil {
		return nil, nil, err
	}
	// If they provided path Parameters, the response type better be http as its the only one that supports path parameters right now.
	if hasPathParams && Flags.api.resptype != "http" {
		errMsg := wski18n.T("A response type of 'http' is required when using path parameters.")
		whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_GENERAL,
			whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
		return nil, nil, whiskErr
	}

	// Is the API verb valid?
	if len(args) > 1 {
		if whiskErr, ok := IsValidApiVerb(args[1]); !ok {
			return nil, nil, whiskErr
		}
		api.GatewayMethod = strings.ToUpper(args[1])
	}

	// Is the specified action name valid?
	var qName = new(QualifiedName)
	if len(args) == 3 {
		qName, err = NewQualifiedName(args[2])
		if err != nil {
			whisk.Debug(whisk.DbgError, "NewQualifiedName(%s) failed: %s\n", args[2], err)
			errMsg := wski18n.T("'{{.name}}' is not a valid action name: {{.err}}",
				map[string]interface{}{"name": args[2], "err": err})
			whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_GENERAL,
				whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
			return nil, nil, whiskErr
		}
		if qName.GetEntityName() == "" {
			whisk.Debug(whisk.DbgError, "Action name '%s' is invalid\n", args[2])
			errMsg := wski18n.T("'{{.name}}' is not a valid action name.", map[string]interface{}{"name": args[2]})
			whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_GENERAL,
				whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
			return nil, nil, whiskErr
		}
	}

	if len(Flags.api.apiname) > 0 {
		if basepathArgIsApiName {
			// Specifying API name as argument AND as a --apiname option value is invalid
			whisk.Debug(whisk.DbgError, "API is specified as an argument '%s' and as a flag '%s'\n", basepath, Flags.api.apiname)
			errMsg := wski18n.T("An API name can only be specified once.")
			whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL,
				whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
			return nil, nil, whiskErr
		}
		apiname = Flags.api.apiname
	}

	api.Namespace = Client.Config.Namespace
	api.Action = new(whisk.ApiAction)
	var urlActionPackage string
	if len(qName.GetPackageName()) > 0 {
		urlActionPackage = qName.GetPackageName()
	} else {
		urlActionPackage = "default"
	}
	backendUrl := Client.Config.Host + "/api/v1/web/" + qName.GetNamespace() + "/" + urlActionPackage + "/" + qName.GetEntity() + ".http"
	if !strings.HasPrefix(backendUrl, "http") {
		backendUrl = "https://" + backendUrl
	}
	api.Action.BackendUrl = backendUrl
	api.Action.BackendMethod = api.GatewayMethod
	api.Action.Name = qName.GetEntityName()
	api.Action.Namespace = qName.GetNamespace()
	api.Action.Auth = Client.Config.AuthToken
	api.ApiName = apiname
	api.GatewayBasePath = basepath
	if !basepathArgIsApiName {
		api.Id = "API:" + api.Namespace + ":" + api.GatewayBasePath
	}
	api.PathParameters, err = generatePathParameters(api.GatewayRelPath)

	whisk.Debug(whisk.DbgInfo, "Parsed api struct: %#v\n", api)
	return api, qName, err
}

func parseSwaggerApi(configfile string, namespace string) (*whisk.Api, error) {
	// Test is for completeness, but this situation should only arise due to an internal error
	if len(configfile) == 0 {
		whisk.Debug(whisk.DbgError, "No swagger file is specified\n")
		errMsg := wski18n.T("A configuration file was not specified.")
		whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL,
			whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
		return nil, whiskErr
	}

	swagger, err := ReadFile(configfile)
	if err != nil {
		whisk.Debug(whisk.DbgError, "readFile(%s) error: %s\n", configfile, err)
		errMsg := wski18n.T("Error reading swagger file '{{.name}}': {{.err}}",
			map[string]interface{}{"name": configfile, "err": err})
		whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_GENERAL,
			whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
		return nil, whiskErr
	}

	// Check if this swagger is in JSON or YAML format
	isYaml := strings.HasSuffix(configfile, yamlFileExtension) || strings.HasSuffix(configfile, ymlFileExtension)
	if isYaml {
		whisk.Debug(whisk.DbgInfo, "Converting YAML formated API configuration into JSON\n")
		jsonbytes, err := yaml.YAMLToJSON([]byte(swagger))
		if err != nil {
			whisk.Debug(whisk.DbgError, "yaml.YAMLToJSON() error: %s\n", err)
			errMsg := wski18n.T("Unable to parse YAML configuration file: {{.err}}", map[string]interface{}{"err": err})
			whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL,
				whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
			return nil, whiskErr
		}
		swagger = string(jsonbytes)
	}

	// Parse the JSON into a swagger object
	swaggerObj := new(whisk.ApiSwagger)
	err = json.Unmarshal([]byte(swagger), swaggerObj)
	if err != nil {
		whisk.Debug(whisk.DbgError, "JSON parse of '%s' error: %s\n", configfile, err)
		errMsg := wski18n.T("Error parsing swagger file '{{.name}}': {{.err}}",
			map[string]interface{}{"name": configfile, "err": err})
		whiskErr := whisk.MakeWskErrorFromWskError(errors.New(errMsg), err, whisk.EXIT_CODE_ERR_GENERAL,
			whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
		return nil, whiskErr
	}

	if swaggerObj.BasePath == "" || swaggerObj.SwaggerName == "" || swaggerObj.Info == nil || swaggerObj.Paths == nil {
		whisk.Debug(whisk.DbgError, "Swagger file is invalid.\n")
		errMsg := wski18n.T("Swagger file is invalid (missing basePath, info, paths, or swagger fields)")
		whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL,
			whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
		return nil, whiskErr
	}

	if _, ok := isValidBasepath(swaggerObj.BasePath); !ok {
		whisk.Debug(whisk.DbgError, "Swagger file basePath is invalid.\n")
		errMsg := wski18n.T("Swagger file basePath must start with a leading slash (/)")
		whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL,
			whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
		return nil, whiskErr
	}

	api := new(whisk.Api)
	api.Namespace = namespace
	api.Swagger = swagger

	return api, nil
}

func getAccessToken() (string, error) {
	var token string = "DUMMY TOKEN"
	var err error

	// If the api gw access token override has been set, use it instead of the default
	if len(ApiGwAccessToken) > 0 {
		token = ApiGwAccessToken
		whisk.Debug(whisk.DbgInfo, "API GW access token override used\n")
	} else {
		props, errprops := ReadProps(Properties.PropsFile)
		if errprops == nil {
			if len(props["APIGW_ACCESS_TOKEN"]) > 0 {
				token = props["APIGW_ACCESS_TOKEN"]
			}
		} else {
			whisk.Debug(whisk.DbgError, "readProps(%s) failed: %s\n", Properties.PropsFile, err)
			errStr := wski18n.T("Unable to obtain the API Gateway access token from the properties file: {{.err}}", map[string]interface{}{"err": err})
			err = whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
		}
	}

	return token, err
}

func getUserContextId() (string, error) {
	var guid string
	var err error

	// If the context id override has been set, use it instead of the default
	if len(ContextId) > 0 {
		guid = ContextId
	} else {
		props, errprops := ReadProps(Properties.PropsFile)
		if errprops == nil {
			if len(props["AUTH"]) > 0 {
				guid = strings.Split(props["AUTH"], ":")[0]
			} else {
				whisk.Debug(whisk.DbgError, "AUTH property not set in properties file: '%s'\n", Properties.PropsFile)
				errStr := wski18n.T("Authorization key is not configured (--auth is required)")
				err = whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
			}
		} else {
			whisk.Debug(whisk.DbgError, "readProps(%s) failed: %s\n", Properties.PropsFile, err)
			errStr := wski18n.T("Unable to obtain the auth key from the properties file: {{.err}}", map[string]interface{}{"err": err})
			err = whisk.MakeWskError(errors.New(errStr), whisk.EXIT_CODE_ERR_GENERAL, whisk.DISPLAY_MSG, whisk.NO_DISPLAY_USAGE)
		}
	}

	return guid, err
}

///////////
// Flags //
///////////

func init() {

	apiCreateCmd.Flags().StringVarP(&Flags.api.apiname, "apiname", "n", "", wski18n.T("Friendly name of the API; ignored when CFG_FILE is specified (default BASE_PATH)"))
	apiCreateCmd.Flags().StringVarP(&Flags.api.configfile, "config-file", "c", "", wski18n.T("`CFG_FILE` containing API configuration in swagger JSON format"))
	apiCreateCmd.Flags().StringVar(&Flags.api.resptype, "response-type", "json", wski18n.T("Set the web action response `TYPE`. Possible values are html, http, json, text, svg"))
	apiGetCmd.Flags().BoolVarP(&Flags.common.detail, "full", "f", false, wski18n.T("display full API configuration details"))
	apiGetCmd.Flags().StringVarP(&Flags.common.format, "format", "", formatOptionJson, wski18n.T("Specify the API output `TYPE`, either json or yaml"))
	apiListCmd.Flags().IntVarP(&Flags.common.skip, "skip", "s", 0, wski18n.T("exclude the first `SKIP` number of actions from the result"))
	apiListCmd.Flags().IntVarP(&Flags.common.limit, "limit", "l", 30, wski18n.T("only return `LIMIT` number of actions from the collection"))
	apiListCmd.Flags().BoolVarP(&Flags.common.nameSort, "name-sort", "n", false, wski18n.T("sorts a list alphabetically by order of [BASE_PATH | API_NAME], API_PATH, then API_VERB; only applicable within the limit/skip returned entity block"))
	apiListCmd.Flags().BoolVarP(&Flags.common.full, "full", "f", false, wski18n.T("display full description of each API"))
	apiCmd.AddCommand(
		apiCreateCmd,
		apiGetCmd,
		apiDeleteCmd,
		apiListCmd,
	)
}
