Skip to content

Commit

Permalink
Merge pull request #5 from dogmatiq/oneof-getters
Browse files Browse the repository at this point in the history
Introduce `Try*()` Accessor Method Generation for One-Of Groups
  • Loading branch information
jmalloc committed Apr 22, 2024
2 parents f86adf6 + 7e033e9 commit fb0d77c
Show file tree
Hide file tree
Showing 8 changed files with 451 additions and 0 deletions.
6 changes: 6 additions & 0 deletions internal/generator/accessor/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Package accessor generates an accessor method for each type in "one-of
// groups". While "protoc-gen-go" plugin already generates accessor methods for
// for each type in "one-of groups", the generated methods lack the ability to
// differentiate between the absence of a value and the presence of a zero
// value.
package accessor
20 changes: 20 additions & 0 deletions internal/generator/accessor/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package accessor

import (
"github.com/dave/jennifer/jen"
"github.com/dogmatiq/primo/internal/generator/internal/scope"
)

// Generate generates accessor methods for each type in one-of group. In
// contrast to already available accessor methods generated by "protoc-gen-go"
// plugin, these accessor methods return a boolean value indicating whether the
// value is present or not.
func Generate(code *jen.File, f *scope.File) error {
for _, m := range f.Messages() {
for _, g := range m.OneOfGroups() {
generateForOneOf(code, g)
}
}

return nil
}
74 changes: 74 additions & 0 deletions internal/generator/accessor/oneof.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package accessor

import (
"github.com/dave/jennifer/jen"
"github.com/dogmatiq/primo/internal/generator/internal/scope"
)

func generateForOneOf(code *jen.File, g *scope.OneOfGroup) {
for _, o := range g.Options {
oneOfAccessorTryFunc(code, o)
}
}

func oneOfAccessorTryFunc(code *jen.File, o *scope.OneOfOption) {
methodName := "Try" + o.DiscriminatorFieldName

code.
Commentf(
"%s returns the value of [%s] in one-of field x.%s.",
methodName,
o.DiscriminatorFieldName,
o.Group.GoFieldName,
)
code.Comment("")
code.Comment("ok returns false if the value of this type is not set.")

code.
Func().
Params(
jen.
Id("x").
Op("*").
Id(o.Group.Message.GoTypeName),
).
Id(methodName).
Params().
Params(
jen.
Id("v").
Add(o.Field.GoType()),
jen.
Id("ok").
Add(jen.Bool()),
).
Block(
jen.
If(
jen.List(
jen.Id("x"),
jen.Id("ok"),
).
Op(":=").
Id("x").
Dot("Get"+o.Group.GoFieldName).
Call().
Assert(
jen.Op("*").
Id(o.DiscriminatorTypeName),
).
Op(";").
Id("ok"),
).Block(
jen.Return(
jen.Id("x").
Dot(o.DiscriminatorFieldName),
jen.True(),
),
),
jen.Return(
jen.Id("v"),
jen.False(),
),
)
}
2 changes: 2 additions & 0 deletions internal/generator/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"

"github.com/dave/jennifer/jen"
"github.com/dogmatiq/primo/internal/generator/accessor"
"github.com/dogmatiq/primo/internal/generator/builder"
"github.com/dogmatiq/primo/internal/generator/exhaustiveswitch"
"github.com/dogmatiq/primo/internal/generator/internal/option"
Expand Down Expand Up @@ -38,6 +39,7 @@ func Generate(
builder.Generate,
exhaustiveswitch.Generate,
mutator.Generate,
accessor.Generate,
); err != nil {
res.Error = proto.String(err.Error())
}
Expand Down
61 changes: 61 additions & 0 deletions internal/test/accessor/helper_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package accessor_test

import (
reflect "reflect"
"testing"

"google.golang.org/protobuf/proto"
)

// testAccessor calls a mutator method to set the value and verifies that the
// corresponding accessor returns the expected value and boolean ok value.
func testAccessor[M proto.Message, T comparable](
t *testing.T,
mutator func(M, T),
accessor func(M) (T, bool),
want T,
) {
t.Helper()

testAccessorFunc(
t,
mutator,
accessor,
want,
func(a, b T) bool { return a == b },
)
}

// testAccessorFunc calls a mutator method to set the value and verifies that the
// corresponding accessor returns the expected value and boolean ok value.
func testAccessorFunc[M proto.Message, T any](
t *testing.T,
mutate func(M, T),
access func(M) (T, bool),
want T,
eq func(T, T) bool,
) {
t.Helper()

var m M
m = reflect.New(
reflect.TypeOf(m).Elem(),
).Interface().(M)

mutate(m, want)

got, gotOK := access(m)
if !gotOK {
t.Fatalf(
"accessor did not return expected ok as true",
)
}

if !eq(got, want) {
t.Fatalf(
"accessor did not return the expected value: got: %v, want: %v",
got,
want,
)
}
}
205 changes: 205 additions & 0 deletions internal/test/accessor/oneof.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions internal/test/accessor/oneof.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
syntax = "proto3";
package primo.test.mutators;

option go_package = "github.com/dogmatiq/primo/internal/test/accessor";

message OneOf {
oneof group {
int32 field_a = 1; // note: two fields of the same type
int32 field_b = 2;
string field_c = 3;
}
}
Loading

0 comments on commit fb0d77c

Please sign in to comment.