Skip to content

Commit 59cd235

Browse files
feat(sidekick/rust): Generate samples for get RPCs (#3312)
1 parent 0c57626 commit 59cd235

File tree

4 files changed

+249
-1
lines changed

4 files changed

+249
-1
lines changed

internal/sidekick/api/model.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,12 +363,56 @@ func (m *Method) HasAutoPopulatedFields() bool {
363363
}
364364

365365
// IsSimple returns true if the method is not a streaming, pagination or LRO method.
366+
// IsSimple simplifies writing mustache templates, mostly for samples.
366367
func (m *Method) IsSimple() bool {
367368
return m.Pagination == nil &&
368369
!m.ClientSideStreaming && !m.ServerSideStreaming &&
369370
m.OperationInfo == nil && m.DiscoveryLro == nil
370371
}
371372

373+
// IsAIPStandard returns true if the method is one of the AIP standard methods.
374+
// IsAIPStandard simplifies writing mustache templates, mostly for samples.
375+
func (m *Method) IsAIPStandard() bool {
376+
return m.AIPStandardGetInfo() != nil
377+
}
378+
379+
// AIPStandardGetInfo contains information relevant to get operations as defined by AIP-131.
380+
type AIPStandardGetInfo struct {
381+
// ResourceNameRequestField is the field in the method input that contains the resource name
382+
// of the resource that the get operation should fetch.
383+
ResourceNameRequestField *Field
384+
}
385+
386+
// AIPStandardGetInfo returns information relevant to a get operation as defined by AIP-131
387+
// if the method is such and operation.
388+
func (m *Method) AIPStandardGetInfo() *AIPStandardGetInfo {
389+
// A get operation is always a simple operation that returns a resource.
390+
if !m.IsSimple() || m.InputType == nil || m.ReturnsEmpty || m.OutputType.Resource == nil {
391+
return nil
392+
}
393+
394+
singular := strings.ToLower(m.OutputType.Resource.Singular)
395+
// The method needs to be called getfoo and the request type needs to be called getfoorequest.
396+
if strings.ToLower(m.Name) != fmt.Sprintf("get%s", singular) ||
397+
strings.ToLower(m.InputType.Name) != fmt.Sprintf("get%srequest", singular) {
398+
return nil
399+
}
400+
401+
// The request needs to have a field for the resource name of the resource to obtain.
402+
nameFieldIndex := slices.IndexFunc(m.InputType.Fields, func(f *Field) bool {
403+
return f.IsResourceReference() &&
404+
f.ResourceReference.Type == m.OutputType.Resource.Type
405+
})
406+
407+
if nameFieldIndex == -1 {
408+
return nil
409+
}
410+
411+
return &AIPStandardGetInfo{
412+
ResourceNameRequestField: m.InputType.Fields[nameFieldIndex],
413+
}
414+
}
415+
372416
// PathInfo contains normalized request path information.
373417
type PathInfo struct {
374418
// The list of bindings, including the top-level binding.

internal/sidekick/api/model_test.go

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,199 @@ func TestIsSimpleMethod(t *testing.T) {
282282
}
283283
}
284284

285+
func TestIsAIPStandard(t *testing.T) {
286+
// Setup for a valid Get operation
287+
resourceType := "google.cloud.secretmanager.v1/Secret"
288+
resourceNameField := &Field{
289+
ResourceReference: &ResourceReference{
290+
Type: resourceType,
291+
},
292+
}
293+
resource := &Resource{
294+
Type: resourceType,
295+
Singular: "secret",
296+
}
297+
output := &Message{
298+
Resource: resource,
299+
}
300+
validGetMethod := &Method{
301+
Name: "GetSecret",
302+
InputType: &Message{Name: "GetSecretRequest", Fields: []*Field{resourceNameField}},
303+
OutputType: output,
304+
}
305+
306+
// Setup for an invalid Get operation (e.g., wrong name)
307+
invalidGetMethod := &Method{
308+
Name: "ListSecrets", // Not a Get method
309+
InputType: &Message{Name: "ListSecretsRequest"},
310+
OutputType: output,
311+
}
312+
313+
testCases := []struct {
314+
name string
315+
method *Method
316+
want bool
317+
}{
318+
{
319+
name: "standard get method returns true",
320+
method: validGetMethod,
321+
want: true,
322+
},
323+
{
324+
name: "non-standard method returns false",
325+
method: invalidGetMethod,
326+
want: false,
327+
},
328+
}
329+
330+
for _, tc := range testCases {
331+
t.Run(tc.name, func(t *testing.T) {
332+
if got := tc.method.IsAIPStandard(); got != tc.want {
333+
t.Errorf("IsAIPStandard() = %v, want %v", got, tc.want)
334+
}
335+
})
336+
}
337+
}
338+
339+
func TestAIPStandardGetInfo(t *testing.T) {
340+
resourceType := "google.cloud.secretmanager.v1/Secret"
341+
resourceNameField := &Field{
342+
ResourceReference: &ResourceReference{
343+
Type: resourceType,
344+
},
345+
}
346+
resource := &Resource{
347+
Type: resourceType,
348+
Singular: "secret",
349+
}
350+
output := &Message{
351+
Resource: resource,
352+
}
353+
testCases := []struct {
354+
name string
355+
method *Method
356+
want *AIPStandardGetInfo
357+
}{
358+
{
359+
name: "valid get operation",
360+
method: &Method{
361+
Name: "GetSecret",
362+
InputType: &Message{Name: "GetSecretRequest", Fields: []*Field{resourceNameField}},
363+
OutputType: output,
364+
},
365+
want: &AIPStandardGetInfo{
366+
ResourceNameRequestField: resourceNameField,
367+
},
368+
},
369+
{
370+
name: "method name is incorrect",
371+
method: &Method{
372+
Name: "Get",
373+
InputType: &Message{Name: "GetSecretRequest", Fields: []*Field{resourceNameField}},
374+
OutputType: output,
375+
},
376+
want: nil,
377+
},
378+
{
379+
name: "request type name is incorrect",
380+
method: &Method{
381+
Name: "GetSecret",
382+
InputType: &Message{Name: "GetRequest", Fields: []*Field{resourceNameField}},
383+
OutputType: output,
384+
},
385+
want: nil,
386+
},
387+
{
388+
name: "returns empty",
389+
method: &Method{
390+
Name: "GetSecret",
391+
InputType: &Message{Name: "GetSecretRequest", Fields: []*Field{resourceNameField}},
392+
OutputType: output,
393+
ReturnsEmpty: true,
394+
},
395+
want: nil,
396+
},
397+
{
398+
name: "output is not a resource",
399+
method: &Method{
400+
Name: "GetSecret",
401+
InputType: &Message{Name: "GetSecretRequest", Fields: []*Field{resourceNameField}},
402+
OutputType: &Message{
403+
Resource: nil,
404+
},
405+
},
406+
want: nil,
407+
},
408+
{
409+
name: "request does not contain resource name field",
410+
method: &Method{
411+
Name: "GetSecret",
412+
InputType: &Message{Name: "GetSecretRequest"},
413+
OutputType: output,
414+
},
415+
want: nil,
416+
},
417+
{
418+
name: "pagination method is not a standard get operation",
419+
method: &Method{
420+
Name: "GetSecret",
421+
InputType: &Message{Name: "GetSecretRequest", Fields: []*Field{resourceNameField}},
422+
OutputType: output,
423+
Pagination: &Field{},
424+
},
425+
want: nil,
426+
},
427+
{
428+
name: "client streaming method is not a standard get operation",
429+
method: &Method{
430+
Name: "GetSecret",
431+
InputType: &Message{Name: "GetSecretRequest", Fields: []*Field{resourceNameField}},
432+
OutputType: output,
433+
ClientSideStreaming: true,
434+
},
435+
want: nil,
436+
},
437+
{
438+
name: "server streaming method is not a standard get operation",
439+
method: &Method{
440+
Name: "GetSecret",
441+
InputType: &Message{Name: "GetSecretRequest", Fields: []*Field{resourceNameField}},
442+
OutputType: output,
443+
ServerSideStreaming: true,
444+
},
445+
want: nil,
446+
},
447+
{
448+
name: "LRO method is not a standard get operation",
449+
method: &Method{
450+
Name: "GetSecret",
451+
InputType: &Message{Name: "GetSecretRequest", Fields: []*Field{resourceNameField}},
452+
OutputType: output,
453+
OperationInfo: &OperationInfo{},
454+
},
455+
want: nil,
456+
},
457+
{
458+
name: "Discovery LRO method is not a standard get operation",
459+
method: &Method{
460+
Name: "GetSecret",
461+
InputType: &Message{Name: "GetSecretRequest", Fields: []*Field{resourceNameField}},
462+
OutputType: output,
463+
DiscoveryLro: &DiscoveryLro{},
464+
},
465+
want: nil,
466+
},
467+
}
468+
for _, tc := range testCases {
469+
t.Run(tc.name, func(t *testing.T) {
470+
got := tc.method.AIPStandardGetInfo()
471+
if diff := cmp.Diff(tc.want, got); diff != "" {
472+
t.Errorf("AIPStandardGetInfo() mismatch (-want +got):\n%s", diff)
473+
}
474+
})
475+
}
476+
}
477+
285478
func TestFieldTypePredicates(t *testing.T) {
286479
type TestCase struct {
287480
field *Field

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
See the License for the specific language governing permissions and
1414
limitations under the License.
1515
}}
16+
{{#AIPStandardGetInfo}}
17+
/// .set_{{ResourceNameRequestField.Codec.SetterName}}(resource_name)
18+
{{/AIPStandardGetInfo}}
19+
{{^IsAIPStandard}}
1620
/// /* set fields */
21+
{{/IsAIPStandard}}

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313
See the License for the specific language governing permissions and
1414
limitations under the License.
1515
}}
16-
/// client: &{{Service.Codec.Name}}
16+
{{#AIPStandardGetInfo}}
17+
/// client: &{{Service.Codec.Name}},
18+
/// resource_name: &str
19+
{{/AIPStandardGetInfo}}
20+
{{^IsAIPStandard}}
21+
/// client: &{{Service.Codec.Name}}
22+
{{/IsAIPStandard}}

0 commit comments

Comments
 (0)