Repository: goadesign/goa
Pull request #3557 (2024-07-10 15:39:48)

Add eval.TooFewArgError()
Related to
* https://github.com/goadesign/goa/pull/3512
* https://github.com/goadesign/goa/pull/3520
* https://github.com/goadesign/goa/pull/3526

raphael: This is great, thank you!

Patch:
diff --git a/dsl/attribute.go b/dsl/attribute.go
index f34e6624..d6a7d3fa 100644
--- a/dsl/attribute.go
+++ b/dsl/attribute.go
@@ -112,29 +112,27 @@ import (
 //	})
 func Attribute(name string, args ...any) {
 	var parent *expr.AttributeExpr
-	{
-		switch def := eval.Current().(type) {
-		case *expr.AttributeExpr:
-			parent = def
-		case expr.CompositeExpr:
-			parent = def.Attribute()
-		default:
-			eval.IncompatibleDSL()
-			return
-		}
-		if parent == nil {
-			eval.ReportError("invalid syntax, attribute %#v has no parent", name)
+	switch def := eval.Current().(type) {
+	case *expr.AttributeExpr:
+		parent = def
+	case expr.CompositeExpr:
+		parent = def.Attribute()
+	default:
+		eval.IncompatibleDSL()
+		return
+	}
+	if parent == nil {
+		eval.ReportError("invalid syntax, attribute %#v has no parent", name)
+		return
+	}
+	if parent.Type == nil {
+		parent.Type = &expr.Object{}
+	}
+	if _, ok := parent.Type.(*expr.Object); !ok {
+		if _, ok := parent.Type.(*expr.Union); !ok {
+			eval.ReportError("can't define child attribute %#v on attribute of type %s %T", name, parent.Type.Name(), parent.Type)
 			return
 		}
-		if parent.Type == nil {
-			parent.Type = &expr.Object{}
-		}
-		if _, ok := parent.Type.(*expr.Object); !ok {
-			if _, ok := parent.Type.(*expr.Union); !ok {
-				eval.ReportError("can't define child attribute %#v on attribute of type %s %T", name, parent.Type.Name(), parent.Type)
-				return
-			}
-		}
 	}
 
 	var attr *expr.AttributeExpr
@@ -298,7 +296,7 @@ func Default(def any) {
 //	})
 func Example(args ...any) {
 	if len(args) == 0 {
-		eval.ReportError("not enough arguments")
+		eval.TooFewArgError()
 		return
 	}
 	if len(args) > 2 {
diff --git a/dsl/error.go b/dsl/error.go
index a7816b2d..9093f36b 100644
--- a/dsl/error.go
+++ b/dsl/error.go
@@ -173,7 +173,7 @@ func Error(name string, args ...any) {
 //	}
 func ErrorName(args ...any) {
 	if len(args) == 0 {
-		eval.IncompatibleDSL()
+		eval.TooFewArgError()
 		return
 	}
 	dsl, ok := args[len(args)-1].(func())
@@ -191,8 +191,8 @@ func ErrorName(args ...any) {
 	case string:
 		Attribute(actual, args[1:]...)
 	case int:
-		if len(args) == 1 {
-			eval.IncompatibleDSL()
+		if len(args) == 2 {
+			eval.TooFewArgError()
 			return
 		}
 		name, ok := args[1].(string)
diff --git a/dsl/http.go b/dsl/http.go
index b653bb9f..5a2ef937 100644
--- a/dsl/http.go
+++ b/dsl/http.go
@@ -895,7 +895,7 @@ func SkipResponseBodyEncodeDecode() {
 //	})
 func Body(args ...any) {
 	if len(args) == 0 {
-		eval.ReportError("not enough arguments, use Body(name), Body(type), Body(func()) or Body(type, func())")
+		eval.TooFewArgError()
 		return
 	}
 
diff --git a/dsl/response.go b/dsl/response.go
index 61527055..438ac7c4 100644
--- a/dsl/response.go
+++ b/dsl/response.go
@@ -202,7 +202,7 @@ func Code(code int) {
 
 func grpcError(n string, p eval.Expression, args ...any) *expr.GRPCErrorExpr {
 	if len(args) == 0 {
-		eval.ReportError("not enough arguments, use Response(name, status), Response(name, status, func()) or Response(name, func())")
+		eval.TooFewArgError()
 		return nil
 	}
 	var (
@@ -260,7 +260,7 @@ func parseResponseArgs(val any, args ...any) (code int, fn func()) {
 
 func httpError(n string, p eval.Expression, args ...any) *expr.HTTPErrorExpr {
 	if len(args) == 0 {
-		eval.ReportError("not enough arguments, use Response(name, status), Response(name, status, func()) or Response(name, func())")
+		eval.TooFewArgError()
 		return nil
 	}
 	var (
diff --git a/eval/eval.go b/eval/eval.go
index 81e6e03a..5a41acfb 100644
--- a/eval/eval.go
+++ b/eval/eval.go
@@ -126,6 +126,12 @@ func InvalidArgError(expected string, actual any) {
 	ReportError("cannot use %#v (type %s) as type %s", actual, reflect.TypeOf(actual), expected)
 }
 
+// TooFewArgError records a too few arguments error. It is used by DSL
+// functions that take dynamic arguments.
+func TooFewArgError() {
+	ReportError("too few arguments given to %s", caller())
+}
+
 // TooManyArgError records a too many arguments error. It is used by DSL
 // functions that take dynamic arguments.
 func TooManyArgError() {


Test patch:
diff --git a/eval/eval_test.go b/eval/eval_test.go
index 4afb4d07..2660f131 100644
--- a/eval/eval_test.go
+++ b/eval/eval_test.go
@@ -35,6 +35,24 @@ func TestInvalidArgError(t *testing.T) {
 	}
 }
 
+func TestTooFewArgError(t *testing.T) {
+	dsls := map[string]func(){
+		"Body":            func() { Body() },
+		"ErrorName":       func() { ErrorName() },
+		"ErrorName (int)": func() { Type("name", func() { ErrorName(1) }) },
+		"Example":         func() { Example() },
+		"Response (grpc)": func() { Service("s", func() { GRPC(func() { Response("name") }) }) },
+		"Response (http)": func() { Service("s", func() { HTTP(func() { Response("name") }) }) },
+	}
+	for name, dsl := range dsls {
+		t.Run(name, func(t *testing.T) {
+			err := expr.RunInvalidDSL(t, dsl)
+			assert.Len(t, strings.Split(err.Error(), "\n"), 1)
+			assert.Contains(t, err.Error(), "too few arguments given to "+strings.Split(name, " ")[0])
+		})
+	}
+}
+
 func TestTooManyArgError(t *testing.T) {
 	dsls := map[string]func(){
 		"APIKey":           func() { Type("name", func() { APIKey("scheme", "name", 1, 2, 3) }) },
diff --git a/expr/example_test.go b/expr/example_test.go
index 872e74be..09edc55b 100644
--- a/expr/example_test.go
+++ b/expr/example_test.go
@@ -63,7 +63,7 @@ func TestExample(t *testing.T) {
 		{"overriding-example", testdata.OverridingExampleDSL, map[string]any{"name": "overridden"}, ""},
 		{"with-extend", testdata.WithExtendExampleDSL, map[string]any{"name": "example"}, ""},
 		{"invalid-example-type", testdata.InvalidExampleTypeDSL, nil, "example value map[int]int{1:1} is incompatible with attribute of type map in attribute"},
-		{"empty-example", testdata.EmptyExampleDSL, nil, "not enough arguments in attribute"},
+		{"empty-example", testdata.EmptyExampleDSL, nil, "too few arguments given to Example in attribute"},
 		{"hiding-example", testdata.HidingExampleDSL, nil, ""},
 		{"overriding-hidden-examples", testdata.OverridingHiddenExamplesDSL, "example", ""},
 	}