Repository: goadesign/goa
Pull request #2601 (2020-07-02 18:55:07)

Add ToExpressionSet helper function.
The function takes an arbitrary slice of expressions and returns an ExpressionSet.
This is especially convenient for plugins that do not have to loop through all
the slice to cast the expression objects to the Expression interface. Yes it does
make use of reflect and hides the performance impact but given the use case (tool
vs. long running process) this seems like a good trade-off.


Patch:
diff --git a/eval/expression.go b/eval/expression.go
index ddf6796c..450e8f8d 100644
--- a/eval/expression.go
+++ b/eval/expression.go
@@ -1,5 +1,7 @@
 package eval
 
+import "reflect"
+
 type (
 	// Expression built by the engine through the DSL functions.
 	Expression interface {
@@ -95,3 +97,21 @@ func (f DSLFunc) DSL() func() {
 
 // EvalName is the name is the qualified name of the expression.
 func (t TopExpr) EvalName() string { return string(t) }
+
+// ToExpressionSet is a convenience function that accepts a slice of expressions
+// and builds the corresponding ExpressionSet.
+func ToExpressionSet(slice interface{}) ExpressionSet {
+	if slice == nil {
+		return nil
+	}
+	s := reflect.ValueOf(slice)
+	if s.Kind() != reflect.Slice {
+		panic("ToExpressionSet() given a non-slice type") // bug
+	}
+	ret := make(ExpressionSet, s.Len())
+	for i := 0; i < s.Len(); i++ {
+		ret[i] = s.Index(i).Interface().(Expression)
+	}
+
+	return ret
+}
diff --git a/expr/root.go b/expr/root.go
index 8dfa24bd..92cea3ab 100644
--- a/expr/root.go
+++ b/expr/root.go
@@ -63,11 +63,7 @@ func (r *RootExpr) WalkSets(walk eval.SetWalker) {
 	walk(eval.ExpressionSet{r.API})
 
 	// Servers
-	servers := make(eval.ExpressionSet, len(r.API.Servers))
-	for i, s := range r.API.Servers {
-		servers[i] = s
-	}
-	walk(servers)
+	walk(eval.ToExpressionSet(r.API.Servers))
 
 	// User types
 	types := make(eval.ExpressionSet, len(r.Types))
@@ -84,14 +80,10 @@ func (r *RootExpr) WalkSets(walk eval.SetWalker) {
 	walk(mtypes)
 
 	// Services
-	services := make(eval.ExpressionSet, len(r.Services))
-	var methods eval.ExpressionSet
-	for i, s := range r.Services {
-		services[i] = s
-	}
-	walk(services)
+	walk(eval.ToExpressionSet(r.Services))
 
 	// Methods (must be done after services)
+	var methods eval.ExpressionSet
 	for _, s := range r.Services {
 		for _, m := range s.Methods {
 			methods = append(methods, m)


Test patch:
diff --git a/eval/expression_test.go b/eval/expression_test.go
new file mode 100644
index 00000000..2eb9dd32
--- /dev/null
+++ b/eval/expression_test.go
@@ -0,0 +1,40 @@
+package eval
+
+import (
+	"testing"
+)
+
+type Expr int
+
+func (e Expr) EvalName() string { return "test expression" }
+
+func TestToExpressionSet(t *testing.T) {
+	cases := []struct {
+		Name        string
+		Slice       []interface{}
+		ExpectPanic bool
+	}{
+		{"simple", []interface{}{Expr(42)}, false},
+		{"nil", nil, false},
+		{"invalid", []interface{}{42}, true},
+	}
+	for _, c := range cases {
+		t.Run(c.Name, func(t *testing.T) {
+			defer func() {
+				if r := recover(); r == nil && c.ExpectPanic {
+					t.Errorf("test did not panic")
+				}
+			}()
+			set := ToExpressionSet(c.Slice)
+			if len(set) != len(c.Slice) {
+				t.Errorf("got set of length %d, expected %d.", len(set), len(c.Slice))
+			} else {
+				for i, e := range set {
+					if e != c.Slice[i] {
+						t.Errorf("got value %v at index %d, expected %v", e, i, c.Slice[i])
+					}
+				}
+			}
+		})
+	}
+}