Skip to content

Commit e35ac86

Browse files
committed
feat(sidekick/rust): Generate samples for update RPCs
Update RPCs are defined by AIP 134
1 parent 5b3fd11 commit e35ac86

File tree

5 files changed

+202
-1
lines changed

5 files changed

+202
-1
lines changed

internal/sidekick/api/model.go

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,8 @@ func (m *Method) IsAIPStandard() bool {
430430
m.AIPStandardDeleteInfo() != nil ||
431431
m.AIPStandardUndeleteInfo() != nil ||
432432
m.AIPStandardListInfo() != nil ||
433-
m.AIPStandardCreateInfo() != nil
433+
m.AIPStandardCreateInfo() != nil ||
434+
m.AIPStandardUpdateInfo() != nil
434435
}
435436

436437
// AIPStandardGetInfo contains information relevant to get operations
@@ -632,6 +633,74 @@ func (m *Method) AIPStandardCreateInfo() *AIPStandardCreateInfo {
632633
}
633634
}
634635

636+
// AIPStandardUpdateInfo contains information relevant to update operations
637+
// that are like those defined by AIP-134.
638+
type AIPStandardUpdateInfo struct {
639+
// ResourceRequestField is the field in the method input that contains the resource
640+
// to be updated.
641+
ResourceRequestField *Field
642+
// UpdateMaskRequestField is the field in the method input that contains the update mask.
643+
UpdateMaskRequestField *Field
644+
}
645+
646+
// AIPStandardUpdateInfo returns information relevant to an update operation that is like
647+
// an update operation as defined by AIP-134, if the method is such an operation.
648+
func (m *Method) AIPStandardUpdateInfo() *AIPStandardUpdateInfo {
649+
if (!m.IsSimple() && !m.IsLRO()) || m.InputType == nil || m.ReturnsEmpty {
650+
return nil
651+
}
652+
var outputResource *Resource
653+
if m.OutputType != nil && m.OutputType.Resource != nil {
654+
outputResource = m.OutputType.Resource
655+
} else if m.OperationInfo != nil {
656+
lroResponse := m.LongRunningResponseType()
657+
if lroResponse != nil {
658+
outputResource = lroResponse.Resource
659+
}
660+
}
661+
if outputResource == nil {
662+
return nil
663+
}
664+
665+
// Standard update methods for resource "Foo" should be named "UpdateFoo".
666+
maybeSingular, found := strings.CutPrefix(strings.ToLower(m.Name), "update")
667+
if !found || maybeSingular == "" {
668+
return nil
669+
}
670+
// The request name should be "UpdateFooRequest".
671+
if strings.ToLower(m.InputType.Name) != fmt.Sprintf("update%srequest", maybeSingular) {
672+
return nil
673+
}
674+
// If the resource has a singular name, it must match.
675+
if outputResource.Singular != "" &&
676+
strings.ToLower(outputResource.Singular) != maybeSingular {
677+
return nil
678+
}
679+
680+
// The request needs to have a field for the resource.
681+
var targetTypeID string
682+
if outputResource.Self != nil {
683+
targetTypeID = outputResource.Self.ID
684+
}
685+
resourceField := findBodyField(m.InputType, m.PathInfo, targetTypeID, maybeSingular)
686+
if resourceField == nil {
687+
return nil
688+
}
689+
// The request may have an update mask.
690+
var updateMaskField *Field
691+
for _, f := range m.InputType.Fields {
692+
if f.Name == "update_mask" && f.TypezID == ".google.protobuf.FieldMask" {
693+
updateMaskField = f
694+
break
695+
}
696+
}
697+
698+
return &AIPStandardUpdateInfo{
699+
ResourceRequestField: resourceField,
700+
UpdateMaskRequestField: updateMaskField,
701+
}
702+
}
703+
635704
// AIPStandardListInfo contains information relevant to list operations
636705
// that are like those defined by AIP-132.
637706
type AIPStandardListInfo struct {

internal/sidekick/api/model_test.go

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1114,6 +1114,121 @@ func TestAIPStandardCreateInfo(t *testing.T) {
11141114
}
11151115
}
11161116

1117+
func TestAIPStandardUpdateInfo(t *testing.T) {
1118+
f := newAIPTestFixture()
1119+
1120+
// Setup for Update
1121+
secretMessage := &Message{ID: "secret_message_id"}
1122+
f.resource.Self = secretMessage
1123+
1124+
resourceField := &Field{
1125+
Name: "secret",
1126+
Typez: MESSAGE_TYPE,
1127+
TypezID: secretMessage.ID,
1128+
}
1129+
updateMaskField := &Field{
1130+
Name: "update_mask",
1131+
TypezID: ".google.protobuf.FieldMask",
1132+
}
1133+
1134+
testCases := []struct {
1135+
name string
1136+
method *Method
1137+
want *AIPStandardUpdateInfo
1138+
}{
1139+
{
1140+
name: "valid update operation",
1141+
method: &Method{
1142+
Name: "UpdateSecret",
1143+
InputType: &Message{
1144+
Name: "UpdateSecretRequest",
1145+
Fields: []*Field{
1146+
resourceField,
1147+
updateMaskField,
1148+
},
1149+
},
1150+
OutputType: &Message{Resource: f.resource},
1151+
Model: f.model,
1152+
},
1153+
want: &AIPStandardUpdateInfo{
1154+
ResourceRequestField: resourceField,
1155+
UpdateMaskRequestField: updateMaskField,
1156+
},
1157+
},
1158+
{
1159+
name: "valid update operation without mask",
1160+
method: &Method{
1161+
Name: "UpdateSecret",
1162+
InputType: &Message{
1163+
Name: "UpdateSecretRequest",
1164+
Fields: []*Field{
1165+
resourceField,
1166+
},
1167+
},
1168+
OutputType: &Message{Resource: f.resource},
1169+
Model: f.model,
1170+
},
1171+
want: &AIPStandardUpdateInfo{
1172+
ResourceRequestField: resourceField,
1173+
},
1174+
},
1175+
{
1176+
name: "invalid update operation (wrong name)",
1177+
method: &Method{
1178+
Name: "ModifySecret",
1179+
InputType: &Message{
1180+
Name: "UpdateSecretRequest",
1181+
Fields: []*Field{
1182+
resourceField,
1183+
},
1184+
},
1185+
OutputType: &Message{Resource: f.resource},
1186+
Model: f.model,
1187+
},
1188+
want: nil,
1189+
},
1190+
{
1191+
name: "invalid update operation (wrong request name)",
1192+
method: &Method{
1193+
Name: "UpdateSecret",
1194+
InputType: &Message{
1195+
Name: "ModifySecretRequest",
1196+
Fields: []*Field{
1197+
resourceField,
1198+
},
1199+
},
1200+
OutputType: &Message{Resource: f.resource},
1201+
Model: f.model,
1202+
},
1203+
want: nil,
1204+
},
1205+
{
1206+
name: "invalid update operation (missing resource)",
1207+
method: &Method{
1208+
Name: "UpdateSecret",
1209+
InputType: &Message{
1210+
Name: "UpdateSecretRequest",
1211+
Fields: []*Field{
1212+
updateMaskField,
1213+
},
1214+
},
1215+
OutputType: &Message{Resource: f.resource},
1216+
Model: f.model,
1217+
},
1218+
want: nil,
1219+
},
1220+
}
1221+
1222+
for _, tc := range testCases {
1223+
t.Run(tc.name, func(t *testing.T) {
1224+
got := tc.method.AIPStandardUpdateInfo()
1225+
if diff := cmp.Diff(tc.want, got); diff != "" {
1226+
t.Errorf("AIPStandardUpdateInfo() mismatch (-want +got):\n%s", diff)
1227+
}
1228+
})
1229+
}
1230+
}
1231+
11171232
func TestAIPStandardListInfo(t *testing.T) {
11181233
f := newAIPTestFixture()
11191234

internal/sidekick/rust/templates/common/client_method_samples/builder_fields.mustache

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ Use no newline at the end of file so the partials are all merged into a single l
3333
}}{{/ResourceIDRequestField}}{{!
3434
}}.set_{{ResourceRequestField.Codec.SetterName}}({{ResourceRequestField.MessageType.Codec.Name}}::new()/* set fields */){{!
3535
}}{{/AIPStandardCreateInfo}}{{!
36+
}}{{#AIPStandardUpdateInfo}}{{!
37+
}}.set_{{ResourceRequestField.Codec.SetterName}}({{ResourceRequestField.MessageType.Codec.Name}}::new().set_name(name)/* set fields */){{!
38+
}}{{#UpdateMaskRequestField}}{{!
39+
}}.set_{{Codec.SetterName}}(FieldMask::default().set_paths(["updated", "fields"])){{!
40+
}}{{/UpdateMaskRequestField}}{{!
41+
}}{{/AIPStandardUpdateInfo}}{{!
3642
}}{{#AIPStandardListInfo}}{{!
3743
}}.set_{{ParentRequestField.Codec.SetterName}}(parent){{!
3844
}}{{/AIPStandardListInfo}}{{!

internal/sidekick/rust/templates/common/client_method_samples/client_method_sample.mustache

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ limitations under the License.
2828
{{#AIPStandardCreateInfo}}
2929
/// use {{Service.Model.Codec.PackageNamespace}}::model::{{ResourceRequestField.MessageType.Codec.Name}};
3030
{{/AIPStandardCreateInfo}}
31+
{{#AIPStandardUpdateInfo}}
32+
{{#UpdateMaskRequestField}}
33+
/// # extern crate wkt as google_cloud_wkt;
34+
/// use google_cloud_wkt::FieldMask;
35+
{{/UpdateMaskRequestField}}
36+
/// use {{Service.Model.Codec.PackageNamespace}}::model::{{ResourceRequestField.MessageType.Codec.Name}};
37+
{{/AIPStandardUpdateInfo}}
3138
/// use {{Service.Model.Codec.PackageNamespace}}::Result;
3239
/// async fn sample(
3340
{{> /templates/common/client_method_samples/parameters}}

internal/sidekick/rust/templates/common/client_method_samples/parameters.mustache

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ limitations under the License.
3232
/// id: &str
3333
{{/ResourceIDRequestField}}
3434
{{/AIPStandardCreateInfo}}
35+
{{#AIPStandardUpdateInfo}}
36+
/// client: &{{Service.Codec.Name}},
37+
/// name: &str
38+
{{/AIPStandardUpdateInfo}}
3539
{{#AIPStandardListInfo}}
3640
/// client: &{{Service.Codec.Name}},
3741
/// parent: &str

0 commit comments

Comments
 (0)