Repository: goadesign/goa
Pull request #3607 (2024-10-28 15:59:28)

Fix conversion methods to use correct external type
#3605 

It seems that `Goify()` is also applied to external types and their names change.
I implemented using `struct:type:name`, but there may be a more better way.

raphael: This approach looks good to me, it's consistent with how we've used the `struct:type:name` to coerce the generated type in other places. Also it's low risk since there should be no other generated code that manipulates the external type. Thank you for the fix!

Patch:
diff --git a/codegen/service/convert.go b/codegen/service/convert.go
index a54b2b68..7d6c801a 100644
--- a/codegen/service/convert.go
+++ b/codegen/service/convert.go
@@ -213,8 +213,7 @@ func ConvertFile(root *expr.RootExpr, service *expr.ServiceExpr) (*codegen.File,
 	}
 
 	// Build header section
-	pkgs = append(pkgs, &codegen.ImportSpec{Path: "context"})
-	pkgs = append(pkgs, codegen.GoaImport(""))
+	pkgs = append(pkgs, &codegen.ImportSpec{Path: "context"}, codegen.GoaImport(""))
 	path := filepath.Join(codegen.Gendir, codegen.SnakeCase(service.Name), "convert.go")
 	sections := []*codegen.SectionTemplate{
 		codegen.Header(service.Name+" service type conversion functions", svc.PkgName, pkgs),
@@ -238,8 +237,10 @@ func ConvertFile(root *expr.RootExpr, service *expr.ServiceExpr) (*codegen.File,
 		srcCtx := typeContext("", svc.Scope)
 		tgtCtx := codegen.NewAttributeContext(false, false, false, tgtPkg, codegen.NewNameScope())
 		srcAtt := &expr.AttributeExpr{Type: c.User}
+		tgtAtt := &expr.AttributeExpr{Type: dt}
+		tgtAtt.AddMeta("struct:type:name", dt.Name()) // Used by transformer to generate the correct type name.
 		code, tf, err := codegen.GoTransform(
-			&expr.AttributeExpr{Type: c.User}, &expr.AttributeExpr{Type: dt},
+			srcAtt, tgtAtt,
 			"t", "v", srcCtx, tgtCtx, "transform", true)
 		if err != nil {
 			return nil, err
@@ -696,24 +697,22 @@ func compatible(from expr.DataType, to reflect.Type, recs ...compRec) error {
 				ok    bool
 				field reflect.StructField
 			)
-			{
-				if ef, k := nat.Attribute.Meta["struct:field:external"]; k {
-					fname = ef[0]
-					if fname == "-" {
-						continue
-					}
-					field, ok = to.FieldByName(ef[0])
-				} else if ef, k := nat.Attribute.Meta["struct.field.external"]; k { // Deprecated syntax. Only present for backward compatibility.
-					fname = ef[0]
-					if fname == "-" {
-						continue
-					}
-					field, ok = to.FieldByName(ef[0])
-				} else {
-					ef := codegen.Goify(ma.ElemName(nat.Name), true)
-					fname = ef
-					field, ok = to.FieldByName(ef)
+			if ef, k := nat.Attribute.Meta["struct:field:external"]; k {
+				fname = ef[0]
+				if fname == "-" {
+					continue
 				}
+				field, ok = to.FieldByName(ef[0])
+			} else if ef, k := nat.Attribute.Meta["struct.field.external"]; k { // Deprecated syntax. Only present for backward compatibility.
+				fname = ef[0]
+				if fname == "-" {
+					continue
+				}
+				field, ok = to.FieldByName(ef[0])
+			} else {
+				ef := codegen.Goify(ma.ElemName(nat.Name), true)
+				fname = ef
+				field, ok = to.FieldByName(ef)
 			}
 			if !ok {
 				return fmt.Errorf("types don't match: could not find field %q of external type %q matching attribute %q of type %q",
diff --git a/codegen/transformer.go b/codegen/transformer.go
index f4e0ba87..f57c5810 100644
--- a/codegen/transformer.go
+++ b/codegen/transformer.go
@@ -242,6 +242,14 @@ func (a *AttributeScope) Name(att *expr.AttributeExpr, pkg string, ptr, useDefau
 		// GoTransform algorithm does not allow for an override.
 		return a.scope.GoTypeDef(att, ptr, useDefault)
 	}
+	if n, ok := att.Meta["struct:type:name"]; ok {
+		// If the attribute has a "struct:type:name" meta then use it as the
+		// type name.
+		if pkg == "" {
+			return n[0]
+		}
+		return pkg + "." + n[0]
+	}
 	return a.scope.GoFullTypeName(att, pkg)
 }
 


Test patch:
diff --git a/codegen/service/convert_test.go b/codegen/service/convert_test.go
index 9edfdbc3..52a0e1d2 100644
--- a/codegen/service/convert_test.go
+++ b/codegen/service/convert_test.go
@@ -210,10 +210,12 @@ func TestConvertFile(t *testing.T) {
 		{"convert-external-name-required", testdata.ConvertExternalNameRequiredDSL, 1, testdata.ConvertExternalNameRequiredCode},
 		{"convert-external-name-pointer", testdata.ConvertExternalNamePointerDSL, 1, testdata.ConvertExternalNamePointerCode},
 		{"convert-external-name-pointer-required", testdata.ConvertExternalNamePointerRequiredDSL, 1, testdata.ConvertExternalNamePointerRequiredCode},
+		{"convert-external-name-with-initialism", testdata.ConvertExternalNameWithInitialismDSL, 1, testdata.ConvertExternalNameWithInitialismCode},
 		{"create-external-name", testdata.CreateExternalNameDSL, 1, testdata.CreateExternalNameCode},
 		{"create-external-name-required", testdata.CreateExternalNameRequiredDSL, 1, testdata.CreateExternalNameRequiredCode},
 		{"create-external-name-pointer", testdata.CreateExternalNamePointerDSL, 1, testdata.CreateExternalNamePointerCode},
 		{"create-external-name-pointer-required", testdata.CreateExternalNamePointerRequiredDSL, 1, testdata.CreateExternalNamePointerRequiredCode},
+		{"create-external-name-with-initialism", testdata.CreateExternalNameWithInitialismDSL, 1, testdata.CreateExternalNameWithInitialismCode},
 
 		{"convert-array-string", testdata.ConvertArrayStringDSL, 1, testdata.ConvertArrayStringCode},
 		{"convert-array-string-required", testdata.ConvertArrayStringRequiredDSL, 1, testdata.ConvertArrayStringRequiredCode},
diff --git a/codegen/service/testdata/convert_dsls.go b/codegen/service/testdata/convert_dsls.go
index d0ef88b9..93ad2d18 100644
--- a/codegen/service/testdata/convert_dsls.go
+++ b/codegen/service/testdata/convert_dsls.go
@@ -106,6 +106,18 @@ var ConvertExternalNamePointerRequiredDSL = func() {
 	})
 }
 
+var ConvertExternalNameWithInitialismDSL = func() {
+	var ExternalNameWithInitialismType = Type("ExternalNameWithInitialismType", func() {
+		ConvertTo(ApiNameT{})
+		Attribute("string", String)
+	})
+	Service("Service", func() {
+		Method("Method", func() {
+			Payload(ExternalNameWithInitialismType)
+		})
+	})
+}
+
 var ConvertArrayStringDSL = func() {
 	var ArrayStringType = Type("ArrayStringType", func() {
 		ConvertTo(ArrayStringT{})
diff --git a/codegen/service/testdata/convert_functions.go b/codegen/service/testdata/convert_functions.go
index f427d0ab..fcf0f30d 100644
--- a/codegen/service/testdata/convert_functions.go
+++ b/codegen/service/testdata/convert_functions.go
@@ -80,6 +80,16 @@ func (t *ExternalNamePointerType) ConvertToExternalNamePointerT() *testdata.Exte
 }
 `
 
+var ConvertExternalNameWithInitialismCode = `// ConvertToApiNameT creates an instance of ApiNameT initialized from t.
+func (t *ExternalNameWithInitialismType) ConvertToApiNameT() *testdata.ApiNameT {
+	v := &testdata.ApiNameT{}
+	if t.String != nil {
+		v.String = *t.String
+	}
+	return v
+}
+`
+
 var ConvertArrayStringCode = `// ConvertToArrayStringT creates an instance of ArrayStringT initialized from t.
 func (t *ArrayStringType) ConvertToArrayStringT() *testdata.ArrayStringT {
 	v := &testdata.ArrayStringT{}
diff --git a/codegen/service/testdata/create_dsls.go b/codegen/service/testdata/create_dsls.go
index 4fb2a428..1bdb72d8 100644
--- a/codegen/service/testdata/create_dsls.go
+++ b/codegen/service/testdata/create_dsls.go
@@ -106,6 +106,18 @@ var CreateExternalNamePointerRequiredDSL = func() {
 	})
 }
 
+var CreateExternalNameWithInitialismDSL = func() {
+	var ExternalNameWithInitialismType = Type("ExternalNameWithInitialismType", func() {
+		CreateFrom(ApiNameT{})
+		Attribute("string", String)
+	})
+	Service("Service", func() {
+		Method("Method", func() {
+			Payload(ExternalNameWithInitialismType)
+		})
+	})
+}
+
 var CreateArrayStringDSL = func() {
 	var ArrayStringType = Type("ArrayStringType", func() {
 		CreateFrom(ArrayStringT{})
diff --git a/codegen/service/testdata/create_functions.go b/codegen/service/testdata/create_functions.go
index 5e5d2f72..396545c2 100644
--- a/codegen/service/testdata/create_functions.go
+++ b/codegen/service/testdata/create_functions.go
@@ -74,6 +74,15 @@ func (t *ExternalNamePointerType) CreateFromExternalNamePointerT(v *testdata.Ext
 }
 `
 
+var CreateExternalNameWithInitialismCode = `// CreateFromApiNameT initializes t from the fields of v
+func (t *ExternalNameWithInitialismType) CreateFromApiNameT(v *testdata.ApiNameT) {
+	temp := &ExternalNameWithInitialismType{
+		String: &v.String,
+	}
+	*t = *temp
+}
+`
+
 var CreateArrayStringCode = `// CreateFromArrayStringT initializes t from the fields of v
 func (t *ArrayStringType) CreateFromArrayStringT(v *testdata.ArrayStringT) {
 	temp := &ArrayStringType{}
diff --git a/codegen/service/testdata/types.go b/codegen/service/testdata/types.go
index db01f21d..be180230 100644
--- a/codegen/service/testdata/types.go
+++ b/codegen/service/testdata/types.go
@@ -18,6 +18,10 @@ type ExternalNamePointerT struct {
 	String *string
 }
 
+type ApiNameT struct {
+	String string
+}
+
 type ArrayStringT struct {
 	ArrayString []string
 }