diff --git a/internal/services/v1/relationships.go b/internal/services/v1/relationships.go index e498e0370a..af4fc48e1b 100644 --- a/internal/services/v1/relationships.go +++ b/internal/services/v1/relationships.go @@ -577,9 +577,21 @@ func validateRelationshipsFilter(ctx context.Context, filter *v1.RelationshipFil } } - // Ensure the resource ID and the resource ID prefix are not set at the same time. - if filter.OptionalResourceId != "" && filter.OptionalResourceIdPrefix != "" { - return NewInvalidFilterErr("resource_id and resource_id_prefix cannot be set at the same time", filter.String()) + // Use counter-based validation for mutual exclusion of resource ID fields + resourceIdFieldsCount := 0 + if filter.OptionalResourceId != "" { + resourceIdFieldsCount++ + } + if filter.OptionalResourceIdPrefix != "" { + resourceIdFieldsCount++ + } + if len(filter.OptionalResourceIds) > 0 { + resourceIdFieldsCount++ + } + + // Ensure only one resource ID field type is set at a time + if resourceIdFieldsCount > 1 { + return NewInvalidFilterErr("only one of resource_id, resource_id_prefix, or resource_ids can be set at the same time", filter.String()) } // Ensure that at least one field is set. @@ -590,6 +602,7 @@ func checkIfFilterIsEmpty(filter *v1.RelationshipFilter) error { if filter.ResourceType == "" && filter.OptionalResourceId == "" && filter.OptionalResourceIdPrefix == "" && + len(filter.OptionalResourceIds) == 0 && filter.OptionalRelation == "" && filter.OptionalSubjectFilter == nil { return NewInvalidFilterErr("at least one field must be set", filter.String()) diff --git a/pkg/datastore/datastore.go b/pkg/datastore/datastore.go index f169695182..6ea3c0e180 100644 --- a/pkg/datastore/datastore.go +++ b/pkg/datastore/datastore.go @@ -331,9 +331,25 @@ func RelationshipsFilterFromCoreFilter(filter *core.RelationshipFilter) (Relatio // RelationshipsFilterFromPublicFilter constructs a datastore RelationshipsFilter from an API-defined RelationshipFilter. func RelationshipsFilterFromPublicFilter(filter *v1.RelationshipFilter) (RelationshipsFilter, error) { var resourceIds []string + + // Handle resource ID fields with mutual exclusion logic + resourceIdFieldsCount := 0 if filter.OptionalResourceId != "" { + resourceIdFieldsCount++ resourceIds = []string{filter.OptionalResourceId} } + if filter.OptionalResourceIdPrefix != "" { + resourceIdFieldsCount++ + } + if len(filter.OptionalResourceIds) > 0 { + resourceIdFieldsCount++ + resourceIds = filter.OptionalResourceIds + } + + // Ensure only one resource ID field type is set + if resourceIdFieldsCount > 1 { + return RelationshipsFilter{}, fmt.Errorf("cannot specify more than one of OptionalResourceId, OptionalResourceIdPrefix, or OptionalResourceIds") + } var subjectsSelectors []SubjectsSelector if filter.OptionalSubjectFilter != nil { @@ -360,10 +376,6 @@ func RelationshipsFilterFromPublicFilter(filter *v1.RelationshipFilter) (Relatio }) } - if filter.OptionalResourceId != "" && filter.OptionalResourceIdPrefix != "" { - return RelationshipsFilter{}, fmt.Errorf("cannot specify both OptionalResourceId and OptionalResourceIDPrefix") - } - if filter.ResourceType == "" && filter.OptionalRelation == "" && len(resourceIds) == 0 && filter.OptionalResourceIdPrefix == "" && len(subjectsSelectors) == 0 { return RelationshipsFilter{}, fmt.Errorf("at least one filter field must be set") } diff --git a/pkg/datastore/datastore_test.go b/pkg/datastore/datastore_test.go index 4cac47d680..5ebaa9ba91 100644 --- a/pkg/datastore/datastore_test.go +++ b/pkg/datastore/datastore_test.go @@ -131,6 +131,87 @@ func TestRelationshipsFilterFromPublicFilter(t *testing.T) { }, "", }, + { + "bulk resource ids single", + &v1.RelationshipFilter{ + ResourceType: "sometype", + OptionalResourceIds: []string{"id1"}, + OptionalRelation: "somerel", + }, + RelationshipsFilter{ + OptionalResourceType: "sometype", + OptionalResourceIds: []string{"id1"}, + OptionalResourceRelation: "somerel", + }, + "", + }, + { + "bulk resource ids multiple", + &v1.RelationshipFilter{ + ResourceType: "sometype", + OptionalResourceIds: []string{"id1", "id2", "id3"}, + OptionalRelation: "somerel", + }, + RelationshipsFilter{ + OptionalResourceType: "sometype", + OptionalResourceIds: []string{"id1", "id2", "id3"}, + OptionalResourceRelation: "somerel", + }, + "", + }, + { + "bulk resource ids with subject filter", + &v1.RelationshipFilter{ + ResourceType: "sometype", + OptionalResourceIds: []string{"id1", "id2"}, + OptionalRelation: "somerel", + OptionalSubjectFilter: &v1.SubjectFilter{ + SubjectType: "someothertype", + }, + }, + RelationshipsFilter{ + OptionalResourceType: "sometype", + OptionalResourceIds: []string{"id1", "id2"}, + OptionalResourceRelation: "somerel", + OptionalSubjectsSelectors: []SubjectsSelector{ + { + OptionalSubjectType: "someothertype", + }, + }, + }, + "", + }, + { + "error: resource_id and resource_ids both set", + &v1.RelationshipFilter{ + ResourceType: "sometype", + OptionalResourceId: "single_id", + OptionalResourceIds: []string{"id1", "id2"}, + }, + RelationshipsFilter{}, + "cannot specify more than one of OptionalResourceId, OptionalResourceIdPrefix, or OptionalResourceIds", + }, + { + "error: resource_id_prefix and resource_ids both set", + &v1.RelationshipFilter{ + ResourceType: "sometype", + OptionalResourceIdPrefix: "prefix_", + OptionalResourceIds: []string{"id1", "id2"}, + }, + RelationshipsFilter{}, + "cannot specify more than one of OptionalResourceId, OptionalResourceIdPrefix, or OptionalResourceIds", + }, + { + "error: all three resource id fields set", + &v1.RelationshipFilter{ + ResourceType: "sometype", + OptionalResourceId: "single_id", + OptionalResourceIdPrefix: "prefix_", + OptionalResourceIds: []string{"id1", "id2"}, + }, + RelationshipsFilter{}, + "cannot specify more than one of OptionalResourceId, OptionalResourceIdPrefix, or OptionalResourceIds", + }, } for _, test := range tests {