Repository: goadesign/goa
Pull request #3502 (2024-04-09 14:22:19)

Fix eval.IncompatibleDSL() to not show internal DSL
The `caller()` function is from v1, but had to be modified for v3.

https://github.com/goadesign/goa/blob/v1/dslengine/runner.go#L419-L429

raphael: Very nice, thank you!

Patch:
diff --git a/eval/eval.go b/eval/eval.go
index d8876760..34cb8f03 100644
--- a/eval/eval.go
+++ b/eval/eval.go
@@ -2,10 +2,10 @@ package eval
 
 import (
 	"fmt"
-	"path/filepath"
 	"reflect"
 	"runtime"
 	"strings"
+	"unicode"
 )
 
 // RunDSL iterates through the root expressions and calls WalkSets on each to
@@ -116,8 +116,7 @@ func ReportError(fm string, vals ...any) {
 // IncompatibleDSL should be called by DSL functions when they are invoked in an
 // incorrect context (e.g. "Params" in "Service").
 func IncompatibleDSL() {
-	elems := strings.Split(caller(), ".")
-	ReportError("invalid use of %s", elems[len(elems)-1])
+	ReportError("invalid use of %s", caller())
 }
 
 // InvalidArgError records an invalid argument error. It is used by DSL
@@ -235,13 +234,20 @@ func finalizeSet(set ExpressionSet) {
 
 // caller returns the name of calling function.
 func caller() string {
-	pc, file, _, ok := runtime.Caller(2)
-	if ok && filepath.Base(file) == "current.go" {
-		pc, _, _, ok = runtime.Caller(3)
-	}
-	if !ok {
-		return "<unknown>"
+	for skip := 2; skip <= 3; skip++ {
+		pc, _, _, ok := runtime.Caller(skip)
+		if !ok {
+			break
+		}
+		name := runtime.FuncForPC(pc).Name()
+		elems := strings.Split(name, ".")
+		caller := elems[len(elems)-1]
+		for _, first := range caller {
+			if unicode.IsUpper(first) {
+				return caller
+			}
+			break
+		}
 	}
-
-	return runtime.FuncForPC(pc).Name()
+	return "<unknown>"
 }


Test patch:
diff --git a/expr/http_endpoint_test.go b/expr/http_endpoint_test.go
index ea4cb639..bb221ecb 100644
--- a/expr/http_endpoint_test.go
+++ b/expr/http_endpoint_test.go
@@ -1,6 +1,7 @@
 package expr_test
 
 import (
+	"strings"
 	"testing"
 
 	"goa.design/goa/v3/eval"
@@ -15,10 +16,11 @@ func TestHTTPRouteValidation(t *testing.T) {
 		Error string
 	}{
 		{"valid", testdata.ValidRouteDSL, ""},
-		{"invalid", testdata.DuplicateWCRouteDSL, `route POST "/{id}" of service "InvalidRoute" HTTP endpoint "Method": Wildcard "id" appears multiple times in full path "/{id}/{id}"`},
+		{"duplicate-wc-route", testdata.DuplicateWCRouteDSL, `route POST "/{id}" of service "DuplicateWCRoute" HTTP endpoint "Method": Wildcard "id" appears multiple times in full path "/{id}/{id}"`},
 		{"disallow-response-body", testdata.DisallowResponseBodyHeadDSL, `route HEAD "/" of service "DisallowResponseBody" HTTP endpoint "Method": HTTP status 200: Response body defined for HEAD method which does not allow response body.
 route HEAD "/" of service "DisallowResponseBody" HTTP endpoint "Method": HTTP status 404: Response body defined for HEAD method which does not allow response body.`,
 		},
+		{"invalid", testdata.InvalidRouteDSL, "invalid use of POST in service \"InvalidRoute\""},
 	}
 	for _, c := range cases {
 		t.Run(c.Name, func(t *testing.T) {
@@ -26,7 +28,7 @@ route HEAD "/" of service "DisallowResponseBody" HTTP endpoint "Method": HTTP st
 				expr.RunDSL(t, c.DSL)
 			} else {
 				err := expr.RunInvalidDSL(t, c.DSL)
-				if err.Error() != c.Error {
+				if !strings.HasSuffix(err.Error(), c.Error) {
 					t.Errorf("got error %q\nexpected %q", err.Error(), c.Error)
 				}
 			}
diff --git a/expr/testdata/endpoint_dsls.go b/expr/testdata/endpoint_dsls.go
index 518a90b9..ab67f26a 100644
--- a/expr/testdata/endpoint_dsls.go
+++ b/expr/testdata/endpoint_dsls.go
@@ -22,7 +22,7 @@ var ValidRouteDSL = func() {
 }
 
 var DuplicateWCRouteDSL = func() {
-	Service("InvalidRoute", func() {
+	Service("DuplicateWCRoute", func() {
 		HTTP(func() {
 			Path("/{id}")
 		})
@@ -52,6 +52,14 @@ var DisallowResponseBodyHeadDSL = func() {
 	})
 }
 
+var InvalidRouteDSL = func() {
+	Service("InvalidRoute", func() {
+		HTTP(func() {
+			POST("/{id}")
+		})
+	})
+}
+
 var EndpointWithParentDSL = func() {
 	Service("Parent", func() {
 		Method("show", func() {