Skip to content

Commit f7c0b84

Browse files
feat(sidekick): Generate samples for single value setters (#2263)
1 parent 9dfe7a0 commit f7c0b84

File tree

11 files changed

+686
-119
lines changed

11 files changed

+686
-119
lines changed

internal/sidekick/internal/api/model.go

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,15 @@ func (api *API) HasMessages() bool {
162162
return len(api.Messages) != 0
163163
}
164164

165+
// ModelCodec returns the Codec field with an alternative name.
166+
//
167+
// In some mustache templates we want to access the annotations for the
168+
// enclosing model. In mustache you can get a field from an enclosing context
169+
// *if* the name is unique.
170+
func (a *API) ModelCodec() any {
171+
return a.Codec
172+
}
173+
165174
// APIState contains helpful information that can be used when generating
166175
// clients.
167176
type APIState struct {
@@ -714,6 +723,12 @@ type Field struct {
714723
// For fields that are part of a OneOf, the group of fields that makes the
715724
// OneOf.
716725
Group *OneOf
726+
// The message that contains this field.
727+
Parent *Message
728+
// The message type for this field, can be nil.
729+
MessageType *Message
730+
// The enum type for this field, can be nil.
731+
EnumType *Enum
717732
// A placeholder to put language specific annotations.
718733
Codec any
719734
}
@@ -733,6 +748,74 @@ func (f *Field) NameEqualJSONName() bool {
733748
return f.JSONName == f.Name
734749
}
735750

751+
// IsString returns true if the primitive type of a field is `STRING_TYPE`.
752+
//
753+
// This is useful for mustache templates that differ only
754+
// in the broad category of field type involved.
755+
func (f *Field) IsString() bool {
756+
return f.Typez == STRING_TYPE
757+
}
758+
759+
// IsBytes returns true if the primitive type of a field is `BYTES_TYPE`.
760+
//
761+
// This is useful for mustache templates that differ only
762+
// in the broad category of field type involved.
763+
func (f *Field) IsBytes() bool {
764+
return f.Typez == BYTES_TYPE
765+
}
766+
767+
// IsBool returns true if the primitive type of a field is `BOOL_TYPE`.
768+
//
769+
// This is useful for mustache templates that differ only
770+
// in the broad category of field type involved.
771+
func (f *Field) IsBool() bool {
772+
return f.Typez == BOOL_TYPE
773+
}
774+
775+
// IsLikeInt returns true if the primitive type of a field is one of the
776+
// integer types.
777+
//
778+
// This is useful for mustache templates that differ only
779+
// in the broad category of field type involved.
780+
func (f *Field) IsLikeInt() bool {
781+
switch f.Typez {
782+
case UINT32_TYPE, UINT64_TYPE, INT32_TYPE, INT64_TYPE, SINT32_TYPE, SINT64_TYPE:
783+
return true
784+
case FIXED32_TYPE, FIXED64_TYPE, SFIXED32_TYPE, SFIXED64_TYPE:
785+
return true
786+
default:
787+
return false
788+
}
789+
}
790+
791+
// IsLikeFloat returns true if the primitive type of a field is a float or
792+
// double.
793+
//
794+
// This is useful for mustache templates that differ only
795+
// in the broad category of field type involved.
796+
func (f *Field) IsLikeFloat() bool {
797+
return f.Typez == DOUBLE_TYPE || f.Typez == FLOAT_TYPE
798+
}
799+
800+
// IsEnum returns true if the primitive type of a field is `ENUM_TYPE`.
801+
//
802+
// This is useful for mustache templates that differ only
803+
// in the broad category of field type involved.
804+
func (f *Field) IsEnum() bool {
805+
return f.Typez == ENUM_TYPE
806+
}
807+
808+
// IsObject returns true if the primitive type of a field is `OBJECT_TYPE`.
809+
//
810+
// This is useful for mustache templates that differ only
811+
// in the broad category of field type involved.
812+
//
813+
// The templates *should* first check if the field is singular, as all maps are
814+
// also objects.
815+
func (f *Field) IsObject() bool {
816+
return f.Typez == MESSAGE_TYPE
817+
}
818+
736819
// Pair is a key-value pair.
737820
type Pair struct {
738821
// Key of the pair.

internal/sidekick/internal/api/model_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,3 +231,58 @@ func TestPathTemplateBuilder(t *testing.T) {
231231
t.Errorf("bad builder result (-want, +got):\n%s", diff)
232232
}
233233
}
234+
235+
func TestFieldTypePredicates(t *testing.T) {
236+
type TestCase struct {
237+
field *Field
238+
isString bool
239+
isBytes bool
240+
isBool bool
241+
isInt bool
242+
isFloat bool
243+
isEnum bool
244+
isObject bool
245+
}
246+
testCases := []TestCase{
247+
{field: &Field{Typez: STRING_TYPE}, isString: true},
248+
{field: &Field{Typez: BYTES_TYPE}, isBytes: true},
249+
{field: &Field{Typez: BOOL_TYPE}, isBool: true},
250+
{field: &Field{Typez: INT32_TYPE}, isInt: true},
251+
{field: &Field{Typez: INT64_TYPE}, isInt: true},
252+
{field: &Field{Typez: UINT32_TYPE}, isInt: true},
253+
{field: &Field{Typez: UINT64_TYPE}, isInt: true},
254+
{field: &Field{Typez: SINT32_TYPE}, isInt: true},
255+
{field: &Field{Typez: SINT64_TYPE}, isInt: true},
256+
{field: &Field{Typez: FIXED32_TYPE}, isInt: true},
257+
{field: &Field{Typez: FIXED64_TYPE}, isInt: true},
258+
{field: &Field{Typez: SFIXED32_TYPE}, isInt: true},
259+
{field: &Field{Typez: SFIXED64_TYPE}, isInt: true},
260+
{field: &Field{Typez: FLOAT_TYPE}, isFloat: true},
261+
{field: &Field{Typez: DOUBLE_TYPE}, isFloat: true},
262+
{field: &Field{Typez: ENUM_TYPE}, isEnum: true},
263+
{field: &Field{Typez: MESSAGE_TYPE}, isObject: true},
264+
}
265+
for _, tc := range testCases {
266+
if tc.field.IsString() != tc.isString {
267+
t.Errorf("IsString() for %v should be %v", tc.field.Typez, tc.isString)
268+
}
269+
if tc.field.IsBytes() != tc.isBytes {
270+
t.Errorf("IsBytes() for %v should be %v", tc.field.Typez, tc.isBytes)
271+
}
272+
if tc.field.IsBool() != tc.isBool {
273+
t.Errorf("IsBool() for %v should be %v", tc.field.Typez, tc.isBool)
274+
}
275+
if tc.field.IsLikeInt() != tc.isInt {
276+
t.Errorf("IsLikeInt() for %v should be %v", tc.field.Typez, tc.isInt)
277+
}
278+
if tc.field.IsLikeFloat() != tc.isFloat {
279+
t.Errorf("IsLikeFloat() for %v should be %v", tc.field.Typez, tc.isFloat)
280+
}
281+
if tc.field.IsEnum() != tc.isEnum {
282+
t.Errorf("IsEnum() for %v should be %v", tc.field.Typez, tc.isEnum)
283+
}
284+
if tc.field.IsObject() != tc.isObject {
285+
t.Errorf("IsObject() for %v should be %v", tc.field.Typez, tc.isObject)
286+
}
287+
}
288+
}

internal/sidekick/internal/api/xref.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,27 @@ import "fmt"
2929
// reference any types or names of the `OneOf` during their generation.
3030
func CrossReference(model *API) error {
3131
for _, m := range model.State.MessageByID {
32+
for _, f := range m.Fields {
33+
f.Parent = m
34+
switch f.Typez {
35+
case MESSAGE_TYPE:
36+
t, ok := model.State.MessageByID[f.TypezID]
37+
if !ok {
38+
return fmt.Errorf("cannot find message type %s for field %s", f.TypezID, f.ID)
39+
}
40+
f.MessageType = t
41+
case ENUM_TYPE:
42+
t, ok := model.State.EnumByID[f.TypezID]
43+
if !ok {
44+
return fmt.Errorf("cannot find enum type %s for field %s", f.TypezID, f.ID)
45+
}
46+
f.EnumType = t
47+
}
48+
}
3249
for _, o := range m.OneOfs {
3350
for _, f := range o.Fields {
3451
f.Group = o
52+
f.Parent = m
3553
}
3654
}
3755
}

internal/sidekick/internal/api/xref_test.go

Lines changed: 92 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,55 +20,130 @@ import (
2020
)
2121

2222
func TestCrossReferenceOneOfs(t *testing.T) {
23-
var fields []*Field
23+
var fields1 []*Field
2424
for i := range 4 {
2525
name := fmt.Sprintf("field%d", i)
26-
fields = append(fields, &Field{
26+
fields1 = append(fields1, &Field{
2727
Name: name,
2828
ID: ".test.Message." + name,
2929
Typez: STRING_TYPE,
3030
IsOneOf: true,
3131
})
3232
}
33-
fields = append(fields, &Field{
33+
fields1 = append(fields1, &Field{
3434
Name: "basic_field",
3535
ID: ".test.Message.basic_field",
3636
Typez: STRING_TYPE,
3737
IsOneOf: true,
3838
})
3939
group0 := &OneOf{
4040
Name: "group0",
41-
Fields: []*Field{fields[0], fields[1]},
41+
Fields: []*Field{fields1[0], fields1[1]},
4242
}
4343
group1 := &OneOf{
4444
Name: "group1",
45-
Fields: []*Field{fields[2], fields[3]},
45+
Fields: []*Field{fields1[2], fields1[3]},
4646
}
47-
message := &Message{
48-
Name: "Message",
49-
ID: ".test.Message",
50-
Fields: fields,
47+
message1 := &Message{
48+
Name: "Message1",
49+
ID: ".test.Message1",
50+
Fields: fields1,
5151
OneOfs: []*OneOf{group0, group1},
5252
}
53-
model := NewTestAPI([]*Message{message}, []*Enum{}, []*Service{})
53+
var fields2 []*Field
54+
for i := range 2 {
55+
name := fmt.Sprintf("field%d", i+4)
56+
fields2 = append(fields2, &Field{
57+
Name: name,
58+
ID: ".test.Message." + name,
59+
Typez: STRING_TYPE,
60+
IsOneOf: true,
61+
})
62+
}
63+
group2 := &OneOf{
64+
Name: "group2",
65+
Fields: []*Field{fields2[0], fields2[1]},
66+
}
67+
message2 := &Message{
68+
Name: "Message2",
69+
ID: ".test.Message2",
70+
OneOfs: []*OneOf{group2},
71+
}
72+
model := NewTestAPI([]*Message{message1, message2}, []*Enum{}, []*Service{})
5473
if err := CrossReference(model); err != nil {
5574
t.Fatal(err)
5675
}
5776

5877
for _, test := range []struct {
59-
field *Field
60-
oneof *OneOf
78+
field *Field
79+
oneof *OneOf
80+
parent *Message
6181
}{
62-
{fields[0], group0},
63-
{fields[1], group0},
64-
{fields[2], group1},
65-
{fields[3], group1},
66-
{fields[4], nil},
82+
{fields1[0], group0, message1},
83+
{fields1[1], group0, message1},
84+
{fields1[2], group1, message1},
85+
{fields1[3], group1, message1},
86+
{fields1[4], nil, message1},
87+
{fields2[0], group2, message2},
88+
{fields2[1], group2, message2},
6789
} {
6890
if test.field.Group != test.oneof {
6991
t.Errorf("mismatched group for %s, got=%v, want=%v", test.field.Name, test.field.Group, test.oneof)
7092
}
93+
if test.field.Parent != test.parent {
94+
t.Errorf("mismatched parent for %s, got=%v, want=%v", test.field.Name, test.field.Parent, test.parent)
95+
}
96+
}
97+
}
98+
99+
func TestCrossReferenceFields(t *testing.T) {
100+
messageT := &Message{
101+
Name: "MessageT",
102+
ID: ".test.MessageT",
103+
}
104+
fieldM := &Field{
105+
Name: "message_field",
106+
ID: ".test.Message.message_field",
107+
Typez: MESSAGE_TYPE,
108+
TypezID: ".test.MessageT",
109+
}
110+
enumT := &Enum{
111+
Name: "EnumT",
112+
ID: ".test.EnumT",
113+
}
114+
fieldE := &Field{
115+
Name: "enum_field",
116+
ID: ".test.Message.enum_field",
117+
Typez: ENUM_TYPE,
118+
TypezID: ".test.EnumT",
119+
}
120+
message := &Message{
121+
Name: "Message",
122+
ID: ".test.Message",
123+
Fields: []*Field{fieldM, fieldE},
124+
}
71125

126+
model := NewTestAPI([]*Message{messageT, message}, []*Enum{enumT}, []*Service{})
127+
if err := CrossReference(model); err != nil {
128+
t.Fatal(err)
129+
}
130+
131+
for _, test := range []struct {
132+
field *Field
133+
parent *Message
134+
}{
135+
{fieldM, message},
136+
{fieldE, message},
137+
} {
138+
if test.field.Parent != test.parent {
139+
t.Errorf("mismatched parent for %s, got=%v, want=%v", test.field.Name, test.field.Parent, test.parent)
140+
}
141+
}
142+
if fieldM.MessageType != messageT {
143+
t.Errorf("mismatched message type for %s, got%v, want=%v", fieldM.Name, fieldM.MessageType, messageT)
144+
}
145+
if fieldE.EnumType != enumT {
146+
t.Errorf("mismatched enum type for %s, got%v, want=%v", fieldE.Name, fieldE.EnumType, enumT)
72147
}
73148
}
74149

0 commit comments

Comments
 (0)