Description
If I import enums from one or more proto files, each in a go_pacakge ending in "v1" to another proto file whose go_pacakge name also ends in "v1", there are multiple compile errors in the generated pb.validate.go file. There was a prior fix for this, #387, that was never merged to main, in fact it was recently marked as closed. I've built a version of the plugin with that fix, and it fixes most, but not all, of the compiler errors. So I believe that plus an additional fix is needed. There are other related fixes that are recently merged into the latest release, but the plugin still generates invalid Go code in this scenario.
I have included a zip file, valtest.zip with a simple Go project demonstrating the issues. To run it, simply invoke sh ./run.sh
or something similar:
valtest.zip
Note here I am using google.golang.org/protobuf/cmd/protoc-gen-go, whereas our project still uses the deprecated github.com/golang/protobuf tools, but the results are the same. The paths=source_relative:. option for protoc-gen-validate seems to not honor the relative paths as specified in the new plugin, and creates a whole hierarchy rooted at github.com in the current project, perhaps the validator code generator should be able to handle both versions of protoc-gen-go. This could be user error or maybe another bug.
Input:
source 1:
option go_package = "github.com/valtest/from/v1";
package fv1;
enum ReportStatus { ...
source 2:
option go_package = "github.com/valtest/from/yetanother/v1";
package yav1;
enum OtherReportStatus { ...
destination:
option go_package = "github.com/valtest/to/v1";
package tv1;
...
import "from/v1/types.proto";
import "from/yetanother/v1/types.proto";
import "validate/validate.proto";
...
message UserInfo {
fv1.ReportStatus s1 = 1 [(validate.rules).enum.defined_only = true];
yav1.OtherReportStatus s2 = 2 [(validate.rules).enum.defined_only = true];
}
Expected Output in user.pb.validate.go:
import (
...
v1 "github.com/valtest/from/v1"
v11 "github.com/valtest/from/yetanother/v1"
...
)
...
_ = v1.ReportStatus(0)
_ = v11.OtherReportStatus(0)
...
if _, ok := v1.ReportStatus_name[int32(m.GetS1())]; !ok {
...
if _, ok := v11.OtherReportStatus_name[int32(m.GetS3())]; !ok {
...
Observed Output (with current released version):
// missing imports and check that imports are used for github.com/valtest/from/v1 and "github.com/valtest/from/yetanother/v1
// error, v1 undefined
if _, ok := v1.ReportStatus_name[int32(m.GetS1())]; !ok {
// error, v1 undefined plus should be a unique prefix like v11
if _, ok := v1.OtherReportStatus_name[int32(m.GetS3())]; !ok {
compiler error:
to/v1/user.pb.validate.go:60:14: undefined: v1
to/v1/user.pb.validate.go:71:14: undefined: v1
Observed Output Applying change from Issue 387 - correct except last 'if' has wrong prefix, 'v1' instead of 'v11' :
import (
...
v1 "github.com/valtest/from/v1"
v11 "github.com/valtest/from/yetanother/v1"
...
)
...
_ = v1.ReportStatus(0)
_ = v11.OtherReportStatus(0)
...
if _, ok := v1.ReportStatus_name[int32(m.GetS1())]; !ok {
...
if _, ok := v1.OtherReportStatus_name[int32(m.GetS3())]; !ok {
...
compiler error:
to/v1/user.pb.validate.go:79:17: undefined: v1.OtherReportStatus_name
Analysis (with change from Issue 387 applied):
Most of the action happens in templates/goshared/register.go. When there are trailing package name conflicts, a set of unique pacakge aliases is generated in func (fns goSharedFuncs) enumPackages
, which is then applied to the template header in templates/go/file.go to generate the includes and code to ensure everything is used. Later, the template function named "typ" is applied to generate the prefixes in actual enum validation code in templates/goshared/enum.go. However, the function bound to "typ" is fns.Type, which is defined in the protoc-gen-star pacakge, and has no knowledge of the package aliases defined above. In fact, I put in some debugging and found:
***in Type() got enum type: 's1', fully qualified name: '.fv1.ReportStatus', context type: 'v1.ReportStatus'
***in Type() got enum type: 's2', fully qualified name: '.yav1.OtherReportStatus', context type: 'v1.OtherReportStatus'
So perhaps we need an override of Type() and possibly we need a way to persist the alias map created for the header part of the file.