Repository: goadesign/goa
Pull request #2307 (2019-10-03 23:59:04)

Fix invalid paths in generated OpenAPI definitions
This is backport of #2306 to v3.


Patch:
diff --git a/dsl/meta.go b/dsl/meta.go
index 0bcc7394..07c41ae7 100644
--- a/dsl/meta.go
+++ b/dsl/meta.go
@@ -126,6 +126,7 @@ import (
 //            Meta("swagger:tag:Backend:desc", "Description of Backend")
 //            Meta("swagger:tag:Backend:url", "http://example.com")
 //            Meta("swagger:tag:Backend:url:desc", "See more docs here")
+//            Meta("swagger:tag:Backend:extension:x-data", `{"foo":"bar"}`)
 //        })
 //    })
 //
diff --git a/http/codegen/openapi/openapi_v2_builder.go b/http/codegen/openapi/openapi_v2_builder.go
index a71ceb09..03de58f1 100644
--- a/http/codegen/openapi/openapi_v2_builder.go
+++ b/http/codegen/openapi/openapi_v2_builder.go
@@ -106,25 +106,34 @@ func NewV2(root *expr.RootExpr, h *expr.HostExpr) (*V2, error) {
 // ExtensionsFromExpr generates swagger extensions from the given meta
 // expression.
 func ExtensionsFromExpr(mdata expr.MetaExpr) map[string]interface{} {
+	return extensionsFromExprWithPrefix(mdata, "swagger:extension:")
+}
+
+// extensionsFromExprWithPrefix generates swagger extensions from
+// the given meta expression with keys starting the given prefix.
+func extensionsFromExprWithPrefix(mdata expr.MetaExpr, prefix string) map[string]interface{} {
+	if !strings.HasSuffix(prefix, ":") {
+		prefix += ":"
+	}
 	extensions := make(map[string]interface{})
 	for key, value := range mdata {
-		chunks := strings.Split(key, ":")
-		if len(chunks) != 3 {
+		if !strings.HasPrefix(key, prefix) {
 			continue
 		}
-		if chunks[0] != "swagger" || chunks[1] != "extension" {
+		name := key[len(prefix):]
+		if strings.Contains(name, ":") {
 			continue
 		}
-		if !strings.HasPrefix(chunks[2], "x-") {
+		if !strings.HasPrefix(name, "x-") {
 			continue
 		}
 		val := value[0]
 		ival := interface{}(val)
 		if err := json.Unmarshal([]byte(val), &ival); err != nil {
-			extensions[chunks[2]] = val
+			extensions[name] = val
 			continue
 		}
-		extensions[chunks[2]] = ival
+		extensions[name] = ival
 	}
 	if len(extensions) == 0 {
 		return nil
@@ -325,6 +334,9 @@ func tagsFromExpr(mdata expr.MetaExpr) (tags []*Tag) {
 			tag.ExternalDocs = docs
 		}
 
+		extensionsPrefix := fmt.Sprintf("%s:extension:", key)
+		tag.Extensions = extensionsFromExprWithPrefix(mdata, extensionsPrefix)
+
 		tags = append(tags, tag)
 	}
 
@@ -566,12 +578,7 @@ func buildPathFromFileServer(s *V2, root *expr.RootExpr, fs *expr.HTTPFileServer
 			Schemes:      schemes,
 		}
 
-		key := expr.HTTPWildcardRegex.ReplaceAllStringFunc(
-			path,
-			func(w string) string {
-				return fmt.Sprintf("/{%s}", w[2:])
-			},
-		)
+		key := expr.HTTPWildcardRegex.ReplaceAllString(path, "/{$1}")
 		if key == "" {
 			key = "/"
 		}


Test patch:
diff --git a/http/codegen/openapi/openapi_v2_builder_test.go b/http/codegen/openapi/openapi_v2_builder_test.go
new file mode 100644
index 00000000..ed354798
--- /dev/null
+++ b/http/codegen/openapi/openapi_v2_builder_test.go
@@ -0,0 +1,51 @@
+package openapi
+
+import (
+	"testing"
+
+	"goa.design/goa/v3/expr"
+)
+
+func TestBuildPathFromFileServer(t *testing.T) {
+	cases := []struct {
+		path     string
+		expected string
+	}{
+		{
+			path:     "/foo",
+			expected: "/foo",
+		},
+		{
+			path:     "/foo/{bar}",
+			expected: "/foo/{bar}",
+		},
+		{
+			path:     "/foo/{*bar}",
+			expected: "/foo/{bar}",
+		},
+	}
+	for _, tc := range cases {
+		t.Run(tc.path, func(t *testing.T) {
+			s := &V2{
+				Paths: make(map[string]interface{}),
+			}
+			root := &expr.RootExpr{
+				API: &expr.APIExpr{},
+			}
+			fs := &expr.HTTPFileServerExpr{
+				Service: &expr.HTTPServiceExpr{
+					ServiceExpr: &expr.ServiceExpr{
+						Name: "service",
+					},
+				},
+				RequestPaths: []string{tc.path},
+			}
+			buildPathFromFileServer(s, root, fs)
+			for actual := range s.Paths {
+				if actual != tc.expected {
+					t.Errorf("got %#v, expected %#v", actual, tc.expected)
+				}
+			}
+		})
+	}
+}
diff --git a/http/codegen/testdata/openapi_v2/TestExtensions/endpoint_file0.golden b/http/codegen/testdata/openapi_v2/TestExtensions/endpoint_file0.golden
index ffdc5b0c..a7f5460d 100644
--- a/http/codegen/testdata/openapi_v2/TestExtensions/endpoint_file0.golden
+++ b/http/codegen/testdata/openapi_v2/TestExtensions/endpoint_file0.golden
@@ -1,1 +1,1 @@
-{"swagger":"2.0","info":{"title":"","version":"","x-test-api":"API"},"host":"goa.design","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/":{"post":{"operationId":"testService#testEndpoint","responses":{"204":{"description":"No Content response."}},"schemes":["https"],"summary":"testEndpoint testService","tags":["testService"],"x-test-operation":"Operation"},"x-test-foo":"bar"}},"tags":[{"name":"Backend","description":"Description of Backend","externalDocs":{"description":"See more docs here","url":"http://example.com"}}]}
\ No newline at end of file
+{"swagger":"2.0","info":{"title":"","version":"","x-test-api":"API"},"host":"goa.design","consumes":["application/json","application/xml","application/gob"],"produces":["application/json","application/xml","application/gob"],"paths":{"/":{"post":{"operationId":"testService#testEndpoint","responses":{"204":{"description":"No Content response."}},"schemes":["https"],"summary":"testEndpoint testService","tags":["testService"],"x-test-operation":"Operation"},"x-test-foo":"bar"}},"tags":[{"description":"Description of Backend","externalDocs":{"description":"See more docs here","url":"http://example.com"},"name":"Backend","x-data":{"foo":"bar"}}]}
\ No newline at end of file
diff --git a/http/codegen/testdata/openapi_v2/TestExtensions/endpoint_file1.golden b/http/codegen/testdata/openapi_v2/TestExtensions/endpoint_file1.golden
index c19fd4c0..d0d7f316 100644
--- a/http/codegen/testdata/openapi_v2/TestExtensions/endpoint_file1.golden
+++ b/http/codegen/testdata/openapi_v2/TestExtensions/endpoint_file1.golden
@@ -27,8 +27,10 @@ paths:
       x-test-operation: Operation
     x-test-foo: bar
 tags:
-- name: Backend
-  description: Description of Backend
+- description: Description of Backend
   externalDocs:
     description: See more docs here
     url: http://example.com
+  name: Backend
+  x-data:
+    foo: bar
diff --git a/http/codegen/testdata/openapi_v2_dsls.go b/http/codegen/testdata/openapi_v2_dsls.go
index 2e5fb4ea..a0938dc6 100644
--- a/http/codegen/testdata/openapi_v2_dsls.go
+++ b/http/codegen/testdata/openapi_v2_dsls.go
@@ -278,6 +278,7 @@ var ExtensionDSL = func() {
 		Meta("swagger:tag:Backend:desc", "Description of Backend")
 		Meta("swagger:tag:Backend:url", "http://example.com")
 		Meta("swagger:tag:Backend:url:desc", "See more docs here")
+		Meta("swagger:tag:Backend:extension:x-data", `{"foo":"bar"}`)
 	})
 	Service("testService", func() {
 		Method("testEndpoint", func() {