You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@openwhisk.apache.org by du...@apache.org on 2018/01/31 18:35:31 UTC

[incubator-openwhisk-cli] branch master updated: Support for Api GW path param support (#170)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new f1af1e5  Support for Api GW path param support (#170)
f1af1e5 is described below

commit f1af1e50d848c4aa1c240f2b11b8806c5e4ab79e
Author: Jesus Alva <ja...@us.ibm.com>
AuthorDate: Wed Jan 31 12:35:29 2018 -0600

    Support for Api GW path param support (#170)
---
 .gitignore                                         |   4 +
 build.gradle                                       |   2 +-
 commands/api.go                                    |  82 +++-
 .../scala/whisk/core/cli/test/ApiGwCliTests.scala  | 227 +++++++++-
 .../scala/whisk/core/cli/test/ApiGwTests.scala     | 133 +++++-
 wski18n/i18n_resources.go                          | 463 ---------------------
 wski18n/resources/en_US.all.json                   |  16 +
 7 files changed, 451 insertions(+), 476 deletions(-)

diff --git a/.gitignore b/.gitignore
index de76850..55423b3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,7 @@ Godeps/_workspace
 build
 /release/
 /vendor/
+incubator-openwhisk-cli.iml
+wski18n/i18n_resources.go
+bin/
+tests/build/
diff --git a/build.gradle b/build.gradle
index 08fc238..29bedf2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -24,7 +24,7 @@ dependencies {
         build(['name':'golang.org/x/sys/unix', 'version':'7f918dd405547ecb864d14a8ecbbfe205b5f930f', 'transitive':false])
         build(['name':'gopkg.in/yaml.v2', 'version':'cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b', 'transitive':false])
         build(['name':'github.com/ghodss/yaml', 'version':'0ca9ea5df5451ffdf184b4428c902747c2c11cd7', 'transitive':false])
-        build(['name':'github.com/apache/incubator-openwhisk-client-go/whisk','version':'ba3bbee442357a239667ef6de378d5b7d33e0ceb','transitive':false])
+        build(['name':'github.com/apache/incubator-openwhisk-client-go/whisk','version':'a0864455f7c18db70d93d4dd2bc0b43d2334ed90','transitive':false])
         // END - Imported from Godeps
         test name:'github.com/stretchr/testify', version:'b91bfb9ebec76498946beb6af7c0230c7cc7ba6c', transitive:false //, tag: 'v1.2.0'
         test name:'github.com/spf13/viper', version:'aafc9e6bc7b7bb53ddaa75a5ef49a17d6e654be5', transitive:false
diff --git a/commands/api.go b/commands/api.go
index 4134683..468fb05 100644
--- a/commands/api.go
+++ b/commands/api.go
@@ -33,6 +33,7 @@ import (
 	"github.com/fatih/color"
 	"github.com/ghodss/yaml"
 	"github.com/spf13/cobra"
+	"regexp"
 )
 
 const (
@@ -41,6 +42,8 @@ const (
 
 	formatOptionYaml = "yaml"
 	formatOptionJson = "json"
+
+	pathParamRegex = `/\{([^/]+)}/|/\{([^/]+)}$`
 )
 
 var apiCmd = &cobra.Command{
@@ -93,6 +96,34 @@ func isValidRelpath(relpath string) (error, bool) {
 	return nil, true
 }
 
+func hasPathParameters(path string) (bool, error) {
+	matched, err := regexp.MatchString(pathParamRegex, path)
+	hasBracket := strings.ContainsRune(path, '{') || strings.ContainsRune(path, '}')
+	if err != nil {
+		whisk.Debug(whisk.DbgInfo, "Unable to compile Regex '%s' to test against path '%s'\n", pathParamRegex, path)
+	}
+	if hasBracket && !matched {
+		errMsg := wski18n.T("Relative path '{{.path}}' does not include valid path parameters. Each parameter must be enclosed in curly braces '{}'.",
+			map[string]interface{}{"path": path})
+		whiskErr := whisk.MakeWskError(errors.New(errMsg), whisk.EXIT_CODE_ERR_GENERAL,
+			whisk.DISPLAY_MSG, whisk.DISPLAY_USAGE)
+		return false, whiskErr
+	}
+	return matched, nil
+}
+
+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
  */
@@ -103,7 +134,7 @@ func getManagedUrl(api *whisk.RetApi, relpath string, operation string) (url str
 		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] {
+			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)
@@ -208,7 +239,7 @@ var apiCreateCmd = &cobra.Command{
 			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] {
+				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 {
@@ -577,7 +608,7 @@ func genFilteredList(resultApi *whisk.RetApi, apiPath string, apiVerb string) []
 			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] {
+				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)
@@ -615,7 +646,7 @@ func genFilteredRow(resultApi *whisk.RetApi, apiPath string, apiVerb string, max
 			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] {
+				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)
@@ -678,7 +709,7 @@ func getLargestActionNameSize(retApiArray *whisk.RetApiArray, apiPath string, ap
 				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] {
+					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)
@@ -712,7 +743,7 @@ func getLargestApiNameSize(retApiArray *whisk.RetApiArray, apiPath string, apiVe
 				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] {
+					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)
@@ -728,6 +759,28 @@ func getLargestApiNameSize(retApiArray *whisk.RetApiArray, apiPath string, apiVe
 	return maxNameSize
 }
 
+func generatePathParameters(relativePath string) []whisk.ApiParameter {
+	pathParams := []whisk.ApiParameter{}
+	regexObj, err := regexp.Compile(pathParamRegex)
+	if err != nil {
+		whisk.Debug(whisk.DbgError, "Failed to match path '%s' to regular expressions `%s`\n", relativePath, pathParamRegex)
+	}
+	matches := regexObj.FindAllString(relativePath, -1)
+	if matches != nil {
+		for _, paramName := range matches {
+			//The next 3 lines clean up the paramName, as the matches are something like `/{param}`
+			openIdx := strings.IndexRune(paramName, '{')
+			closeIdx := strings.IndexRune(paramName, '}')
+			paramName = string([]rune(paramName)[openIdx+1 : closeIdx])
+			param := whisk.ApiParameter{Name: paramName, In: "path", Required: true, Type: "string",
+				Description: wski18n.T("Default description for '{{.name}}'", map[string]interface{}{"name": paramName})}
+			pathParams = append(pathParams, param)
+		}
+	}
+
+	return pathParams
+}
+
 /*
  * if # args = 4
  * args[0] = API base path
@@ -754,6 +807,9 @@ func parseApi(cmd *cobra.Command, args []string) (*whisk.Api, *QualifiedName, er
 			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
@@ -768,6 +824,19 @@ func parseApi(cmd *cobra.Command, args []string) (*whisk.Api, *QualifiedName, er
 		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 {
@@ -827,6 +896,7 @@ func parseApi(cmd *cobra.Command, args []string) (*whisk.Api, *QualifiedName, er
 	if !basepathArgIsApiName {
 		api.Id = "API:" + api.Namespace + ":" + api.GatewayBasePath
 	}
+	api.PathParameters = generatePathParameters(api.GatewayRelPath)
 
 	whisk.Debug(whisk.DbgInfo, "Parsed api struct: %#v\n", api)
 	return api, qName, nil
diff --git a/tests/src/test/scala/whisk/core/cli/test/ApiGwCliTests.scala b/tests/src/test/scala/whisk/core/cli/test/ApiGwCliTests.scala
index ba886c0..13ffc6a 100644
--- a/tests/src/test/scala/whisk/core/cli/test/ApiGwCliTests.scala
+++ b/tests/src/test/scala/whisk/core/cli/test/ApiGwCliTests.scala
@@ -17,17 +17,236 @@
 
 package whisk.core.cli.test
 
+import com.jayway.restassured.RestAssured
+
+import common.{TestUtils, Wsk}
+import common.TestUtils._
+
 import org.junit.runner.RunWith
 import org.scalatest.junit.JUnitRunner
 
-import common.Wsk
-import common.TestUtils.SUCCESS_EXIT
+import scala.concurrent.duration.DurationInt
+import scala.util.parsing.json.JSON
 
 /**
- * Tests for basic CLI usage. Some of these tests require a deployed backend.
- */
+  * Tests for basic CLI usage. Some of these tests require a deployed backend.
+  */
 @RunWith(classOf[JUnitRunner])
 class ApiGwCliTests extends ApiGwTests {
   override lazy val wsk: common.Wsk = new Wsk
   override lazy val createCode = SUCCESS_EXIT
+  behavior of "Cli Wsk api creation with path parameters no swagger"
+
+  it should "fail to create an API if the relative path contains invalid path parameters" in withAssetCleaner(wskprops) {(wp, assetHelper) =>
+    val actionName = "APIGWTEST_BAD_RELATIVE_PATH_ACTION"
+    val basePath = "/mybase/path"
+    val file = TestUtils.getTestActionFilename(s"echo-web-http.js")
+    assetHelper.withCleaner(wsk.action, actionName, confirmDelete = true) {
+      (action, _) =>
+        action.create(actionName, Some(file), web = Some("true"))
+    }
+    var relPath = "/bad/{path/value"
+    var rr = apiCreate(basepath = Some(basePath),
+      relpath = Some(relPath),
+      operation = Some("GET"),
+      action = Some(actionName),
+      expectedExitCode = ANY_ERROR_EXIT)
+    rr.stderr should include(
+      s"Relative path '${relPath}' does not include valid path parameters. Each parameter must be enclosed in curly braces '{}'.")
+
+    relPath = "/bad/path}/value"
+    rr = apiCreate(basepath = Some(basePath),
+      relpath = Some(relPath),
+      operation = Some("GET"),
+      action = Some(actionName),
+      expectedExitCode = ANY_ERROR_EXIT)
+    rr.stderr should include(
+      s"Relative path '${relPath}' does not include valid path parameters. Each parameter must be enclosed in curly braces '{}'.")
+
+    relPath = "/bad/{path/va}lue"
+    rr = apiCreate(basepath = Some(basePath),
+      relpath = Some(relPath),
+      operation = Some("GET"),
+      action = Some(actionName),
+      expectedExitCode = ANY_ERROR_EXIT)
+    rr.stderr should include(
+      s"Relative path '${relPath}' does not include valid path parameters. Each parameter must be enclosed in curly braces '{}'.")
+
+    relPath = "/ba}d/{path/value"
+    rr = apiCreate(basepath = Some(basePath),
+      relpath = Some(relPath),
+      operation = Some("GET"),
+      action = Some(actionName),
+      expectedExitCode = ANY_ERROR_EXIT)
+    rr.stderr should include(
+      s"Relative path '${relPath}' does not include valid path parameters. Each parameter must be enclosed in curly braces '{}'.")
+
+    relPath = "/ba}d/{p{at}h/value"
+    rr = apiCreate(basepath = Some(basePath),
+      relpath = Some(relPath),
+      operation = Some("GET"),
+      action = Some(actionName),
+      expectedExitCode = ANY_ERROR_EXIT)
+    rr.stderr should include(
+      s"Relative path '${relPath}' does not include valid path parameters. Each parameter must be enclosed in curly braces '{}'.")
+  }
+
+  it should "fail to create an API if the base path contains path parameters" in withAssetCleaner(wskprops) {(wp, assetHelper) =>
+    val actionName = "APIGWTEST_BAD_BASE_PATH_ACTION"
+    val basePath = "/mybase/{path}"
+    val file = TestUtils.getTestActionFilename(s"echo-web-http.js")
+    assetHelper.withCleaner(wsk.action, actionName, confirmDelete = true) {
+      (action, _) =>
+        action.create(actionName, Some(file), web = Some("true"))
+    }
+    val relPath = "/bad/{path}/value"
+    val rr = apiCreate(basepath = Some(basePath),
+      relpath = Some(relPath),
+      operation = Some("GET"),
+      action = Some(actionName),
+      expectedExitCode = ANY_ERROR_EXIT)
+    rr.stderr should include(
+      s"The base path '${basePath}' cannot have parameters. Only the relative path supports path parameters.")
+  }
+
+  it should "fail to create an Api if path parameters are specified but http response type is not given" in withAssetCleaner(
+    wskprops) { (wp, assetHelper) =>
+    val actionName = "CLI_APIGWTEST_PATH_param_fail1_action"
+    val file = TestUtils.getTestActionFilename(s"echo-web-http.js")
+    assetHelper.withCleaner(wsk.action, actionName, confirmDelete = true) {
+      (action, _) =>
+        action.create(actionName, Some(file), web = Some("true"))
+    }
+    val relPath = "/bad/{path}/value"
+    val rr = apiCreate(basepath = Some("/mybase"),
+                       relpath = Some(relPath),
+                       operation = Some("GET"),
+                       action = Some(actionName),
+                       expectedExitCode = ANY_ERROR_EXIT)
+    rr.stderr should include(
+      s"A response type of 'http' is required when using path parameters.")
+  }
+
+  it should "create api with path parameters for the verb" in withAssetCleaner(
+    wskprops) { (wp, assetHelper) =>
+    val testName = "CLI_APIGWTEST_PATH_PARAMS1"
+    val testBasePath = s"/${testName}_bp"
+    val testUrlName1 = "scooby"
+    val testUrlName2 = "doo"
+    val testRelPath = "/path/{with}/some/{path}/params"
+    val testRelPathGet = s"/path/${testUrlName1}/some/${testUrlName2}/params"
+    val testUrlOp = "get"
+    val testApiName = testName + " API Name"
+    val actionName = testName + "_action"
+    var exception: Throwable = null
+    val reqPath = "\\$\\(request.path\\)"
+
+    // Create the action for the API.  It must be a "web-action" action.
+    val file = TestUtils.getTestActionFilename(s"echo-web-http.js")
+    assetHelper.withCleaner(wsk.action, actionName, confirmDelete = true) {
+      (action, _) =>
+        action.create(actionName, Some(file), web = Some("true"))
+    }
+    try {
+      var rr = apiCreate(
+        basepath = Some(testBasePath),
+        relpath = Some(testRelPath),
+        operation = Some(testUrlOp),
+        action = Some(actionName),
+        apiname = Some(testApiName),
+        responsetype = Some("http")
+      )
+      verifyApiCreated(rr)
+      val swaggerApiUrl = getSwaggerUrl(rr).replace("{with}", testUrlName1).replace("{path}", testUrlName2)
+
+      //Validate the api created contained parameters and they were correct
+      rr = apiGet(basepathOrApiName = Some(testApiName))
+      rr.stdout should include(testBasePath)
+      rr.stdout should include(s"${actionName}")
+      rr.stdout should include regex (""""cors":\s*\{\s*\n\s*"enabled":\s*true""")
+      rr.stdout should include regex (s"""target-url.*${actionName}.http${reqPath}""")
+      val params = getParametersFromJson(rr, testRelPath)
+      params.size should be(2)
+      validateParameter(params(0), "with", "path", true, "string", "Default description for 'with'")
+      validateParameter(params(1), "path", "path", true, "string", "Default description for 'path'")
+
+      //Lets call the swagger url so we can make sure the response is valid and contains our path in the ow path
+      val apiToInvoke = s"$swaggerApiUrl"
+      println(s"Invoking: '${apiToInvoke}'")
+      val response = whisk.utils.retry({
+        val response = RestAssured.given().config(getSslConfig()).get(s"$apiToInvoke")
+        response.statusCode should be(200)
+        response
+      }, 6, Some(2.second))
+      val jsonReponse = JSON.parseFull(response.asString()).asInstanceOf[Option[Map[String, String]]].get
+      jsonReponse.get("__ow_path").get should not be ("")
+      jsonReponse.get("__ow_path").get should include (testRelPathGet)
+    } catch {
+      case unknown: Throwable => exception = unknown
+    } finally {
+      apiDelete(basepathOrApiName = testBasePath)
+    }
+    assert(exception == null)
+  }
+
+  it should "create api with path parameters and pass them into the action bound to the api" in withAssetCleaner(
+    wskprops) { (wp, assetHelper) =>
+    val testName = "CLI_APIGWTEST_PATH_PARAMS2"
+    val testBasePath = "/" + testName + "_bp"
+    val testRelPath = "/path/{with}/some/{path}"
+    val testUrlName1 = "scooby"
+    val testUrlName2 = "doo"
+    val testRelPathGet = s"/path/${testUrlName1}/some/$testUrlName2"
+    val testUrlOp = "get"
+    val testApiName = testName + " API Name"
+    val actionName = testName + "_action"
+    var exception: Throwable = null
+    val reqPath = "\\$\\(request.path\\)"
+    // Create the action for the API.  It must be a "web-action" action.
+    val file = TestUtils.getTestActionFilename(s"echo-web-http.js")
+    assetHelper.withCleaner(wsk.action, actionName, confirmDelete = true) {
+      (action, _) =>
+        action.create(actionName, Some(file), web = Some("true"))
+    }
+    try {
+      var rr = apiCreate(
+        basepath = Some(testBasePath),
+        relpath = Some(testRelPath),
+        operation = Some(testUrlOp),
+        action = Some(actionName),
+        apiname = Some(testApiName),
+        responsetype = Some("http")
+      )
+      verifyApiCreated(rr)
+      val swaggerApiUrl = getSwaggerUrl(rr).replace("{with}", testUrlName1).replace("{path}", testUrlName2)
+
+      //Validate the api created contained parameters and they were correct
+      rr = apiGet(basepathOrApiName = Some(testApiName))
+      rr.stdout should include(testBasePath)
+      rr.stdout should include(s"${actionName}")
+      rr.stdout should include regex (""""cors":\s*\{\s*\n\s*"enabled":\s*true""")
+      rr.stdout should include regex (s"""target-url.*${actionName}.http${reqPath}""")
+      val params = getParametersFromJson(rr, testRelPath)
+      params.size should be(2)
+      validateParameter(params(0), "with", "path", true, "string", "Default description for 'with'")
+      validateParameter(params(1), "path", "path", true, "string", "Default description for 'path'")
+
+      //Lets call the swagger url so we can make sure the response is valid and contains our path in the ow path
+      val apiToInvoke = s"$swaggerApiUrl"
+      println(s"Invoking: '${apiToInvoke}'")
+      val response = whisk.utils.retry({
+        val response = RestAssured.given().config(getSslConfig()).get(s"$apiToInvoke")
+        response.statusCode should be(200)
+        response
+      }, 6, Some(2.second))
+      val jsonReponse = JSON.parseFull(response.asString()).asInstanceOf[Option[Map[String, String]]].get
+      jsonReponse.get("__ow_path").get should not be ("")
+      jsonReponse.get("__ow_path").get should include (testRelPathGet)
+    } catch {
+      case unknown: Throwable => exception = unknown
+    } finally {
+      apiDelete(basepathOrApiName = testBasePath)
+    }
+    assert(exception == null)
+  }
 }
diff --git a/tests/src/test/scala/whisk/core/cli/test/ApiGwTests.scala b/tests/src/test/scala/whisk/core/cli/test/ApiGwTests.scala
index ce38449..4bbe395 100644
--- a/tests/src/test/scala/whisk/core/cli/test/ApiGwTests.scala
+++ b/tests/src/test/scala/whisk/core/cli/test/ApiGwTests.scala
@@ -17,6 +17,8 @@
 
 package whisk.core.cli.test
 
+import com.jayway.restassured.RestAssured
+
 import java.io.File
 import java.io.BufferedWriter
 import java.io.FileWriter
@@ -25,9 +27,19 @@ import org.junit.runner.RunWith
 
 import org.scalatest.junit.JUnitRunner
 
+import com.jayway.restassured.config.RestAssuredConfig
+import com.jayway.restassured.config.SSLConfig
+
 import common.TestUtils._
-import common.TestCLIUtils
-import common.WskProps
+import common.TestUtils
+import common.WhiskProperties
+import common.{TestCLIUtils, WhiskProperties, WskProps}
+
+import scala.concurrent.duration.DurationInt
+import scala.util.parsing.json.JSON
+import scala.util.matching.Regex
+
+import org.apache.commons.io.FileUtils
 
 /**
  * Tests for testing the CLI "api" subcommand.  Most of these tests require a deployed backend.
@@ -163,8 +175,125 @@ abstract class ApiGwTests extends BaseApiGwTests {
     rr.stderr should include("The supplied authentication is invalid")
   }
 
+  def writeSwaggerFile(rr: RunResult): File = {
+    val swaggerfile = File.createTempFile("api", ".json")
+    swaggerfile.deleteOnExit()
+    val bw = new BufferedWriter(new FileWriter(swaggerfile))
+    bw.write(rr.stdout)
+    bw.close()
+    return swaggerfile
+  }
+
+  def getSwaggerUrl(rr: RunResult) : String = {
+    rr.stdout.split("\n")(1)
+  }
+
+  def replaceStringInFile(fileName: String, replacements: Map[Regex, String]): String = {
+    val encoding = "UTF-8"
+
+    val contents = FileUtils.readFileToString(new File(fileName), encoding)
+    var newContents = contents
+    replacements foreach ((regex) => newContents = regex._1.replaceAllIn(newContents, regex._2))
+    val tmpFileName = fileName + "-" + System.currentTimeMillis() + ".tmp"
+    val tmpFile = new File(tmpFileName)
+    if (tmpFile.exists()) {
+      FileUtils.forceDelete(tmpFile)
+    }
+    FileUtils.writeStringToFile(new File(tmpFileName), newContents, encoding)
+    tmpFileName
+  }
+
+  def getParametersFromJson(rr: RunResult, pathName: String): List[Map[String, Any]] = {
+    val parsed = JSON.parseFull(rr.stdout).asInstanceOf[Option[Map[String, Map[String, Map[String, Map[String, List[Map[String, String]]]]]]]]
+    parsed.get("paths").get(pathName).get("get").get("parameters").get
+  }
+
+  def getSslConfig(): RestAssuredConfig = {
+    // force RestAssured to allow all hosts in SSL certificates
+    new RestAssuredConfig().sslConfig(new SSLConfig().keystore("keystore", WhiskProperties.getSslCertificateChallenge).allowAllHostnames())
+  }
+
+  def validateParameter(parameter: Map[String, Any], name: String, in: String, required: Boolean, pType: String, description: String): Unit = {
+    parameter.get("name").get should be(name)
+    parameter.get("in").get should be(in)
+    parameter.get("required") should be(Some(required))
+    parameter.get("type").get should be(pType)
+    parameter.get("description").get should be(description)
+  }
+
   behavior of "Wsk api"
 
+  behavior of "Cli Wsk api creation with path parameters with swagger"
+
+  it should "create the API when swagger file contains path parameters" in withAssetCleaner(
+    wskprops) { (wp, assetHelper) =>
+    val actionName = "cli_apigwtest_path_param_swagger_action"
+    var exception: Throwable = null
+    val apiName = "/guest/v1"
+    val reqPath = "\\$\\(request.path\\)"
+    val testRelPath = "/api2/greeting2/{name}"
+    val testUrlName = "scooby"
+    val testRelPathGet = s"/api2/greeting2/$testUrlName"
+    val testUrlOp = "get"
+    var file = TestUtils.getTestActionFilename(s"echo-web-http.js")
+    val hostRegex = "%HOST%".r
+    assetHelper.withCleaner(wsk.action, actionName, confirmDelete = true) {
+      (action, _) =>
+        action.create(actionName, Some(file), web = Some("true"))
+    }
+    try {
+      //Create the API
+      var rr : RunResult = apiCreate(
+        basepath = Some(apiName),
+        relpath = Some(testRelPath),
+        operation = Some(testUrlOp),
+        action = Some(actionName),
+        responsetype = Some("http")
+      )
+      verifyApiCreated(rr)
+
+      //Get the api so we can create the swagger from the returned output
+      rr = apiGet(basepathOrApiName = Some(apiName))
+      rr.stdout should include regex (s"""target-url.*${actionName}.http${reqPath}""")
+      val swaggerFile = writeSwaggerFile(rr)
+
+      //Delete the api so we can re-create it using the swagger
+      rr = apiDelete(basepathOrApiName = apiName)
+      verifyApiDeleted(rr)
+
+      //Create the api using the swagger file.
+
+      rr = apiCreate(swagger = Some(swaggerFile.getAbsolutePath()), expectedExitCode = SUCCESS_EXIT)
+      verifyApiCreated(rr)
+      val swaggerApiUrl = getSwaggerUrl(rr).replace("{name}", testUrlName)
+
+      //Lets validate that the swagger we get from the create contains the correct info.
+      rr = apiGet(basepathOrApiName = Some(apiName))
+      rr.stdout should include regex (s"""target-url.*${actionName}.http${reqPath}""")
+      val params = getParametersFromJson(rr, testRelPath)
+      params.size should be(1)
+      validateParameter(params(0), "name", "path", true, "string", "Default description for 'name'")
+
+      //Lets call the swagger url so we can make sure the response is valid and contains our path in the ow path
+      val apiToInvoke = s"$swaggerApiUrl"
+      println(s"Invoking: '${apiToInvoke}'")
+      val response = whisk.utils.retry({
+        val response = RestAssured.given().config(getSslConfig()).get(s"$apiToInvoke")
+        response.statusCode should be(200)
+        response
+      }, 6, Some(2.second))
+      val jsonReponse = JSON.parseFull(response.asString()).asInstanceOf[Option[Map[String, String]]].get
+      jsonReponse.get("__ow_path").get should not be ("")
+      jsonReponse.get("__ow_path").get should include (testRelPathGet)
+
+    } catch {
+      case unknown: Throwable => exception = unknown
+    } finally {
+      apiDelete(basepathOrApiName = apiName)
+    }
+    assert(exception == null)
+  }
+
   it should "reject an api commands with an invalid path parameter" in {
     val badpath = "badpath"
 
diff --git a/wski18n/i18n_resources.go b/wski18n/i18n_resources.go
deleted file mode 100644
index bf3bbae..0000000
--- a/wski18n/i18n_resources.go
+++ /dev/null
@@ -1,463 +0,0 @@
-/*
- * 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.
- */
-
-// Code generated by go-bindata.
-// sources:
-// wski18n/resources/de_DE.all.json
-// wski18n/resources/en_US.all.json
-// wski18n/resources/es_ES.all.json
-// wski18n/resources/fr_FR.all.json
-// wski18n/resources/it_IT.all.json
-// wski18n/resources/ja_JA.all.json
-// wski18n/resources/ko_KR.all.json
-// wski18n/resources/pt_BR.all.json
-// wski18n/resources/zh_Hans.all.json
-// wski18n/resources/zh_Hant.all.json
-// DO NOT EDIT!
-
-package wski18n
-
-import (
-	"bytes"
-	"compress/gzip"
-	"fmt"
-	"io"
-	"io/ioutil"
-	"os"
-	"path/filepath"
-	"strings"
-	"time"
-)
-
-func bindataRead(data []byte, name string) ([]byte, error) {
-	gz, err := gzip.NewReader(bytes.NewBuffer(data))
-	if err != nil {
-		return nil, fmt.Errorf("Read %q: %v", name, err)
-	}
-
-	var buf bytes.Buffer
-	_, err = io.Copy(&buf, gz)
-	clErr := gz.Close()
-
-	if err != nil {
-		return nil, fmt.Errorf("Read %q: %v", name, err)
-	}
-	if clErr != nil {
-		return nil, err
-	}
-
-	return buf.Bytes(), nil
-}
-
-type asset struct {
-	bytes []byte
-	info  os.FileInfo
-}
-
-type bindataFileInfo struct {
-	name    string
-	size    int64
-	mode    os.FileMode
-	modTime time.Time
-}
-
-func (fi bindataFileInfo) Name() string {
-	return fi.name
-}
-func (fi bindataFileInfo) Size() int64 {
-	return fi.size
-}
-func (fi bindataFileInfo) Mode() os.FileMode {
-	return fi.mode
-}
-func (fi bindataFileInfo) ModTime() time.Time {
-	return fi.modTime
-}
-func (fi bindataFileInfo) IsDir() bool {
-	return false
-}
-func (fi bindataFileInfo) Sys() interface{} {
-	return nil
-}
-
-var _wski18nResourcesDe_deAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00")
-
-func wski18nResourcesDe_deAllJsonBytes() ([]byte, error) {
-	return bindataRead(
-		_wski18nResourcesDe_deAllJson,
-		"wski18n/resources/de_DE.all.json",
-	)
-}
-
-func wski18nResourcesDe_deAllJson() (*asset, error) {
-	bytes, err := wski18nResourcesDe_deAllJsonBytes()
-	if err != nil {
-		return nil, err
-	}
-
-	info := bindataFileInfo{name: "wski18n/resources/de_DE.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1510606284, 0)}
-	a := &asset{bytes: bytes, info: info}
-	return a, nil
-}
-
-var _wski18nResourcesEn_usAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xcc\x5d\x4f\x73\xdb\x38\xb2\xbf\xcf\xa7\xe8\xca\xc5\xce\x2b\xd9\xd9\x3d\xbd\x7a\x49\xcd\x41\x13\x7b\x36\xde\x49\x6c\x57\xec\xcc\xee\xd4\x66\x6b\x04\x91\x90\x85\x35\x05\x70\x00\xd0\xb2\x92\xf1\x77\x7f\x05\x80\xa4\x48\x09\x7f\x49\x39\xd9\x53\x1c\xb1\xfb\xd7\x0d\xa0\x01\x34\x1a\x0d\xe0\x5f\x3f\x00\x7c\xfd\x01\x00\xe0\x05\xc9\x5f\xbc\x86\x17\xd3\xb2\x2c\x48\x86\x24\x61\x14\xf0\x23\x91\x38\x87\x8a\xe2\xc7\x12 [...]
-
-func wski18nResourcesEn_usAllJsonBytes() ([]byte, error) {
-	return bindataRead(
-		_wski18nResourcesEn_usAllJson,
-		"wski18n/resources/en_US.all.json",
-	)
-}
-
-func wski18nResourcesEn_usAllJson() (*asset, error) {
-	bytes, err := wski18nResourcesEn_usAllJsonBytes()
-	if err != nil {
-		return nil, err
-	}
-
-	info := bindataFileInfo{name: "wski18n/resources/en_US.all.json", size: 51835, mode: os.FileMode(420), modTime: time.Unix(1510606284, 0)}
-	a := &asset{bytes: bytes, info: info}
-	return a, nil
-}
-
-var _wski18nResourcesEs_esAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00")
-
-func wski18nResourcesEs_esAllJsonBytes() ([]byte, error) {
-	return bindataRead(
-		_wski18nResourcesEs_esAllJson,
-		"wski18n/resources/es_ES.all.json",
-	)
-}
-
-func wski18nResourcesEs_esAllJson() (*asset, error) {
-	bytes, err := wski18nResourcesEs_esAllJsonBytes()
-	if err != nil {
-		return nil, err
-	}
-
-	info := bindataFileInfo{name: "wski18n/resources/es_ES.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1510606284, 0)}
-	a := &asset{bytes: bytes, info: info}
-	return a, nil
-}
-
-var _wski18nResourcesFr_frAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8a\xe6\x52\x50\xa8\xe6\x52\x50\x50\x50\x50\xca\x4c\x51\xb2\x52\x50\x4a\xaa\x2c\x48\x2c\x2e\x56\x48\x4e\x2d\x2a\xc9\x4c\xcb\x4c\x4e\x2c\x49\x55\x48\xce\x48\x4d\xce\xce\xcc\x4b\x57\xd2\x81\x28\x2c\x29\x4a\xcc\x2b\xce\x49\x2c\xc9\xcc\xcf\x03\xe9\x08\xce\xcf\x4d\x55\x40\x12\x53\xc8\xcc\x53\x70\x2b\x4a\xcd\x4b\xce\x50\xe2\x52\x50\xa8\xe5\x8a\xe5\x02\x04\x00\x00\xff\xff\x45\xa4\xe9\x62\x65\x00\x00\x00")
-
-func wski18nResourcesFr_frAllJsonBytes() ([]byte, error) {
-	return bindataRead(
-		_wski18nResourcesFr_frAllJson,
-		"wski18n/resources/fr_FR.all.json",
-	)
-}
-
-func wski18nResourcesFr_frAllJson() (*asset, error) {
-	bytes, err := wski18nResourcesFr_frAllJsonBytes()
-	if err != nil {
-		return nil, err
-	}
-
-	info := bindataFileInfo{name: "wski18n/resources/fr_FR.all.json", size: 101, mode: os.FileMode(420), modTime: time.Unix(1510606284, 0)}
-	a := &asset{bytes: bytes, info: info}
-	return a, nil
-}
-
-var _wski18nResourcesIt_itAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00")
-
-func wski18nResourcesIt_itAllJsonBytes() ([]byte, error) {
-	return bindataRead(
-		_wski18nResourcesIt_itAllJson,
-		"wski18n/resources/it_IT.all.json",
-	)
-}
-
-func wski18nResourcesIt_itAllJson() (*asset, error) {
-	bytes, err := wski18nResourcesIt_itAllJsonBytes()
-	if err != nil {
-		return nil, err
-	}
-
-	info := bindataFileInfo{name: "wski18n/resources/it_IT.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1510606284, 0)}
-	a := &asset{bytes: bytes, info: info}
-	return a, nil
-}
-
-var _wski18nResourcesJa_jaAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00")
-
-func wski18nResourcesJa_jaAllJsonBytes() ([]byte, error) {
-	return bindataRead(
-		_wski18nResourcesJa_jaAllJson,
-		"wski18n/resources/ja_JA.all.json",
-	)
-}
-
-func wski18nResourcesJa_jaAllJson() (*asset, error) {
-	bytes, err := wski18nResourcesJa_jaAllJsonBytes()
-	if err != nil {
-		return nil, err
-	}
-
-	info := bindataFileInfo{name: "wski18n/resources/ja_JA.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1510606284, 0)}
-	a := &asset{bytes: bytes, info: info}
-	return a, nil
-}
-
-var _wski18nResourcesKo_krAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00")
-
-func wski18nResourcesKo_krAllJsonBytes() ([]byte, error) {
-	return bindataRead(
-		_wski18nResourcesKo_krAllJson,
-		"wski18n/resources/ko_KR.all.json",
-	)
-}
-
-func wski18nResourcesKo_krAllJson() (*asset, error) {
-	bytes, err := wski18nResourcesKo_krAllJsonBytes()
-	if err != nil {
-		return nil, err
-	}
-
-	info := bindataFileInfo{name: "wski18n/resources/ko_KR.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1510606284, 0)}
-	a := &asset{bytes: bytes, info: info}
-	return a, nil
-}
-
-var _wski18nResourcesPt_brAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00")
-
-func wski18nResourcesPt_brAllJsonBytes() ([]byte, error) {
-	return bindataRead(
-		_wski18nResourcesPt_brAllJson,
-		"wski18n/resources/pt_BR.all.json",
-	)
-}
-
-func wski18nResourcesPt_brAllJson() (*asset, error) {
-	bytes, err := wski18nResourcesPt_brAllJsonBytes()
-	if err != nil {
-		return nil, err
-	}
-
-	info := bindataFileInfo{name: "wski18n/resources/pt_BR.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1510606284, 0)}
-	a := &asset{bytes: bytes, info: info}
-	return a, nil
-}
-
-var _wski18nResourcesZh_hansAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00")
-
-func wski18nResourcesZh_hansAllJsonBytes() ([]byte, error) {
-	return bindataRead(
-		_wski18nResourcesZh_hansAllJson,
-		"wski18n/resources/zh_Hans.all.json",
-	)
-}
-
-func wski18nResourcesZh_hansAllJson() (*asset, error) {
-	bytes, err := wski18nResourcesZh_hansAllJsonBytes()
-	if err != nil {
-		return nil, err
-	}
-
-	info := bindataFileInfo{name: "wski18n/resources/zh_Hans.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1510606284, 0)}
-	a := &asset{bytes: bytes, info: info}
-	return a, nil
-}
-
-var _wski18nResourcesZh_hantAllJson = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x01\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00")
-
-func wski18nResourcesZh_hantAllJsonBytes() ([]byte, error) {
-	return bindataRead(
-		_wski18nResourcesZh_hantAllJson,
-		"wski18n/resources/zh_Hant.all.json",
-	)
-}
-
-func wski18nResourcesZh_hantAllJson() (*asset, error) {
-	bytes, err := wski18nResourcesZh_hantAllJsonBytes()
-	if err != nil {
-		return nil, err
-	}
-
-	info := bindataFileInfo{name: "wski18n/resources/zh_Hant.all.json", size: 0, mode: os.FileMode(420), modTime: time.Unix(1510606284, 0)}
-	a := &asset{bytes: bytes, info: info}
-	return a, nil
-}
-
-// Asset loads and returns the asset for the given name.
-// It returns an error if the asset could not be found or
-// could not be loaded.
-func Asset(name string) ([]byte, error) {
-	cannonicalName := strings.Replace(name, "\\", "/", -1)
-	if f, ok := _bindata[cannonicalName]; ok {
-		a, err := f()
-		if err != nil {
-			return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
-		}
-		return a.bytes, nil
-	}
-	return nil, fmt.Errorf("Asset %s not found", name)
-}
-
-// MustAsset is like Asset but panics when Asset would return an error.
-// It simplifies safe initialization of global variables.
-func MustAsset(name string) []byte {
-	a, err := Asset(name)
-	if err != nil {
-		panic("asset: Asset(" + name + "): " + err.Error())
-	}
-
-	return a
-}
-
-// AssetInfo loads and returns the asset info for the given name.
-// It returns an error if the asset could not be found or
-// could not be loaded.
-func AssetInfo(name string) (os.FileInfo, error) {
-	cannonicalName := strings.Replace(name, "\\", "/", -1)
-	if f, ok := _bindata[cannonicalName]; ok {
-		a, err := f()
-		if err != nil {
-			return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
-		}
-		return a.info, nil
-	}
-	return nil, fmt.Errorf("AssetInfo %s not found", name)
-}
-
-// AssetNames returns the names of the assets.
-func AssetNames() []string {
-	names := make([]string, 0, len(_bindata))
-	for name := range _bindata {
-		names = append(names, name)
-	}
-	return names
-}
-
-// _bindata is a table, holding each asset generator, mapped to its name.
-var _bindata = map[string]func() (*asset, error){
-	"wski18n/resources/de_DE.all.json":   wski18nResourcesDe_deAllJson,
-	"wski18n/resources/en_US.all.json":   wski18nResourcesEn_usAllJson,
-	"wski18n/resources/es_ES.all.json":   wski18nResourcesEs_esAllJson,
-	"wski18n/resources/fr_FR.all.json":   wski18nResourcesFr_frAllJson,
-	"wski18n/resources/it_IT.all.json":   wski18nResourcesIt_itAllJson,
-	"wski18n/resources/ja_JA.all.json":   wski18nResourcesJa_jaAllJson,
-	"wski18n/resources/ko_KR.all.json":   wski18nResourcesKo_krAllJson,
-	"wski18n/resources/pt_BR.all.json":   wski18nResourcesPt_brAllJson,
-	"wski18n/resources/zh_Hans.all.json": wski18nResourcesZh_hansAllJson,
-	"wski18n/resources/zh_Hant.all.json": wski18nResourcesZh_hantAllJson,
-}
-
-// AssetDir returns the file names below a certain
-// directory embedded in the file by go-bindata.
-// For example if you run go-bindata on data/... and data contains the
-// following hierarchy:
-//     data/
-//       foo.txt
-//       img/
-//         a.png
-//         b.png
-// then AssetDir("data") would return []string{"foo.txt", "img"}
-// AssetDir("data/img") would return []string{"a.png", "b.png"}
-// AssetDir("foo.txt") and AssetDir("notexist") would return an error
-// AssetDir("") will return []string{"data"}.
-func AssetDir(name string) ([]string, error) {
-	node := _bintree
-	if len(name) != 0 {
-		cannonicalName := strings.Replace(name, "\\", "/", -1)
-		pathList := strings.Split(cannonicalName, "/")
-		for _, p := range pathList {
-			node = node.Children[p]
-			if node == nil {
-				return nil, fmt.Errorf("Asset %s not found", name)
-			}
-		}
-	}
-	if node.Func != nil {
-		return nil, fmt.Errorf("Asset %s not found", name)
-	}
-	rv := make([]string, 0, len(node.Children))
-	for childName := range node.Children {
-		rv = append(rv, childName)
-	}
-	return rv, nil
-}
-
-type bintree struct {
-	Func     func() (*asset, error)
-	Children map[string]*bintree
-}
-
-var _bintree = &bintree{nil, map[string]*bintree{
-	"wski18n": {nil, map[string]*bintree{
-		"resources": {nil, map[string]*bintree{
-			"de_DE.all.json":   {wski18nResourcesDe_deAllJson, map[string]*bintree{}},
-			"en_US.all.json":   {wski18nResourcesEn_usAllJson, map[string]*bintree{}},
-			"es_ES.all.json":   {wski18nResourcesEs_esAllJson, map[string]*bintree{}},
-			"fr_FR.all.json":   {wski18nResourcesFr_frAllJson, map[string]*bintree{}},
-			"it_IT.all.json":   {wski18nResourcesIt_itAllJson, map[string]*bintree{}},
-			"ja_JA.all.json":   {wski18nResourcesJa_jaAllJson, map[string]*bintree{}},
-			"ko_KR.all.json":   {wski18nResourcesKo_krAllJson, map[string]*bintree{}},
-			"pt_BR.all.json":   {wski18nResourcesPt_brAllJson, map[string]*bintree{}},
-			"zh_Hans.all.json": {wski18nResourcesZh_hansAllJson, map[string]*bintree{}},
-			"zh_Hant.all.json": {wski18nResourcesZh_hantAllJson, map[string]*bintree{}},
-		}},
-	}},
-}}
-
-// RestoreAsset restores an asset under the given directory
-func RestoreAsset(dir, name string) error {
-	data, err := Asset(name)
-	if err != nil {
-		return err
-	}
-	info, err := AssetInfo(name)
-	if err != nil {
-		return err
-	}
-	err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
-	if err != nil {
-		return err
-	}
-	err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
-	if err != nil {
-		return err
-	}
-	err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
-	if err != nil {
-		return err
-	}
-	return nil
-}
-
-// RestoreAssets restores an asset under the given directory recursively
-func RestoreAssets(dir, name string) error {
-	children, err := AssetDir(name)
-	// File
-	if err != nil {
-		return RestoreAsset(dir, name)
-	}
-	// Dir
-	for _, child := range children {
-		err = RestoreAssets(dir, filepath.Join(name, child))
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func _filePath(dir, name string) string {
-	cannonicalName := strings.Replace(name, "\\", "/", -1)
-	return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
-}
diff --git a/wski18n/resources/en_US.all.json b/wski18n/resources/en_US.all.json
index 2748280..ee7d434 100644
--- a/wski18n/resources/en_US.all.json
+++ b/wski18n/resources/en_US.all.json
@@ -1558,5 +1558,21 @@
   {
     "id": "Cannot specify both --kind and --docker at the same time.",
     "translation": "Cannot specify both --kind and --docker at the same time."
+  },
+  {
+    "id": "Relative path '{{.path}}' does not include valid path parameters. Each parameter must be enclosed in curly braces '{}'.",
+    "translation": "Relative path '{{.path}}' does not include valid path parameters. Each parameter must be enclosed in curly braces '{}'."
+  },
+  {
+    "id": "Default description for '{{.name}}'",
+    "translation": "Default description for '{{.name}}'"
+  },
+  {
+    "id": "The base path '{{.path}}' cannot have parameters. Only the relative path supports path parameters.",
+    "translation": "The base path '{{.path}}' cannot have parameters. Only the relative path supports path parameters."
+  },
+  {
+    "id": "A response type of 'http' is required when using path parameters.",
+    "translation": "A response type of 'http' is required when using path parameters."
   }
 ]

-- 
To stop receiving notification emails like this one, please contact
dubeejw@apache.org.