Skip to content

Commit 32cb4f8

Browse files
committed
feat(sidekick): Relax AIP checks for samples of delete operations
1 parent cc3ee38 commit 32cb4f8

File tree

2 files changed

+146
-26
lines changed

2 files changed

+146
-26
lines changed

internal/sidekick/api/model.go

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -379,15 +379,16 @@ func (m *Method) IsAIPStandard() bool {
379379
m.AIPStandardDeleteInfo() != nil
380380
}
381381

382-
// AIPStandardGetInfo contains information relevant to get operations as defined by AIP-131.
382+
// AIPStandardGetInfo contains information relevant to get operations
383+
// that are like those defined by AIP-131.
383384
type AIPStandardGetInfo struct {
384385
// ResourceNameRequestField is the field in the method input that contains the resource name
385386
// of the resource that the get operation should fetch.
386387
ResourceNameRequestField *Field
387388
}
388389

389-
// AIPStandardGetInfo returns information relevant to a get operation as defined by AIP-131
390-
// if the method is such and operation.
390+
// AIPStandardGetInfo returns information relevant to a get operation that is like
391+
// a get opereation as defined by AIP-131, if the method is such and operation.
391392
func (m *Method) AIPStandardGetInfo() *AIPStandardGetInfo {
392393
// A get operation is always a simple operation that returns a resource.
393394
if !m.IsSimple() || m.InputType == nil || m.ReturnsEmpty || m.OutputType.Resource == nil {
@@ -425,15 +426,16 @@ func (m *Method) AIPStandardGetInfo() *AIPStandardGetInfo {
425426
}
426427
}
427428

428-
// AIPStandardDeleteInfo contains information relevant to delete operations as defined by AIP-135.
429+
// AIPStandardDeleteInfo contains information relevant to delete operations
430+
// that are like those defined by AIP-135.
429431
type AIPStandardDeleteInfo struct {
430432
// ResourceNameRequestField is the field in the method input that contains the resource name
431433
// of the resource that the delete operation should delete.
432434
ResourceNameRequestField *Field
433435
}
434436

435-
// AIPStandardDeleteInfo returns information relevant to a delete operation as defined by AIP-135
436-
// if the method is such an operation.
437+
// AIPStandardDeleteInfo returns information relevant to a delete operation that is like
438+
// a delete operation as defined by AIP-135, if the method is such an operation.
437439
func (m *Method) AIPStandardDeleteInfo() *AIPStandardDeleteInfo {
438440
// A delete operation is either simple or LRO.
439441
if !m.IsSimple() && m.OperationInfo == nil {
@@ -451,24 +453,44 @@ func (m *Method) AIPStandardDeleteInfo() *AIPStandardDeleteInfo {
451453
return nil
452454
}
453455

454-
// Find the field in the request that is a resource reference of a resource
455-
// whose singular name is maybeSingular.
456-
fieldIndex := slices.IndexFunc(m.InputType.Fields, func(f *Field) bool {
456+
// Find the field in the request that is a resource reference of a resource,
457+
// chosen as follows:
458+
//
459+
// We prioritize the matches as follows:
460+
// 1. The field name is "name" and the resource singular name matches maybeSingular.
461+
// 2. The field name is "name" and the resource singular name is empty.
462+
// 3. The resource singular name matches maybeSingular.
463+
var bestField *Field
464+
for _, f := range m.InputType.Fields {
457465
if f.ResourceReference == nil {
458-
return false
466+
continue
459467
}
460468
resource, ok := m.Model.State.ResourceByType[f.ResourceReference.Type]
461469
if !ok {
462-
return false
470+
continue
463471
}
464-
return strings.ToLower(resource.Singular) == maybeSingular
465-
})
466-
if fieldIndex == -1 {
472+
473+
fieldSingular := strings.ToLower(resource.Singular)
474+
475+
if f.Name == "name" &&
476+
(fieldSingular == "" || fieldSingular == maybeSingular) {
477+
bestField = f
478+
// If we already found a field named "name" and it matches 1. or 2.
479+
// we won't find a better field.
480+
break
481+
}
482+
483+
if fieldSingular == maybeSingular {
484+
bestField = f
485+
}
486+
}
487+
488+
if bestField == nil {
467489
return nil
468490
}
469491

470492
return &AIPStandardDeleteInfo{
471-
ResourceNameRequestField: m.InputType.Fields[fieldIndex],
493+
ResourceNameRequestField: bestField,
472494
}
473495
}
474496

internal/sidekick/api/model_test.go

Lines changed: 109 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -508,22 +508,50 @@ func TestAIPStandardGetInfo(t *testing.T) {
508508
}
509509

510510
func TestAIPStandardDeleteInfo(t *testing.T) {
511-
resourceType := "google.cloud.secretmanager.v1/Secret"
511+
resource := &Resource{
512+
Type: "google.cloud.secretmanager.v1/Secret",
513+
Singular: "secret",
514+
}
515+
resourceWithoutSingular := &Resource{
516+
Type: "google.cloud.secretmanager.v1/SecretWithoutSingular",
517+
}
512518
resourceNameField := &Field{
513519
Name: "name",
514520
ResourceReference: &ResourceReference{
515-
Type: resourceType,
521+
Type: resource.Type,
516522
},
517523
}
518-
resource := &Resource{
519-
Type: resourceType,
520-
Singular: "secret",
524+
resourceOtherNameField := &Field{
525+
Name: "other_name",
526+
ResourceReference: &ResourceReference{
527+
Type: resource.Type,
528+
},
529+
}
530+
resourceNameNoSingularField := &Field{
531+
Name: "name",
532+
ResourceReference: &ResourceReference{
533+
Type: resourceWithoutSingular.Type,
534+
},
521535
}
536+
resourceOtherNameNoSingularField := &Field{
537+
Name: "other_name",
538+
ResourceReference: &ResourceReference{
539+
Type: resourceWithoutSingular.Type,
540+
},
541+
}
542+
nonExistentResourceField := &Field{
543+
Name: "name",
544+
ResourceReference: &ResourceReference{
545+
Type: "nonexistent.googleapis.com/NonExistent",
546+
},
547+
}
548+
522549
model := &API{
523-
ResourceDefinitions: []*Resource{resource},
550+
ResourceDefinitions: []*Resource{resource, resourceWithoutSingular},
524551
State: &APIState{
525552
ResourceByType: map[string]*Resource{
526-
resourceType: resource,
553+
resource.Type: resource,
554+
resourceWithoutSingular.Type: resourceWithoutSingular,
527555
},
528556
},
529557
}
@@ -545,6 +573,18 @@ func TestAIPStandardDeleteInfo(t *testing.T) {
545573
ResourceNameRequestField: resourceNameField,
546574
},
547575
},
576+
{
577+
name: "valid simple delete with missing singular name on resource",
578+
method: &Method{
579+
Name: "DeleteSecret",
580+
InputType: &Message{Name: "DeleteSecretRequest", Fields: []*Field{resourceNameNoSingularField}},
581+
ReturnsEmpty: true,
582+
Model: model,
583+
},
584+
want: &AIPStandardDeleteInfo{
585+
ResourceNameRequestField: resourceNameNoSingularField,
586+
},
587+
},
548588
{
549589
name: "valid lro delete",
550590
method: &Method{
@@ -557,6 +597,18 @@ func TestAIPStandardDeleteInfo(t *testing.T) {
557597
ResourceNameRequestField: resourceNameField,
558598
},
559599
},
600+
{
601+
name: "valid delete with other name matching singular",
602+
method: &Method{
603+
Name: "DeleteSecret",
604+
InputType: &Message{Name: "DeleteSecretRequest", Fields: []*Field{resourceOtherNameField}},
605+
ReturnsEmpty: true,
606+
Model: model,
607+
},
608+
want: &AIPStandardDeleteInfo{
609+
ResourceNameRequestField: resourceOtherNameField,
610+
},
611+
},
560612
{
561613
name: "incorrect method name",
562614
method: &Method{
@@ -582,16 +634,62 @@ func TestAIPStandardDeleteInfo(t *testing.T) {
582634
InputType: &Message{
583635
Name: "DeleteSecretRequest",
584636
Fields: []*Field{
585-
{
586-
Name: "name",
587-
ResourceReference: &ResourceReference{Type: "nonexistent.googleapis.com/NonExistent"},
588-
},
637+
nonExistentResourceField,
589638
},
590639
},
591640
Model: model, // model's ResourceByType does not contain the nonexistent resource
592641
},
593642
want: nil,
594643
},
644+
{
645+
name: "invalid delete with no matching field",
646+
method: &Method{
647+
Name: "DeleteSecret",
648+
InputType: &Message{
649+
Name: "DeleteSecretRequest",
650+
Fields: []*Field{
651+
nonExistentResourceField,
652+
resourceOtherNameNoSingularField,
653+
},
654+
},
655+
Model: model,
656+
},
657+
want: nil,
658+
},
659+
{
660+
name: "priority: name field with matching singular wins over other field with matching singular",
661+
method: &Method{
662+
Name: "DeleteSecret",
663+
InputType: &Message{
664+
Name: "DeleteSecretRequest",
665+
Fields: []*Field{
666+
resourceOtherNameField,
667+
resourceNameField,
668+
},
669+
},
670+
Model: model,
671+
},
672+
want: &AIPStandardDeleteInfo{
673+
ResourceNameRequestField: resourceNameField,
674+
},
675+
},
676+
{
677+
name: "priority: name field with empty singular wins over other field with matching singular",
678+
method: &Method{
679+
Name: "DeleteSecret",
680+
InputType: &Message{
681+
Name: "DeleteSecretRequest",
682+
Fields: []*Field{
683+
resourceOtherNameField,
684+
resourceNameNoSingularField,
685+
},
686+
},
687+
Model: model,
688+
},
689+
want: &AIPStandardDeleteInfo{
690+
ResourceNameRequestField: resourceNameNoSingularField,
691+
},
692+
},
595693
}
596694

597695
for _, tc := range testCases {

0 commit comments

Comments
 (0)