Skip to content

Commit 13fd5a1

Browse files
feat(sidekick): Show actual enum values on setter samples. (#2491)
1 parent 35e4495 commit 13fd5a1

File tree

7 files changed

+176
-23
lines changed

7 files changed

+176
-23
lines changed

internal/sidekick/internal/api/model.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,24 @@ type Field struct {
729729
Codec any
730730
}
731731

732+
// FieldParent returns the Parent field with an alternative name.
733+
//
734+
// In some mustache templates we want to access the parent for the
735+
// enclosing field. In mustache you can get a field from an enclosing context
736+
// *if* the name is unique.
737+
func (f *Field) FieldParent() *Message {
738+
return f.Parent
739+
}
740+
741+
// FieldCodec returns the Codec field with an alternative name.
742+
//
743+
// In some mustache templates we want to access the codec for the
744+
// enclosing field. In mustache you can get a field from an enclosing context
745+
// *if* the name is unique.
746+
func (f *Field) FieldCodec() any {
747+
return f.Codec
748+
}
749+
732750
// DocumentAsRequired returns true if the field should be documented as required.
733751
func (field *Field) DocumentAsRequired() bool {
734752
return slices.Contains(field.Behavior, FIELD_BEHAVIOR_REQUIRED)

internal/sidekick/internal/rust/annotate.go

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,12 @@ type enumAnnotation struct {
448448
// this is basically `QualifiedName`. For messages in the current package
449449
// this includes `modelAnnotations.PackageName`.
450450
NameInExamples string
451+
// These are some of the enum values that can be used in examples,
452+
// accompanied by an index to facilitate generating code that can
453+
// distinctly reference each value. These attempt to avoid deprecated
454+
// and the default values. Contains at most 3 elements. Will be empty iff
455+
// the enum has no values.
456+
ValuesForExamples []*enumValueForExamples
451457
// If set, this enum is only enabled when some features are enabled
452458
FeatureGates []string
453459
FeatureGatesOp string
@@ -461,6 +467,11 @@ type enumValueAnnotation struct {
461467
SerializeAsString bool
462468
}
463469

470+
type enumValueForExamples struct {
471+
EnumValue *api.EnumValue
472+
Index int
473+
}
474+
464475
// annotateModel creates a struct used as input for Mustache templates.
465476
// Fields and methods defined in this struct directly correspond to Mustache
466477
// tags. For example, the Mustache tag {{#Services}} uses the
@@ -1063,19 +1074,6 @@ func (c *codec) annotateEnum(e *api.Enum, model *api.API, full bool) {
10631074
if strings.HasPrefix(qualifiedName, c.modulePath+"::") {
10641075
nameInExamples = fmt.Sprintf("%s::model::%s", c.packageNamespace(model), relativeName)
10651076
}
1066-
annotations := &enumAnnotation{
1067-
Name: enumName(e),
1068-
ModuleName: toSnake(enumName(e)),
1069-
QualifiedName: qualifiedName,
1070-
RelativeName: relativeName,
1071-
NameInExamples: nameInExamples,
1072-
}
1073-
e.Codec = annotations
1074-
1075-
if !full {
1076-
// We have basic annotations, we are done.
1077-
return
1078-
}
10791077

10801078
// For BigQuery (and so far only BigQuery), the enum values conflict when
10811079
// converted to the Rust style [1]. Basically, there are several enum values
@@ -1100,6 +1098,44 @@ func (c *codec) annotateEnum(e *api.Enum, model *api.API, full bool) {
11001098
}
11011099
}
11021100

1101+
// We try to pick some good enum values to show in examples.
1102+
// - We pick from the already computed unique values, even though that applies to BigQuery only.
1103+
// - We pick values that are not deprecated.
1104+
// - We don't pick the default value.
1105+
goodValues := language.FilterSlice(unique, func(ev *api.EnumValue) bool {
1106+
return !ev.Deprecated && ev.Number != 0
1107+
})
1108+
// If we couldn't find any good enum values for examples, then we pick from all enum values.
1109+
if len(goodValues) == 0 {
1110+
goodValues = unique
1111+
}
1112+
// We pick at most 3 values as samples do not need to be exhaustive.
1113+
goodValues = goodValues[:min(3, len(goodValues))]
1114+
1115+
i := -1
1116+
forExamples := language.MapSlice(goodValues, func(ev *api.EnumValue) *enumValueForExamples {
1117+
i++
1118+
return &enumValueForExamples{
1119+
EnumValue: ev,
1120+
Index: i,
1121+
}
1122+
})
1123+
1124+
annotations := &enumAnnotation{
1125+
Name: enumName(e),
1126+
ModuleName: toSnake(enumName(e)),
1127+
QualifiedName: qualifiedName,
1128+
RelativeName: relativeName,
1129+
NameInExamples: nameInExamples,
1130+
ValuesForExamples: forExamples,
1131+
}
1132+
e.Codec = annotations
1133+
1134+
if !full {
1135+
// We have basic annotations, we are done.
1136+
return
1137+
}
1138+
11031139
annotations.DocLines = c.formatDocComments(e.Documentation, e.ID, model.State, e.Scopes())
11041140
annotations.UniqueNames = unique
11051141
}

internal/sidekick/internal/rust/annotate_test.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,11 @@ func TestEnumAnnotations(t *testing.T) {
684684
DocLines: []string{"/// The enum is documented."},
685685
UniqueNames: []*api.EnumValue{v0, v1, v2, v3, v4},
686686
NameInExamples: "google_cloud_test_v1::model::TestEnum",
687+
ValuesForExamples: []*enumValueForExamples{
688+
{EnumValue: v0, Index: 0},
689+
{EnumValue: v1, Index: 1},
690+
{EnumValue: v3, Index: 2},
691+
},
687692
}
688693
if diff := cmp.Diff(want, enum.Codec, cmpopts.IgnoreFields(api.EnumValue{}, "Codec", "Parent")); diff != "" {
689694
t.Errorf("mismatch in enum annotations (-want, +got)\n:%s", diff)
@@ -776,6 +781,10 @@ func TestDuplicateEnumValueAnnotations(t *testing.T) {
776781
RelativeName: "TestEnum",
777782
UniqueNames: []*api.EnumValue{v0, v2},
778783
NameInExamples: "google_cloud_test_v1::model::TestEnum",
784+
ValuesForExamples: []*enumValueForExamples{
785+
{EnumValue: v0, Index: 0},
786+
{EnumValue: v2, Index: 1},
787+
},
779788
}
780789

781790
if diff := cmp.Diff(want, enum.Codec, cmpopts.IgnoreFields(api.EnumValue{}, "Codec", "Parent")); diff != "" {
@@ -1719,3 +1728,82 @@ func TestSetterSampleAnnotations(t *testing.T) {
17191728
t.Errorf("mismatch in message_field.MessageType")
17201729
}
17211730
}
1731+
1732+
func TestEnumAnnotationsValuesForExamples(t *testing.T) {
1733+
v_good1 := &api.EnumValue{Name: "GOOD_1", Number: 1}
1734+
v_good2 := &api.EnumValue{Name: "GOOD_2", Number: 2}
1735+
v_good3 := &api.EnumValue{Name: "GOOD_3", Number: 3}
1736+
v_good4 := &api.EnumValue{Name: "GOOD_4", Number: 4}
1737+
v_bad_deprecated := &api.EnumValue{Name: "BAD_DEPRECATED", Number: 5, Deprecated: true}
1738+
v_bad_default := &api.EnumValue{Name: "BAD_DEFAULT", Number: 0}
1739+
1740+
testCases := []struct {
1741+
name string
1742+
values []*api.EnumValue
1743+
wantExamples []*enumValueForExamples
1744+
}{
1745+
{
1746+
name: "more than 3 good values",
1747+
values: []*api.EnumValue{v_good1, v_good2, v_good3, v_good4},
1748+
wantExamples: []*enumValueForExamples{
1749+
{EnumValue: v_good1, Index: 0},
1750+
{EnumValue: v_good2, Index: 1},
1751+
{EnumValue: v_good3, Index: 2},
1752+
},
1753+
},
1754+
{
1755+
name: "less than 3 good values",
1756+
values: []*api.EnumValue{v_good1, v_good2, v_bad_deprecated},
1757+
wantExamples: []*enumValueForExamples{
1758+
{EnumValue: v_good1, Index: 0},
1759+
{EnumValue: v_good2, Index: 1},
1760+
},
1761+
},
1762+
{
1763+
name: "no good values",
1764+
values: []*api.EnumValue{v_bad_default, v_bad_deprecated},
1765+
wantExamples: []*enumValueForExamples{
1766+
{EnumValue: v_bad_default, Index: 0},
1767+
{EnumValue: v_bad_deprecated, Index: 1},
1768+
},
1769+
},
1770+
{
1771+
name: "no values",
1772+
values: []*api.EnumValue{},
1773+
wantExamples: []*enumValueForExamples{},
1774+
},
1775+
{
1776+
name: "mixed good and bad values",
1777+
values: []*api.EnumValue{v_bad_default, v_good1, v_bad_deprecated, v_good2},
1778+
wantExamples: []*enumValueForExamples{
1779+
{EnumValue: v_good1, Index: 0},
1780+
{EnumValue: v_good2, Index: 1},
1781+
},
1782+
},
1783+
}
1784+
1785+
for _, tc := range testCases {
1786+
t.Run(tc.name, func(t *testing.T) {
1787+
enum := &api.Enum{
1788+
Name: "TestEnum",
1789+
ID: ".test.v1.TestEnum",
1790+
Package: "test.v1",
1791+
Values: tc.values,
1792+
}
1793+
model := api.NewTestAPI([]*api.Message{}, []*api.Enum{enum}, []*api.Service{})
1794+
if err := api.CrossReference(model); err != nil {
1795+
t.Fatalf("CrossReference() failed: %v", err)
1796+
}
1797+
codec, err := newCodec("protobuf", map[string]string{})
1798+
if err != nil {
1799+
t.Fatal(err)
1800+
}
1801+
annotateModel(model, codec)
1802+
1803+
got := enum.Codec.(*enumAnnotation).ValuesForExamples
1804+
if diff := cmp.Diff(tc.wantExamples, got, cmpopts.IgnoreFields(api.EnumValue{}, "Parent")); diff != "" {
1805+
t.Errorf("mismatch in ValuesForExamples (-want, +got)\n:%s", diff)
1806+
}
1807+
})
1808+
}
1809+
}

internal/sidekick/internal/rust/templates/common/setter_preamble/map.mustache

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,9 @@ limitations under the License.
7878
/// # use {{Parent.Codec.NameInExamples}};
7979
/// use {{Codec.ValueField.EnumType.Codec.NameInExamples}};
8080
/// let x = {{Parent.Codec.Name}}::new().set_{{Codec.SetterName}}([
81-
/// ("key0", {{Codec.ValueField.EnumType.Codec.Name}}::default()),
82-
/// ("key1", {{Codec.ValueField.EnumType.Codec.Name}}::default()),
81+
{{#Codec.ValueField.EnumType.Codec.ValuesForExamples}}
82+
/// ("key{{Index}}", {{EnumValue.Codec.EnumType}}::{{EnumValue.Codec.VariantName}}),
83+
{{/Codec.ValueField.EnumType.Codec.ValuesForExamples}}
8384
/// ]);
8485
/// ```
8586
{{/Codec.ValueField.IsEnum}}
@@ -159,8 +160,9 @@ limitations under the License.
159160
/// # use {{Parent.Codec.NameInExamples}};
160161
/// use {{Codec.ValueField.EnumType.Codec.NameInExamples}};
161162
/// let x = {{Parent.Codec.Name}}::new().set_{{Codec.SetterName}}([
162-
/// (true, {{Codec.ValueField.EnumType.Codec.Name}}::default()),
163-
/// (false, {{Codec.ValueField.EnumType.Codec.Name}}::default()),
163+
{{#Codec.ValueField.EnumType.Codec.ValuesForExamples}}
164+
/// (true, {{EnumValue.Codec.EnumType}}::{{EnumValue.Codec.VariantName}}),
165+
{{/Codec.ValueField.EnumType.Codec.ValuesForExamples}}
164166
/// ]);
165167
/// ```
166168
{{/Codec.ValueField.IsEnum}}
@@ -240,8 +242,9 @@ limitations under the License.
240242
/// # use {{Parent.Codec.NameInExamples}};
241243
/// use {{Codec.ValueField.EnumType.Codec.NameInExamples}};
242244
/// let x = {{Parent.Codec.Name}}::new().set_{{Codec.SetterName}}([
243-
/// (0, {{Codec.ValueField.EnumType.Codec.Name}}::default()),
244-
/// (1, {{Codec.ValueField.EnumType.Codec.Name}}::default()),
245+
{{#Codec.ValueField.EnumType.Codec.ValuesForExamples}}
246+
/// ({{Index}}, {{EnumValue.Codec.EnumType}}::{{EnumValue.Codec.VariantName}}),
247+
{{/Codec.ValueField.EnumType.Codec.ValuesForExamples}}
245248
/// ]);
246249
/// ```
247250
{{/Codec.ValueField.IsEnum}}

internal/sidekick/internal/rust/templates/common/setter_preamble/repeated.mustache

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@ limitations under the License.
6363
/// ```
6464
/// # use {{Parent.Codec.NameInExamples}};
6565
/// use {{EnumType.Codec.NameInExamples}};
66-
/// let x = {{Parent.Codec.Name}}::new().set_{{Codec.SetterName}}([ {{EnumType.Codec.Name}}::default() ]);
66+
/// let x = {{Parent.Codec.Name}}::new().set_{{Codec.SetterName}}([
67+
{{#EnumType.Codec.ValuesForExamples}}
68+
/// {{EnumValue.Codec.EnumType}}::{{EnumValue.Codec.VariantName}},
69+
{{/EnumType.Codec.ValuesForExamples}}
70+
/// ]);
6771
/// ```
6872
{{/IsEnum}}
6973
{{#IsObject}}

internal/sidekick/internal/rust/templates/common/setter_preamble/singular_option.mustache

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,10 @@ limitations under the License.
6868
/// ```
6969
/// # use {{Parent.Codec.NameInExamples}};
7070
/// use {{EnumType.Codec.NameInExamples}};
71-
/// let x = {{Parent.Codec.Name}}::new().set_or_clear_{{Codec.SetterName}}({{EnumType.Codec.Name}}::default());
72-
/// let x = {{Parent.Codec.Name}}::new().set_or_clear_{{Codec.SetterName}}(None::<{{EnumType.Codec.Name}}>);
71+
{{#EnumType.Codec.ValuesForExamples}}
72+
/// let x{{Index}} = {{FieldParent.Codec.Name}}::new().set_or_clear_{{FieldCodec.SetterName}}(Some({{EnumValue.Codec.EnumType}}::{{EnumValue.Codec.VariantName}}));
73+
{{/EnumType.Codec.ValuesForExamples}}
74+
/// let x_none = {{Parent.Codec.Name}}::new().set_or_clear_{{Codec.SetterName}}(None::<{{EnumType.Codec.Name}}>);
7375
/// ```
7476
{{/IsEnum}}
7577
{{#IsObject}}

internal/sidekick/internal/rust/templates/common/setter_preamble/singular_value.mustache

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ limitations under the License.
6161
/// ```
6262
/// # use {{Parent.Codec.NameInExamples}};
6363
/// use {{EnumType.Codec.NameInExamples}};
64-
/// let x = {{Parent.Codec.Name}}::new().set_{{Codec.SetterName}}({{EnumType.Codec.Name}}::default());
64+
{{#EnumType.Codec.ValuesForExamples}}
65+
/// let x{{Index}} = {{FieldParent.Codec.Name}}::new().set_{{FieldCodec.SetterName}}({{EnumValue.Codec.EnumType}}::{{EnumValue.Codec.VariantName}});
66+
{{/EnumType.Codec.ValuesForExamples}}
6567
/// ```
6668
{{/IsEnum}}
6769
{{#IsObject}}

0 commit comments

Comments
 (0)