Skip to content

Commit 263b27c

Browse files
committed
feat: add support for the Opaque API
A while back a new Go protobuf opaque API was [introduced](https://go.dev/blog/protobuf-opaque). Code bases wanting to switch to the opaque API cannot use this plugin at the moment because it assumes the generated types (which it uses in the tests) are generated with the open struct API (the default in older versions of protocol buffers). In this commit we introduce support for generating code which instead uses getters and setters so it works with code generated with the Opaque API (which makes all struct fields private). This is done via an option with the default being the open struct API to avoid breaking changes.
1 parent b9340b6 commit 263b27c

148 files changed

Lines changed: 32666 additions & 402 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.sage/proto.go

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@ const (
2121

2222
func (Proto) All(ctx context.Context) error {
2323
sg.SerialDeps(ctx, Proto.BufFormat, Proto.BufLint, Proto.APILinterLint)
24-
sg.SerialDeps(ctx, Proto.BufGenerate, Proto.BufGenerateGoogleapis)
24+
sg.SerialDeps(
25+
ctx,
26+
Proto.BufGenerateOpenStruct,
27+
Proto.BufGenerateOpaque,
28+
Proto.BufGenerateGoogleapisOpenStruct,
29+
Proto.BufGenerateGoogleapisOpaque,
30+
)
2531
return nil
2632
}
2733

@@ -61,22 +67,49 @@ func (Proto) ProtocGenGoAIPTest(ctx context.Context) error {
6167
return sg.Command(ctx, "go", "build", "-o", sg.FromBinDir("protoc-gen-go-aip-test"), ".").Run()
6268
}
6369

64-
func (Proto) BufGenerate(ctx context.Context) error {
70+
func (Proto) BufGenerateOpenStruct(ctx context.Context) error {
6571
sg.Deps(ctx, Proto.ProtocGenGo, Proto.ProtocGenGoGRPC, Proto.ProtocGenGoAIPTest)
6672
sg.Logger(ctx).Println("generating proto stubs...")
67-
cmd := sgbuf.Command(ctx, "generate", "--template", "buf.gen.yaml", "--path", "einride")
73+
cmd := sgbuf.Command(ctx, "generate", "--template", "buf.openstruct.gen.yaml", "--path", "einride")
74+
cmd.Dir = sg.FromGitRoot("proto")
75+
return cmd.Run()
76+
}
77+
78+
func (Proto) BufGenerateOpaque(ctx context.Context) error {
79+
sg.Deps(ctx, Proto.ProtocGenGo, Proto.ProtocGenGoGRPC, Proto.ProtocGenGoAIPTest)
80+
sg.Logger(ctx).Println("generating proto stubs...")
81+
cmd := sgbuf.Command(ctx, "generate", "--template", "buf.opaque.gen.yaml", "--path", "einride")
82+
cmd.Dir = sg.FromGitRoot("proto")
83+
return cmd.Run()
84+
}
85+
86+
func (Proto) BufGenerateGoogleapisOpenStruct(ctx context.Context) error {
87+
sg.Deps(ctx, Proto.ProtocGenGo, Proto.ProtocGenGoGRPC, Proto.ProtocGenGoAIPTest)
88+
sg.Logger(ctx).Println("generating example proto stubs...")
89+
cmd := sgbuf.Command(
90+
ctx,
91+
"generate",
92+
"https://github.com/googleapis/googleapis.git#depth=1000,ref="+googleapisRef,
93+
"--template", "buf.openstruct.gen.googleapis.yaml",
94+
"--path", "google/area120/tables/v1alpha1",
95+
"--path", "google/cloud/aiplatform/v1",
96+
"--path", "google/cloud/gsuiteaddons/v1",
97+
"--path", "google/cloud/scheduler/v1",
98+
"--path", "google/pubsub/v1",
99+
"--path", "google/spanner",
100+
)
68101
cmd.Dir = sg.FromGitRoot("proto")
69102
return cmd.Run()
70103
}
71104

72-
func (Proto) BufGenerateGoogleapis(ctx context.Context) error {
105+
func (Proto) BufGenerateGoogleapisOpaque(ctx context.Context) error {
73106
sg.Deps(ctx, Proto.ProtocGenGo, Proto.ProtocGenGoGRPC, Proto.ProtocGenGoAIPTest)
74107
sg.Logger(ctx).Println("generating example proto stubs...")
75108
cmd := sgbuf.Command(
76109
ctx,
77110
"generate",
78111
"https://github.com/googleapis/googleapis.git#depth=1000,ref="+googleapisRef,
79-
"--template", "buf.gen.googleapis.yaml",
112+
"--template", "buf.opaque.gen.googleapis.yaml",
80113
"--path", "google/area120/tables/v1alpha1",
81114
"--path", "google/cloud/aiplatform/v1",
82115
"--path", "google/cloud/gsuiteaddons/v1",

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,26 @@ protoc
5454
[.proto files ...]
5555
```
5656

57+
#### Opaque API Support
58+
59+
The plugin supports both the traditional Open Struct API and the newer
60+
[Opaque API](https://go.dev/blog/protobuf-opaque). By default, the plugin
61+
generates code that expects your proto resources to be generated using the Open
62+
Struct API but if you are generating using the Opaque API, it is possible to
63+
configure this in the plugin using the `api_mode=API_OPAQUE` option:
64+
65+
```bash
66+
protoc
67+
--go-aip-test_out=[OUTPUT DIR] \
68+
--go-aip-test_opt=module=[OUTPUT MODULE],api_mode=API_OPAQUE \
69+
[.proto files ...]
70+
```
71+
72+
Available values for the `api_mode` option:
73+
74+
- `api_mode=API_OPEN` (default) - Generate code for the Open Struct API
75+
- `api_mode=API_OPAQUE` - Generate code for the Opaque API
76+
5777
This can also be done via a
5878
[buf generate](https://docs.buf.build/generate/usage) template. See
5979
[buf.gen.yaml](./proto/buf.gen.yaml) for an example.
@@ -102,6 +122,8 @@ func Test_FreightService(t *testing.T) {
102122
}
103123
```
104124

125+
See [example/opaque](./example/opaque) for an opaque example.
126+
105127
#### Alternative 2:
106128

107129
Implement the generated configure provider interface
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package example
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"sync/atomic"
7+
"testing"
8+
9+
examplefreightv1 "github.com/einride/protoc-gen-go-aip-test/proto/gen/opaque/einride/example/freight/v1"
10+
)
11+
12+
func Test_FreightService(t *testing.T) {
13+
t.Parallel()
14+
t.Skip("this is just an example, the service is not implemented.")
15+
// setup server before test
16+
server := examplefreightv1.UnimplementedFreightServiceServer{}
17+
// setup test suite
18+
suite := examplefreightv1.FreightServiceTestSuite{
19+
T: t,
20+
Server: server,
21+
}
22+
23+
// counter to keep track of unique IDs.
24+
var idCounter uint64
25+
26+
// run tests for each resource in the service
27+
ctx := context.Background()
28+
suite.TestShipper(ctx, examplefreightv1.FreightServiceShipperTestSuiteConfig{
29+
// Create should return a resource which is valid to create, i.e.
30+
// all required fields set.
31+
Create: func() *examplefreightv1.Shipper {
32+
var shipper examplefreightv1.Shipper
33+
shipper.SetDisplayName("Example shipper")
34+
shipper.SetBillingAccount("billingAccounts/12345")
35+
return &shipper
36+
},
37+
// IDGenerator should return a valid and unique ID to use in the Create call.
38+
// If non-nil, this function will be called to set the ID on all Create calls.
39+
// If the ID field is required, tests will fail if this is nil.
40+
IDGenerator: func() string {
41+
id := atomic.AddUint64(&idCounter, 1)
42+
return fmt.Sprintf("valid-id-%d", id)
43+
},
44+
// Update should return a resource which is valid to update, i.e.
45+
// all required fields set.
46+
Update: func() *examplefreightv1.Shipper {
47+
var shipper examplefreightv1.Shipper
48+
shipper.SetDisplayName("Updated example shipper")
49+
shipper.SetBillingAccount("billingAccounts/54321")
50+
return &shipper
51+
},
52+
})
53+
}
54+
55+
func Test_FreightService_AlternativeSetup(t *testing.T) {
56+
// Even though no implementation exists, the tests will pass but be skipped.
57+
examplefreightv1.TestServices(t, &aipTests{})
58+
}
59+
60+
type aipTests struct{}
61+
62+
var _ examplefreightv1.FreightServiceTestSuiteConfigProvider = &aipTests{}
63+
64+
func (a aipTests) FreightServiceShipper(_ *testing.T) *examplefreightv1.FreightServiceShipperTestSuiteConfig {
65+
// Returns nil to indicate that it's not ready to be tested.
66+
return nil
67+
}
68+
69+
func (a aipTests) FreightServiceSite(_ *testing.T) *examplefreightv1.FreightServiceSiteTestSuiteConfig {
70+
// Returns nil to indicate that it's not ready to be tested.
71+
return nil
72+
}

example/freight_service_test.go renamed to example/openstruct/freight_service_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"sync/atomic"
77
"testing"
88

9-
examplefreightv1 "github.com/einride/protoc-gen-go-aip-test/proto/gen/einride/example/freight/v1"
9+
examplefreightv1 "github.com/einride/protoc-gen-go-aip-test/proto/gen/openstruct/einride/example/freight/v1"
1010
)
1111

1212
func Test_FreightService(t *testing.T) {

internal/aiptest/batchget/all_exists.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,32 @@ var allExists = suite.Test{
2121
onlyif.HasMethod(aipreflect.MethodTypeBatchGet),
2222
onlyif.BatchMethodNotAlternative(aipreflect.MethodTypeBatchGet),
2323
),
24-
Generate: func(f *protogen.GeneratedFile, scope suite.Scope) error {
24+
Generate: func(f *protogen.GeneratedFile, scope suite.Scope, apiMode util.APIMode) error {
2525
batchGetMethod, _ := util.StandardMethod(scope.Service, scope.Resource, aipreflect.MethodTypeBatchGet)
2626
responseResources := strcase.UpperCamelCase(string(util.FindResourceField(
2727
batchGetMethod.Output.Desc,
2828
scope.Resource,
2929
).Name()))
30+
names := []string{"created00", "created01", "created02"}
31+
getters := make([]string, 0, len(names))
32+
for _, name := range names {
33+
getters = append(getters, util.FieldGet(name, "Name", apiMode))
34+
}
3035
util.MethodBatchGet{
3136
Resource: scope.Resource,
3237
Method: batchGetMethod,
3338
Parent: "parent",
34-
Names: []string{"created00.Name", "created01.Name", "created02.Name"},
35-
}.Generate(f, "response", "err", ":=")
39+
Names: getters,
40+
}.Generate(f, "req", "response", "err", ":=", apiMode)
3641
f.P(ident.AssertNilError, "(t, err)")
3742
f.P(ident.AssertDeepEqual, "(")
3843
f.P("t,")
3944
f.P("[]*", scope.Message.GoIdent, "{")
40-
f.P("created00,")
41-
f.P("created01,")
42-
f.P("created02,")
45+
for _, name := range names {
46+
f.P(name + ",")
47+
}
4348
f.P("},")
44-
f.P("response.", responseResources, ",")
49+
f.P(util.FieldGet("response", responseResources, apiMode), ",")
4550
f.P(ident.ProtocmpTransform, "(),")
4651
f.P(")")
4752
return nil

internal/aiptest/batchget/atomic.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,19 +22,19 @@ var atomic = suite.Test{
2222
onlyif.HasMethod(aipreflect.MethodTypeBatchGet),
2323
onlyif.BatchMethodNotAlternative(aipreflect.MethodTypeBatchGet),
2424
),
25-
Generate: func(f *protogen.GeneratedFile, scope suite.Scope) error {
25+
Generate: func(f *protogen.GeneratedFile, scope suite.Scope, apiMode util.APIMode) error {
2626
batchGetMethod, _ := util.StandardMethod(scope.Service, scope.Resource, aipreflect.MethodTypeBatchGet)
2727
util.MethodBatchGet{
2828
Resource: scope.Resource,
2929
Method: batchGetMethod,
3030
Parent: "parent",
3131
Names: []string{
32-
"created00.Name",
32+
util.FieldGet("created00", "Name", apiMode),
3333
// appending to the resource name ensures it is valid
34-
"created01.Name + \"notfound\"",
35-
"created02.Name",
34+
util.FieldGet("created01", "Name", apiMode) + " + \"notfound\"",
35+
util.FieldGet("created02", "Name", apiMode),
3636
},
37-
}.Generate(f, "_", "err", ":=")
37+
}.Generate(f, "req", "_", "err", ":=", apiMode)
3838
f.P(ident.AssertEqual, "(t, ", ident.Codes(codes.NotFound), ", ", ident.StatusCode, "(err), err)")
3939
return nil
4040
},

internal/aiptest/batchget/duplicate_names.go

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,27 +22,33 @@ var duplicateNames = suite.Test{
2222
onlyif.HasMethod(aipreflect.MethodTypeBatchGet),
2323
onlyif.BatchMethodNotAlternative(aipreflect.MethodTypeBatchGet),
2424
),
25-
Generate: func(f *protogen.GeneratedFile, scope suite.Scope) error {
25+
Generate: func(f *protogen.GeneratedFile, scope suite.Scope, apiMode util.APIMode) error {
2626
batchGetMethod, _ := util.StandardMethod(scope.Service, scope.Resource, aipreflect.MethodTypeBatchGet)
2727
responseResources := strcase.UpperCamelCase(string(util.FindResourceField(
2828
batchGetMethod.Output.Desc,
2929
scope.Resource,
3030
).Name()))
3131

32+
names := []string{"created00", "created00"}
33+
getters := make([]string, 0, len(names))
34+
for _, name := range names {
35+
getters = append(getters, util.FieldGet(name, "Name", apiMode))
36+
}
3237
util.MethodBatchGet{
3338
Resource: scope.Resource,
3439
Method: batchGetMethod,
3540
Parent: "parent",
36-
Names: []string{"created00.Name", "created00.Name"},
37-
}.Generate(f, "response", "err", ":=")
41+
Names: getters,
42+
}.Generate(f, "req", "response", "err", ":=", apiMode)
3843
f.P(ident.AssertNilError, "(t, err)")
3944
f.P(ident.AssertDeepEqual, "(")
4045
f.P("t,")
4146
f.P("[]*", scope.Message.GoIdent, "{")
42-
f.P("created00,")
43-
f.P("created00,")
47+
for _, name := range names {
48+
f.P(name + ",")
49+
}
4450
f.P("},")
45-
f.P("response.", responseResources, ",")
51+
f.P(util.FieldGet("response", responseResources, apiMode), ",")
4652
f.P(ident.ProtocmpTransform, "(),")
4753
f.P(")")
4854
return nil

internal/aiptest/batchget/names_invalid.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ var namesInvalid = suite.Test{
2323
onlyif.HasMethod(aipreflect.MethodTypeBatchGet),
2424
onlyif.BatchMethodNotAlternative(aipreflect.MethodTypeBatchGet),
2525
),
26-
Generate: func(f *protogen.GeneratedFile, scope suite.Scope) error {
26+
Generate: func(f *protogen.GeneratedFile, scope suite.Scope, apiMode util.APIMode) error {
2727
batchGetMethod, _ := util.StandardMethod(scope.Service, scope.Resource, aipreflect.MethodTypeBatchGet)
2828
if util.HasParent(scope.Resource) {
2929
f.P("parent := ", ident.FixtureNextParent, "(t, false)")
@@ -33,7 +33,7 @@ var namesInvalid = suite.Test{
3333
Method: batchGetMethod,
3434
Parent: "parent",
3535
Names: []string{strconv.Quote("invalid resource name")},
36-
}.Generate(f, "_", "err", ":=")
36+
}.Generate(f, "req", "_", "err", ":=", apiMode)
3737
f.P(ident.AssertEqual, "(t, ", ident.Codes(codes.InvalidArgument), ", ", ident.StatusCode, "(err), err)")
3838
return nil
3939
},

internal/aiptest/batchget/names_missing.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ var namesMissing = suite.Test{
2121
onlyif.HasMethod(aipreflect.MethodTypeBatchGet),
2222
onlyif.BatchMethodNotAlternative(aipreflect.MethodTypeBatchGet),
2323
),
24-
Generate: func(f *protogen.GeneratedFile, scope suite.Scope) error {
24+
Generate: func(f *protogen.GeneratedFile, scope suite.Scope, apiMode util.APIMode) error {
2525
batchGetMethod, _ := util.StandardMethod(scope.Service, scope.Resource, aipreflect.MethodTypeBatchGet)
2626
if util.HasParent(scope.Resource) {
2727
f.P("parent := ", ident.FixtureNextParent, "(t, false)")
@@ -31,7 +31,7 @@ var namesMissing = suite.Test{
3131
Method: batchGetMethod,
3232
Parent: "parent",
3333
Names: nil,
34-
}.Generate(f, "_", "err", ":=")
34+
}.Generate(f, "req", "_", "err", ":=", apiMode)
3535
f.P(ident.AssertEqual, "(t, ", ident.Codes(codes.InvalidArgument), ", ", ident.StatusCode, "(err), err)")
3636
return nil
3737
},

internal/aiptest/batchget/ordered.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ var ordered = suite.Test{
2121
onlyif.HasMethod(aipreflect.MethodTypeBatchGet),
2222
onlyif.BatchMethodNotAlternative(aipreflect.MethodTypeBatchGet),
2323
),
24-
Generate: func(f *protogen.GeneratedFile, scope suite.Scope) error {
24+
Generate: func(f *protogen.GeneratedFile, scope suite.Scope, apiMode util.APIMode) error {
2525
batchGetMethod, _ := util.StandardMethod(scope.Service, scope.Resource, aipreflect.MethodTypeBatchGet)
2626
responseResources := strcase.UpperCamelCase(string(util.FindResourceField(
2727
batchGetMethod.Output.Desc,
@@ -37,9 +37,16 @@ var ordered = suite.Test{
3737
Method: batchGetMethod,
3838
Parent: "parent",
3939
Names: []string{"order[0].GetName()", "order[1].GetName()", "order[2].GetName()"},
40-
}.Generate(f, "response", "err", ":=")
40+
}.Generate(f, "req", "response", "err", ":=", apiMode)
4141
f.P(ident.AssertNilError, "(t, err)")
42-
f.P(ident.AssertDeepEqual, "(t, order, response.", responseResources, ",", ident.ProtocmpTransform, "())")
42+
f.P(
43+
ident.AssertDeepEqual,
44+
"(t, order, ",
45+
util.FieldGet("response", responseResources, apiMode),
46+
",",
47+
ident.ProtocmpTransform,
48+
"())",
49+
)
4350
f.P("}")
4451
return nil
4552
},

0 commit comments

Comments
 (0)