diff --git a/proto/apidocs.swagger.json b/proto/apidocs.swagger.json index d63eb95..2a91296 100644 --- a/proto/apidocs.swagger.json +++ b/proto/apidocs.swagger.json @@ -3123,6 +3123,13 @@ "optionalSubjectFilter": { "$ref": "#/definitions/v1SubjectFilter", "description": "optional_subject_filter is the optional filter for the subjects of the relationships." + }, + "optionalResourceIds": { + "type": "array", + "items": { + "type": "string" + }, + "description": "optional_resource_ids is the *optional* list of resource IDs for bulk operations.\nIf specified, optional_resource_id and optional_resource_id_prefix cannot be specified.\nThis enables efficient bulk queries using SQL IN clauses." } }, "description": "RelationshipFilter is a collection of filters which when applied to a\nrelationship will return relationships that have exactly matching fields.\n\nAll fields are optional and if left unspecified will not filter relationships,\nbut at least one field must be specified.\n\nNOTE: The performance of the API will be affected by the selection of fields\non which to filter. If a field is not indexed, the performance of the API\ncan be significantly slower." diff --git a/proto/authzed/api/v1/permission_service.pb.go b/proto/authzed/api/v1/permission_service.pb.go index 6e40415..86c7605 100644 --- a/proto/authzed/api/v1/permission_service.pb.go +++ b/proto/authzed/api/v1/permission_service.pb.go @@ -438,8 +438,12 @@ type RelationshipFilter struct { OptionalRelation string `protobuf:"bytes,3,opt,name=optional_relation,json=optionalRelation,proto3" json:"optional_relation,omitempty"` // optional_subject_filter is the optional filter for the subjects of the relationships. OptionalSubjectFilter *SubjectFilter `protobuf:"bytes,4,opt,name=optional_subject_filter,json=optionalSubjectFilter,proto3" json:"optional_subject_filter,omitempty"` - unknownFields protoimpl.UnknownFields - sizeCache protoimpl.SizeCache + // optional_resource_ids is the *optional* list of resource IDs for bulk operations. + // If specified, optional_resource_id and optional_resource_id_prefix cannot be specified. + // This enables efficient bulk queries using SQL IN clauses. + OptionalResourceIds []string `protobuf:"bytes,6,rep,name=optional_resource_ids,json=optionalResourceIds,proto3" json:"optional_resource_ids,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *RelationshipFilter) Reset() { @@ -507,6 +511,13 @@ func (x *RelationshipFilter) GetOptionalSubjectFilter() *SubjectFilter { return nil } +func (x *RelationshipFilter) GetOptionalResourceIds() []string { + if x != nil { + return x.OptionalResourceIds + } + return nil +} + // SubjectFilter specifies a filter on the subject of a relationship. // // subject_type is required and all other fields are optional, and will not @@ -2488,13 +2499,14 @@ const file_authzed_api_v1_permission_service_proto_rawDesc = "" + "\x11at_least_as_fresh\x18\x02 \x01(\v2\x18.authzed.api.v1.ZedTokenH\x00R\x0eatLeastAsFresh\x12F\n" + "\x11at_exact_snapshot\x18\x03 \x01(\v2\x18.authzed.api.v1.ZedTokenH\x00R\x0fatExactSnapshot\x12;\n" + "\x10fully_consistent\x18\x04 \x01(\bB\x0e\xfaB\x04j\x02\b\x01\xbaH\x04j\x02\b\x01H\x00R\x0ffullyConsistentB\x17\n" + - "\vrequirement\x12\b\xf8B\x01\xbaH\x02\b\x01\"\xb8\x05\n" + + "\vrequirement\x12\b\xf8B\x01\xbaH\x02\b\x01\"\x9a\x06\n" + "\x12RelationshipFilter\x12\xbc\x01\n" + "\rresource_type\x18\x01 \x01(\tB\x96\x01\xfaBHrF(\x80\x012A^(([a-z][a-z0-9_]{1,61}[a-z0-9]/)*[a-z][a-z0-9_]{1,62}[a-z0-9])?$\xbaHHrF(\x80\x012A^(([a-z][a-z0-9_]{1,61}[a-z0-9]/)*[a-z][a-z0-9_]{1,62}[a-z0-9])?$R\fresourceType\x12|\n" + "\x14optional_resource_id\x18\x02 \x01(\tBJ\xfaB\"r (\x80\b2\x1b^([a-zA-Z0-9/_|\\-=+]{1,})?$\xbaH\"r (\x80\b2\x1b^([a-zA-Z0-9/_|\\-=+]{1,})?$R\x12optionalResourceId\x12\x89\x01\n" + "\x1boptional_resource_id_prefix\x18\x05 \x01(\tBJ\xfaB\"r (\x80\b2\x1b^([a-zA-Z0-9/_|\\-=+]{1,})?$\xbaH\"r (\x80\b2\x1b^([a-zA-Z0-9/_|\\-=+]{1,})?$R\x18optionalResourceIdPrefix\x12\x81\x01\n" + "\x11optional_relation\x18\x03 \x01(\tBT\xfaB'r%(@2!^([a-z][a-z0-9_]{1,62}[a-z0-9])?$\xbaH'r%(@2!^([a-z][a-z0-9_]{1,62}[a-z0-9])?$R\x10optionalRelation\x12U\n" + - "\x17optional_subject_filter\x18\x04 \x01(\v2\x1d.authzed.api.v1.SubjectFilterR\x15optionalSubjectFilter\"\xad\x04\n" + + "\x17optional_subject_filter\x18\x04 \x01(\v2\x1d.authzed.api.v1.SubjectFilterR\x15optionalSubjectFilter\x12`\n" + + "\x15optional_resource_ids\x18\x06 \x03(\tB,\xfaB)\x92\x01&\x10d\"\"r (\x80\b2\x1b^([a-zA-Z0-9/_|\\-=+]{1,})?$R\x13optionalResourceIds\"\xad\x04\n" + "\rSubjectFilter\x12\xb4\x01\n" + "\fsubject_type\x18\x01 \x01(\tB\x90\x01\xfaBErC(\x80\x012>^([a-z][a-z0-9_]{1,61}[a-z0-9]/)*[a-z][a-z0-9_]{1,62}[a-z0-9]$\xbaHErC(\x80\x012>^([a-z][a-z0-9_]{1,61}[a-z0-9]/)*[a-z][a-z0-9_]{1,62}[a-z0-9]$R\vsubjectType\x12\x84\x01\n" + "\x13optional_subject_id\x18\x02 \x01(\tBT\xfaB'r%(\x80\b2 ^(([a-zA-Z0-9/_|\\-=+]{1,})|\\*)?$\xbaH'r%(\x80\b2 ^(([a-zA-Z0-9/_|\\-=+]{1,})|\\*)?$R\x11optionalSubjectId\x12Y\n" + diff --git a/proto/authzed/api/v1/permission_service.pb.validate.go b/proto/authzed/api/v1/permission_service.pb.validate.go index d41face..c8b96b9 100644 --- a/proto/authzed/api/v1/permission_service.pb.validate.go +++ b/proto/authzed/api/v1/permission_service.pb.validate.go @@ -421,6 +421,44 @@ func (m *RelationshipFilter) validate(all bool) error { } } + if len(m.GetOptionalResourceIds()) > 100 { + err := RelationshipFilterValidationError{ + field: "OptionalResourceIds", + reason: "value must contain no more than 100 item(s)", + } + if !all { + return err + } + errors = append(errors, err) + } + + for idx, item := range m.GetOptionalResourceIds() { + _, _ = idx, item + + if len(item) > 1024 { + err := RelationshipFilterValidationError{ + field: fmt.Sprintf("OptionalResourceIds[%v]", idx), + reason: "value length must be at most 1024 bytes", + } + if !all { + return err + } + errors = append(errors, err) + } + + if !_RelationshipFilter_OptionalResourceIds_Pattern.MatchString(item) { + err := RelationshipFilterValidationError{ + field: fmt.Sprintf("OptionalResourceIds[%v]", idx), + reason: "value does not match regex pattern \"^([a-zA-Z0-9/_|\\\\-=+]{1,})?$\"", + } + if !all { + return err + } + errors = append(errors, err) + } + + } + if len(errors) > 0 { return RelationshipFilterMultiError(errors) } @@ -509,6 +547,8 @@ var _RelationshipFilter_OptionalResourceIdPrefix_Pattern = regexp.MustCompile("^ var _RelationshipFilter_OptionalRelation_Pattern = regexp.MustCompile("^([a-z][a-z0-9_]{1,62}[a-z0-9])?$") +var _RelationshipFilter_OptionalResourceIds_Pattern = regexp.MustCompile("^([a-zA-Z0-9/_|\\-=+]{1,})?$") + // Validate checks the field values on SubjectFilter with the rules defined in // the proto definition for this message. If any rules are violated, the first // error encountered is returned, or nil if there are no violations. diff --git a/proto/authzed/api/v1/permission_service_vtproto.pb.go b/proto/authzed/api/v1/permission_service_vtproto.pb.go index 15f71ed..0796d8d 100644 --- a/proto/authzed/api/v1/permission_service_vtproto.pb.go +++ b/proto/authzed/api/v1/permission_service_vtproto.pb.go @@ -91,6 +91,11 @@ func (m *RelationshipFilter) CloneVT() *RelationshipFilter { r.OptionalResourceIdPrefix = m.OptionalResourceIdPrefix r.OptionalRelation = m.OptionalRelation r.OptionalSubjectFilter = m.OptionalSubjectFilter.CloneVT() + if rhs := m.OptionalResourceIds; rhs != nil { + tmpContainer := make([]string, len(rhs)) + copy(tmpContainer, rhs) + r.OptionalResourceIds = tmpContainer + } if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) copy(r.unknownFields, m.unknownFields) @@ -842,6 +847,15 @@ func (this *RelationshipFilter) EqualVT(that *RelationshipFilter) bool { if this.OptionalResourceIdPrefix != that.OptionalResourceIdPrefix { return false } + if len(this.OptionalResourceIds) != len(that.OptionalResourceIds) { + return false + } + for i, vx := range this.OptionalResourceIds { + vy := that.OptionalResourceIds[i] + if vx != vy { + return false + } + } return string(this.unknownFields) == string(that.unknownFields) } @@ -1902,6 +1916,15 @@ func (m *RelationshipFilter) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if len(m.OptionalResourceIds) > 0 { + for iNdEx := len(m.OptionalResourceIds) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.OptionalResourceIds[iNdEx]) + copy(dAtA[i:], m.OptionalResourceIds[iNdEx]) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.OptionalResourceIds[iNdEx]))) + i-- + dAtA[i] = 0x32 + } + } if len(m.OptionalResourceIdPrefix) > 0 { i -= len(m.OptionalResourceIdPrefix) copy(dAtA[i:], m.OptionalResourceIdPrefix) @@ -3824,6 +3847,12 @@ func (m *RelationshipFilter) SizeVT() (n int) { if l > 0 { n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) } + if len(m.OptionalResourceIds) > 0 { + for _, s := range m.OptionalResourceIds { + l = len(s) + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } n += len(m.unknownFields) return n } @@ -4879,6 +4908,38 @@ func (m *RelationshipFilter) UnmarshalVT(dAtA []byte) error { } m.OptionalResourceIdPrefix = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field OptionalResourceIds", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.OptionalResourceIds = append(m.OptionalResourceIds, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:])