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.