Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: bulk permission check api is added #1681

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open

Conversation

ucatbas
Copy link
Contributor

@ucatbas ucatbas commented Oct 10, 2024

  • Added a new Bulk Permission Check API endpoint that accepts multiple permission checks in one call.
  • Added integration tests

Summary by CodeRabbit

  • New Features

    • Introduced a new API endpoint for bulk permission checks at /v1/tenants/{tenant_id}/permissions/bulk-check.
    • Added support for checking multiple permissions in a single request, improving efficiency for clients.
  • Bug Fixes

    • Enhanced validation for permission check requests and responses, ensuring stricter adherence to defined constraints.
  • Documentation

    • Updated API documentation with new endpoint details and usage examples for better clarity.
  • Tests

    • Added new test cases for bulk permission checks across various integrations (Facebook Groups, Google Docs, Notion).
    • Introduced additional test cases for Github and Exclusion sample contexts, validating permissions against repository entities.

Copy link

coderabbitai bot commented Oct 10, 2024

Walkthrough

The changes introduce a new API endpoint /v1/tenants/{tenant_id}/permissions/bulk-check for performing bulk permission checks. This endpoint supports a POST request that allows clients to check multiple permissions in a single call. New schemas for request and response structures are defined, including BulkCheckBody, BulkPermissionCheckResponse, SinglePermissionCheck, and SinglePermissionCheckResponse. Additionally, new validation methods and test cases for bulk checks across various integrations have been added, enhancing the API's functionality and testing capabilities.

Changes

File Path Change Summary
docs/api-reference/apidocs.swagger.json Added new endpoint /v1/tenants/{tenant_id}/permissions/bulk-check for bulk permission checks with request body BulkCheckBody and response type BulkPermissionCheckResponse. Added definitions for SinglePermissionCheck and SinglePermissionCheckResponse.
docs/api-reference/openapiv2/apidocs.swagger.json Similar changes as above, defining the new endpoint and its parameters, including the request and response schemas.
integration-test/usecases/facebook_groups_test.go Added a test case "Facebook Groups Sample: Bulk Checks" to validate bulk permission checks.
integration-test/usecases/google_docs_test.go Added a test case "Google Docs Sample: Bulk Checks" for bulk permission checks.
integration-test/usecases/notion_test.go Added a test case "Notion Sample: Bulk Checks" for testing bulk permission checks.
internal/servers/permissionServer.go Introduced BulkCheck method in PermissionServer to handle bulk permission checks and return results.
internal/engines/balancer/balancer.go Added BulkCheck method to Balancer struct; currently unimplemented.
internal/engines/check.go Added BulkCheck method to CheckEngine struct for performing permission checks on a set of entities.
internal/invoke/invoke.go Added BulkCheck method to DirectInvoker struct for bulk permission checks.
internal/engines/cache/cache.go Added BulkCheck method to CheckEngineWithCache struct; currently unimplemented.
internal/engines/bulk.go Enhanced BulkChecker functionality with new types and methods for processing bulk checks.
internal/engines/lookup.go Updated callback signatures in LookupEntity, LookupEntityStream, and LookupSubject methods to accept variadic parameters for improved clarity.
internal/storage/memory/schemaReader.go Improved error handling in ReadEntityDefinition and ReadRuleDefinition methods for consistency.
integration-test/specific/health_test.go Modified gRPC server connection string in health check test.
integration-test/usecases/usecase_test.go Changed gRPC server connection string in test setup.
internal/engines/check_test.go Added new test cases in Github Sample and Exclusion Sample contexts for bulk permission checks.

Possibly related PRs

Suggested reviewers

  • tolgaOzen

🐰 In a field of dreams so bright,
A new endpoint brings delight.
With bulk checks now in play,
Permissions verified in a single sway.
Hops of joy, we celebrate,
Efficiency is truly great! 🌟


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Outside diff range and nitpick comments (3)
integration-test/usecases/google_docs_test.go (1)

77-146: LGTM! The new test case is well-structured and consistent with existing tests.

The new "Google Docs Sample: Bulk Checks" test case effectively tests the bulk permission check functionality. It maintains consistency with existing test patterns and provides good coverage of the new feature.

However, there are a few suggestions for improvement:

  1. Consider making the depth configurable or add a comment explaining the choice of 100:
 Metadata: &base.PermissionCheckRequestMetadata{
     SchemaVersion: initialGoogleDocsSchemaVersion,
     SnapToken:     initialGoogleDocsSnapToken,
-    Depth:         100,
+    Depth:         100, // TODO: Explain why 100 or make configurable
 },
  1. Add separate error handling for the BulkCheck call:
 bulkRes, err := permissionClient.BulkCheck(ctx, &base.BulkPermissionCheckRequest{
     TenantId: "google-docs",
     Checks:   checks,
 })
-Expect(err).ShouldNot(HaveOccurred())
+Expect(err).ShouldNot(HaveOccurred(), "BulkCheck call failed: %v", err)
+Expect(bulkRes).ShouldNot(BeNil(), "BulkCheck response is nil")
  1. Optimize the result validation loop for better efficiency:
-for idx, check := range scenario.Checks {
-    expectedResults := check.Assertions
-    for _, expected := range expectedResults {
-        exp := base.CheckResult_CHECK_RESULT_ALLOWED
-        if !expected {
-            exp = base.CheckResult_CHECK_RESULT_DENIED
-        }
-
-        Expect(bulkRes.Results[idx].Can).Should(Equal(exp), "Permission result mismatch for check %d, expected %v, got %v", idx, exp, bulkRes.Results[idx].Can)
-    }
-}
+for idx, result := range bulkRes.Results {
+    expectedResult := base.CheckResult_CHECK_RESULT_ALLOWED
+    if !scenario.Checks[idx].Assertions[result.Permission] {
+        expectedResult = base.CheckResult_CHECK_RESULT_DENIED
+    }
+    Expect(result.Can).Should(Equal(expectedResult), "Permission result mismatch for check %d, permission %s, expected %v, got %v", idx, result.Permission, expectedResult, result.Can)
+}

These changes will improve the test case's robustness and efficiency.

docs/api-reference/openapiv2/apidocs.swagger.json (1)

654-711: Endpoint structure looks good, but some improvements needed.

The new bulk permission check endpoint is well-structured and consistent with other endpoints in the API. However, there are a few areas for improvement:

  1. The description could be more detailed to provide better context for API users.
  2. The code samples in the x-codeSamples section are currently empty. These should be filled with actual examples to help developers understand how to use the endpoint.
  3. Consider adding more language examples to the x-codeSamples section for broader language support.

To enhance the endpoint definition, consider applying these changes:

  1. Expand the description to include more details about the bulk check operation, its use cases, and any limitations.
  2. Fill in the code samples for Go, Node.js, and cURL to demonstrate how to use the endpoint.
  3. Add examples for other popular languages like Python, Java, or Ruby in the x-codeSamples section.
internal/servers/permissionServer.go (1)

49-51: Add detailed documentation for the BulkCheck method

The BulkCheck method currently has a brief comment. Consider adding more comprehensive documentation to explain the method's purpose, parameters, expected behavior, and return values. This will improve maintainability and help other developers understand the code better.

Here's how you could enhance the documentation:

-// Bulk Check - Performs Bulk Authorization Checks
+// BulkCheck performs bulk authorization checks for a list of permission checks.
+// It validates the request, processes each permission check, and returns a bulk response containing the results.
+// Parameters:
+// - ctx: The context for controlling cancellation and timeouts.
+// - request: The BulkPermissionCheckRequest containing tenant ID and a list of checks.
+// Returns:
+// - *v1.BulkPermissionCheckResponse: The response containing results for each permission check.
+// - error: An error if the bulk check operation fails.
 func (r *PermissionServer) BulkCheck(ctx context.Context, request *v1.BulkPermissionCheckRequest) (*v1.BulkPermissionCheckResponse, error) {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 39d7316 and 7e96e31.

⛔ Files ignored due to path filters (3)
  • pkg/pb/base/v1/service.pb.go is excluded by !**/*.pb.go
  • pkg/pb/base/v1/service.pb.gw.go is excluded by !**/*.pb.gw.go
  • pkg/pb/base/v1/service_grpc.pb.go is excluded by !**/*.pb.go
📒 Files selected for processing (8)
  • docs/api-reference/apidocs.swagger.json (3 hunks)
  • docs/api-reference/openapiv2/apidocs.swagger.json (3 hunks)
  • integration-test/usecases/facebook_groups_test.go (1 hunks)
  • integration-test/usecases/google_docs_test.go (1 hunks)
  • integration-test/usecases/notion_test.go (1 hunks)
  • internal/servers/permissionServer.go (1 hunks)
  • pkg/pb/base/v1/service.pb.validate.go (1 hunks)
  • proto/base/v1/service.proto (4 hunks)
🧰 Additional context used
🔇 Additional comments (14)
docs/api-reference/apidocs.swagger.json (5)

654-711: New bulk check endpoint looks good!

The new /v1/tenants/{tenant_id}/permissions/bulk-check endpoint is well-defined and consistent with existing API patterns. It correctly uses the POST method, includes appropriate parameters, and is properly tagged. The summary and description clearly explain its purpose.

However, we should review the referenced request (BulkCheckBody) and response (BulkPermissionCheckResponse) structures to ensure they are properly defined.


1540-1553: BulkCheckBody structure is well-defined

The BulkCheckBody request structure for the bulk check operation is properly defined. It contains a single required field checks which is an array of SinglePermissionCheck objects, allowing for multiple permission checks in a single request. The structure and its description are clear and consistent with the API's existing patterns.


1554-1567: BulkPermissionCheckResponse structure is appropriate

The BulkPermissionCheckResponse structure for the bulk check operation response is well-defined. It contains a single field results which is an array of SinglePermissionCheckResponse objects, allowing for multiple permission check results to be returned. The structure and its description are clear and consistent with the API's existing patterns.


3126-3184: SinglePermissionCheck and SinglePermissionCheckResponse structures are well-designed

The SinglePermissionCheck and SinglePermissionCheckResponse structures are properly defined and consistent with the existing API patterns. They include all necessary fields for performing and responding to individual permission checks within the bulk operation. The inclusion of an index field in both structures ensures that responses can be correctly matched to their corresponding requests.

These structures effectively reuse the existing permission check logic while adapting it for bulk operations, promoting consistency and maintainability in the API.


Line range hint 654-3184: Summary: Bulk permission check feature is a valuable addition to the API

The introduction of the bulk permission check endpoint (/v1/tenants/{tenant_id}/permissions/bulk-check) and its associated structures (BulkCheckBody, BulkPermissionCheckResponse, SinglePermissionCheck, and SinglePermissionCheckResponse) is a well-implemented and valuable addition to the Permify API.

These changes allow clients to perform multiple permission checks in a single request, which can significantly improve efficiency for applications that need to check multiple permissions simultaneously. The new structures and endpoint are consistent with existing API patterns and best practices, ensuring that the API remains cohesive and easy to use.

Overall, this feature enhances the API's functionality without disrupting its existing structure or usage patterns.

proto/base/v1/service.proto (5)

155-163: New BulkCheck RPC method is correctly defined

The BulkCheck method is properly added to the Permission service with the correct request and response types. The HTTP mapping aligns with the existing API design.


962-1010: SinglePermissionCheck message is well-defined

The SinglePermissionCheck message correctly encapsulates the details required for individual permission checks within a bulk request. The fields are consistent with those in PermissionCheckRequest.


1013-1026: BulkPermissionCheckResponse message is correctly structured

The BulkPermissionCheckResponse message appropriately includes a list of SinglePermissionCheckResponse results corresponding to each permission check performed.


1029-1045: SinglePermissionCheckResponse message is correctly defined

The SinglePermissionCheckResponse provides the result of a single permission check within the bulk response, including the index to correlate with the corresponding request.


Line range hint 69-151: Updated OpenAPI examples enhance API documentation

The new examples in Go, JavaScript, and cURL for the Check method provide clearer guidance on how to use the API. The inclusion of the relation field in the subject reflects changes in the request structure.

pkg/pb/base/v1/service.pb.validate.go (4)

727-832: Validation methods for BulkPermissionCheckRequest are correctly implemented

The validation methods for BulkPermissionCheckRequest comprehensively check all required fields, enforce length and pattern constraints on TenantId, limit the number of Checks to 100, and ensure each SinglePermissionCheck is validated properly.


909-1025: SinglePermissionCheck validation methods are thorough and accurate

The validation methods for SinglePermissionCheck appropriately validate Index, ensure required fields (Metadata, Entity, Permission, Subject) are present, enforce length and pattern constraints on Permission, and validate embedded messages.


1229-1312: Implementation of BulkPermissionCheckResponse validation is correct

The validation methods for BulkPermissionCheckResponse correctly enforce the maximum number of Results and validate each SinglePermissionCheckResponse.


1388-1457: SinglePermissionCheckResponse validation methods are properly implemented

The validation methods for SinglePermissionCheckResponse ensure that Index is non-negative and that Metadata is valid.

Comment on lines 76 to 82
response, err := r.invoker.Check(ctx, singleRequest)
if err != nil {
// Log and record the error for each failed check
span.RecordError(err)
span.SetStatus(otelCodes.Error, err.Error())
slog.ErrorContext(ctx, err.Error())

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Include error details in the response for failed checks

When an error occurs during a permission check, the response sets Can to CHECK_RESULT_DENIED without providing error details. Including error information can help clients understand why a check failed and handle errors more effectively.

You could modify the error handling as follows:

 // Perform the permission check
 response, err := r.invoker.Check(ctx, singleRequest)
 if err != nil {
     // Log and record the error for each failed check
     span.RecordError(err)
     span.SetStatus(otelCodes.Error, err.Error())
     slog.ErrorContext(ctx, err.Error())

     // Add the failure response with index
     results[i] = &v1.SinglePermissionCheckResponse{
         Can:      v1.CheckResult_CHECK_RESULT_DENIED,
         Metadata: &v1.PermissionCheckResponseMetadata{
+            CheckError: err.Error(), // Assuming you have a field to include error details
+            // Alternatively, add a separate field for error messages
         },
         Index:    int32(i),
     }
 } else {
     // Successful check response, attach the index
     results[i] = &v1.SinglePermissionCheckResponse{
         Can:      response.Can,
         Metadata: response.Metadata,
         Index:    int32(i),
     }
 }

Make sure to update the SinglePermissionCheckResponse and PermissionCheckResponseMetadata structures to include an error field if they don't already have one.

Committable suggestion was skipped due to low confidence.

Comment on lines 63 to 97
for i, check := range checks {
// Create individual PermissionCheckRequest for each item
singleRequest := &v1.PermissionCheckRequest{
TenantId: request.TenantId,
Metadata: check.Metadata,
Entity: check.Entity,
Permission: check.Permission,
Subject: check.Subject,
Context: check.Context,
Arguments: check.Arguments,
}

// Perform the permission check
response, err := r.invoker.Check(ctx, singleRequest)
if err != nil {
// Log and record the error for each failed check
span.RecordError(err)
span.SetStatus(otelCodes.Error, err.Error())
slog.ErrorContext(ctx, err.Error())

// Add the failure response with index
results[i] = &v1.SinglePermissionCheckResponse{
Can: v1.CheckResult_CHECK_RESULT_DENIED,
Metadata: &v1.PermissionCheckResponseMetadata{},
Index: int32(i),
}
} else {
// Successful check response, attach the index
results[i] = &v1.SinglePermissionCheckResponse{
Can: response.Can,
Metadata: response.Metadata,
Index: int32(i),
}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Handle context cancellation to ensure timely termination

Currently, the loop does not check if the context has been canceled. If the client cancels the request or if a timeout occurs, the server might continue processing all checks unnecessarily. To handle this, check the context's cancellation status within the loop.

You can add a context check like this:

 for i, check := range checks {
+    // Check if the context is canceled
+    select {
+    case <-ctx.Done():
+        // Context canceled, return the error
+        return nil, ctx.Err()
+    default:
+        // Continue processing
+    }

     // Create individual PermissionCheckRequest for each item
     singleRequest := &v1.PermissionCheckRequest{
         // fields...
     }
     // Rest of the code...
 }

This ensures that the server responds promptly to context cancellations.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for i, check := range checks {
// Create individual PermissionCheckRequest for each item
singleRequest := &v1.PermissionCheckRequest{
TenantId: request.TenantId,
Metadata: check.Metadata,
Entity: check.Entity,
Permission: check.Permission,
Subject: check.Subject,
Context: check.Context,
Arguments: check.Arguments,
}
// Perform the permission check
response, err := r.invoker.Check(ctx, singleRequest)
if err != nil {
// Log and record the error for each failed check
span.RecordError(err)
span.SetStatus(otelCodes.Error, err.Error())
slog.ErrorContext(ctx, err.Error())
// Add the failure response with index
results[i] = &v1.SinglePermissionCheckResponse{
Can: v1.CheckResult_CHECK_RESULT_DENIED,
Metadata: &v1.PermissionCheckResponseMetadata{},
Index: int32(i),
}
} else {
// Successful check response, attach the index
results[i] = &v1.SinglePermissionCheckResponse{
Can: response.Can,
Metadata: response.Metadata,
Index: int32(i),
}
}
}
for i, check := range checks {
// Check if the context is canceled
select {
case <-ctx.Done():
// Context canceled, return the error
return nil, ctx.Err()
default:
// Continue processing
}
// Create individual PermissionCheckRequest for each item
singleRequest := &v1.PermissionCheckRequest{
TenantId: request.TenantId,
Metadata: check.Metadata,
Entity: check.Entity,
Permission: check.Permission,
Subject: check.Subject,
Context: check.Context,
Arguments: check.Arguments,
}
// Perform the permission check
response, err := r.invoker.Check(ctx, singleRequest)
if err != nil {
// Log and record the error for each failed check
span.RecordError(err)
span.SetStatus(otelCodes.Error, err.Error())
slog.ErrorContext(ctx, err.Error())
// Add the failure response with index
results[i] = &v1.SinglePermissionCheckResponse{
Can: v1.CheckResult_CHECK_RESULT_DENIED,
Metadata: &v1.PermissionCheckResponseMetadata{},
Index: int32(i),
}
} else {
// Successful check response, attach the index
results[i] = &v1.SinglePermissionCheckResponse{
Can: response.Can,
Metadata: response.Metadata,
Index: int32(i),
}
}
}

🛠️ Refactor suggestion

Consider processing checks concurrently to improve performance

The BulkCheck method processes permission checks sequentially in a loop. For a large number of checks, this could become a performance bottleneck. Since each permission check is independent, you could process them concurrently using goroutines and synchronization mechanisms to improve throughput.

Here's a possible refactor using goroutines and a sync.WaitGroup:

+import (
+	"sync"
+)
 func (r *PermissionServer) BulkCheck(ctx context.Context, request *v1.BulkPermissionCheckRequest) (*v1.BulkPermissionCheckResponse, error) {
     ctx, span := tracer.Start(ctx, "permissions.bulk-check")
     defer span.End()

     // Validate the incoming request
     v := request.Validate()
     if v != nil {
         return nil, status.Error(GetStatus(v), v.Error())
     }

     checks := request.Checks
     results := make([]*v1.SinglePermissionCheckResponse, len(checks))

+    var wg sync.WaitGroup
+    var mu sync.Mutex
+    wg.Add(len(checks))
     for i, check := range checks {
+        go func(i int, check *v1.SinglePermissionCheck) {
+            defer wg.Done()
         // Create individual PermissionCheckRequest for each item
         singleRequest := &v1.PermissionCheckRequest{
             TenantId:   request.TenantId,
             Metadata:   check.Metadata,
             Entity:     check.Entity,
             Permission: check.Permission,
             Subject:    check.Subject,
             Context:    check.Context,
             Arguments:  check.Arguments,
         }

         // Perform the permission check
         response, err := r.invoker.Check(ctx, singleRequest)
         if err != nil {
             // Log and record the error for each failed check
             span.RecordError(err)
             span.SetStatus(otelCodes.Error, err.Error())
             slog.ErrorContext(ctx, err.Error())

             // Add the failure response with index
+            mu.Lock()
             results[i] = &v1.SinglePermissionCheckResponse{
                 Can:      v1.CheckResult_CHECK_RESULT_DENIED,
                 Metadata: &v1.PermissionCheckResponseMetadata{},
                 Index:    int32(i),
+                // Optionally include error details if needed
             }
+            mu.Unlock()
         } else {
             // Successful check response, attach the index
+            mu.Lock()
             results[i] = &v1.SinglePermissionCheckResponse{
                 Can:      response.Can,
                 Metadata: response.Metadata,
                 Index:    int32(i),
             }
+            mu.Unlock()
         }
+        }(i, check)
     }
+    wg.Wait()

     // Return the bulk response
     return &v1.BulkPermissionCheckResponse{
         Results: results,
     }, nil
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for i, check := range checks {
// Create individual PermissionCheckRequest for each item
singleRequest := &v1.PermissionCheckRequest{
TenantId: request.TenantId,
Metadata: check.Metadata,
Entity: check.Entity,
Permission: check.Permission,
Subject: check.Subject,
Context: check.Context,
Arguments: check.Arguments,
}
// Perform the permission check
response, err := r.invoker.Check(ctx, singleRequest)
if err != nil {
// Log and record the error for each failed check
span.RecordError(err)
span.SetStatus(otelCodes.Error, err.Error())
slog.ErrorContext(ctx, err.Error())
// Add the failure response with index
results[i] = &v1.SinglePermissionCheckResponse{
Can: v1.CheckResult_CHECK_RESULT_DENIED,
Metadata: &v1.PermissionCheckResponseMetadata{},
Index: int32(i),
}
} else {
// Successful check response, attach the index
results[i] = &v1.SinglePermissionCheckResponse{
Can: response.Can,
Metadata: response.Metadata,
Index: int32(i),
}
}
}
import (
"sync"
)
func (r *PermissionServer) BulkCheck(ctx context.Context, request *v1.BulkPermissionCheckRequest) (*v1.BulkPermissionCheckResponse, error) {
ctx, span := tracer.Start(ctx, "permissions.bulk-check")
defer span.End()
// Validate the incoming request
v := request.Validate()
if v != nil {
return nil, status.Error(GetStatus(v), v.Error())
}
checks := request.Checks
results := make([]*v1.SinglePermissionCheckResponse, len(checks))
var wg sync.WaitGroup
var mu sync.Mutex
wg.Add(len(checks))
for i, check := range checks {
go func(i int, check *v1.SinglePermissionCheck) {
defer wg.Done()
// Create individual PermissionCheckRequest for each item
singleRequest := &v1.PermissionCheckRequest{
TenantId: request.TenantId,
Metadata: check.Metadata,
Entity: check.Entity,
Permission: check.Permission,
Subject: check.Subject,
Context: check.Context,
Arguments: check.Arguments,
}
// Perform the permission check
response, err := r.invoker.Check(ctx, singleRequest)
if err != nil {
// Log and record the error for each failed check
span.RecordError(err)
span.SetStatus(otelCodes.Error, err.Error())
slog.ErrorContext(ctx, err.Error())
// Add the failure response with index
mu.Lock()
results[i] = &v1.SinglePermissionCheckResponse{
Can: v1.CheckResult_CHECK_RESULT_DENIED,
Metadata: &v1.PermissionCheckResponseMetadata{},
Index: int32(i),
// Optionally include error details if needed
}
mu.Unlock()
} else {
// Successful check response, attach the index
mu.Lock()
results[i] = &v1.SinglePermissionCheckResponse{
Can: response.Can,
Metadata: response.Metadata,
Index: int32(i),
}
mu.Unlock()
}
}(i, check)
}
wg.Wait()
// Return the bulk response
return &v1.BulkPermissionCheckResponse{
Results: results,
}, nil
}

⚠️ Potential issue

Ensure individual checks are properly validated

While the overall BulkPermissionCheckRequest is validated, individual SinglePermissionCheck items may have invalid data. To prevent potential runtime errors, consider adding validation for each SinglePermissionCheck within the loop.

You could incorporate validation like this:

 for i, check := range checks {
+    // Validate the individual check
+    v := check.Validate()
+    if v != nil {
+        // Handle invalid check
+        results[i] = &v1.SinglePermissionCheckResponse{
+            Can:      v1.CheckResult_CHECK_RESULT_DENIED,
+            Metadata: &v1.PermissionCheckResponseMetadata{},
+            Index:    int32(i),
+            // Optionally include error details if needed
+        }
+        continue
+    }

     // Create individual PermissionCheckRequest for each item
     singleRequest := &v1.PermissionCheckRequest{
         TenantId:   request.TenantId,
         Metadata:   check.Metadata,
         Entity:     check.Entity,
         Permission: check.Permission,
         Subject:    check.Subject,
         Context:    check.Context,
         Arguments:  check.Arguments,
     }
     // Rest of the code...
 }

Committable suggestion was skipped due to low confidence.

Comment on lines +133 to +142
for idx, check := range scenario.Checks {
expectedResults := check.Assertions
for _, expected := range expectedResults {
exp := base.CheckResult_CHECK_RESULT_ALLOWED
if !expected {
exp = base.CheckResult_CHECK_RESULT_DENIED
}
Expect(bulkRes.Results[idx].Can).Should(Equal(exp), "Permission result mismatch for check %d, expected %v, got %v", idx, exp, bulkRes.Results[idx].Can)
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Correct the indexing mismatch when validating bulk check results

There is a potential mismatch between the indices of the bulkRes.Results array and the expected results from scenario.Checks. Since multiple permission checks are appended to checks for each scenario (one for each permission in check.Assertions), the total number of results may exceed the number of scenarios.

Currently, the code uses idx from the outer loop to index bulkRes.Results[idx], which may not align correctly with the corresponding result. This can lead to incorrect validation of permission results.

Suggested Fix:

Introduce a separate index to accurately track the position in bulkRes.Results. Here's how you can modify the code:

     // Iterate over results and validate them
+    resultIdx := 0
     for _, check := range scenario.Checks {
-        expectedResults := check.Assertions
-        for _, expected := range expectedResults {
+        for permission, expected := range check.Assertions {
             exp := base.CheckResult_CHECK_RESULT_ALLOWED
             if !expected {
                 exp = base.CheckResult_CHECK_RESULT_DENIED
             }
-            Expect(bulkRes.Results[idx].Can).Should(Equal(exp), "Permission result mismatch for check %d, expected %v, got %v", idx, exp, bulkRes.Results[idx].Can)
+            Expect(bulkRes.Results[resultIdx].Can).Should(Equal(exp), "Permission result mismatch at result index %d for permission '%s', expected %v, got %v", resultIdx, permission, exp, bulkRes.Results[resultIdx].Can)
+            resultIdx++
         }
     }

Explanation:

  • Initialize resultIdx before the loops to keep track of the current index in bulkRes.Results.
  • Use for permission, expected := range check.Assertions to iterate over each permission and its expected result.
  • Replace bulkRes.Results[idx] with bulkRes.Results[resultIdx] to correctly access the corresponding result.
  • Increment resultIdx after each expectation to move to the next result.

This ensures that each expected permission result is correctly matched with the corresponding result from the bulk check response.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for idx, check := range scenario.Checks {
expectedResults := check.Assertions
for _, expected := range expectedResults {
exp := base.CheckResult_CHECK_RESULT_ALLOWED
if !expected {
exp = base.CheckResult_CHECK_RESULT_DENIED
}
Expect(bulkRes.Results[idx].Can).Should(Equal(exp), "Permission result mismatch for check %d, expected %v, got %v", idx, exp, bulkRes.Results[idx].Can)
}
}
resultIdx := 0
for _, check := range scenario.Checks {
for permission, expected := range check.Assertions {
exp := base.CheckResult_CHECK_RESULT_ALLOWED
if !expected {
exp = base.CheckResult_CHECK_RESULT_DENIED
}
Expect(bulkRes.Results[resultIdx].Can).Should(Equal(exp), "Permission result mismatch at result index %d for permission '%s', expected %v, got %v", resultIdx, permission, exp, bulkRes.Results[resultIdx].Can)
resultIdx++
}
}

Comment on lines +77 to +146
It("Facebook Groups Sample: Bulk Checks", func() {
for _, scenario := range shapes.InitialFacebookGroupsShape.Scenarios {
var checks []*base.SinglePermissionCheck
for _, check := range scenario.Checks {

entity, err := tuple.E(check.Entity)
if err != nil {
Expect(err).ShouldNot(HaveOccurred())
}

ear, err := tuple.EAR(check.Subject)
if err != nil {
Expect(err).ShouldNot(HaveOccurred())
}

subject := &base.Subject{
Type: ear.GetEntity().GetType(),
Id: ear.GetEntity().GetId(),
Relation: ear.GetRelation(),
}

var contextTuples []*base.Tuple

for _, t := range check.Context.Tuples {
tup, err := tuple.Tuple(t)
if err != nil {
Expect(err).ShouldNot(HaveOccurred())
}

contextTuples = append(contextTuples, tup)
}

for permission, _ := range check.Assertions {

checks = append(checks, &base.SinglePermissionCheck{
Metadata: &base.PermissionCheckRequestMetadata{
SchemaVersion: initialFacebookGroupsSchemaVersion,
SnapToken: initialFacebookGroupsSnapToken,
Depth: 100,
},
Context: &base.Context{
Tuples: contextTuples,
},
Entity: entity,
Permission: permission,
Subject: subject,
})
}
}
// Perform the bulk check
bulkRes, err := permissionClient.BulkCheck(ctx, &base.BulkPermissionCheckRequest{
TenantId: "facebook-groups",
Checks: checks,
})
Expect(err).ShouldNot(HaveOccurred())

// Iterate over results and validate them
for idx, check := range scenario.Checks {
expectedResults := check.Assertions
for _, expected := range expectedResults {
exp := base.CheckResult_CHECK_RESULT_ALLOWED
if !expected {
exp = base.CheckResult_CHECK_RESULT_DENIED
}
Expect(bulkRes.Results[idx].Can).Should(Equal(exp), "Permission result mismatch for check %d, expected %v, got %v", idx, exp, bulkRes.Results[idx].Can)
}
}
}
})

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor common code into helper functions to reduce duplication

The "Bulk Checks" test case duplicates several blocks of code from the "Checks" test case, including processing entities, subjects, context tuples, and constructing permission checks. To improve maintainability and readability, consider extracting these repeated code segments into reusable helper functions.

Comment on lines +134 to +143
for idx, check := range scenario.Checks {
expectedResults := check.Assertions
for _, expected := range expectedResults {
exp := base.CheckResult_CHECK_RESULT_ALLOWED
if !expected {
exp = base.CheckResult_CHECK_RESULT_DENIED
}
Expect(bulkRes.Results[idx].Can).Should(Equal(exp), "Permission result mismatch for check %d, expected %v, got %v", idx, exp, bulkRes.Results[idx].Can)
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix result indexing during bulk check validation

The current validation loop assumes a one-to-one correspondence between scenario.Checks and bulkRes.Results, which is incorrect because multiple SinglePermissionChecks are added per check (one per permission). This can lead to mismatches when validating the results.

To fix this, introduce a separate index resultIdx to correctly iterate over bulkRes.Results and ensure each permission's result is validated against the expected value.

Apply this diff to fix the indexing:

+               resultIdx := 0
                for idx, check := range scenario.Checks {
                    expectedResults := check.Assertions
-                   for _, expected := range expectedResults {
+                   for permission, expected := range expectedResults {
                        exp := base.CheckResult_CHECK_RESULT_ALLOWED
                        if !expected {
                            exp = base.CheckResult_CHECK_RESULT_DENIED
                        }
-                       Expect(bulkRes.Results[idx].Can).Should(Equal(exp), "Permission result mismatch for check %d, expected %v, got %v", idx, exp, bulkRes.Results[idx].Can)
+                       Expect(bulkRes.Results[resultIdx].Can).Should(Equal(exp), "Permission result mismatch for check %d, permission %s, expected %v, got %v", idx, permission, exp, bulkRes.Results[resultIdx].Can)
+                       resultIdx++
                    }
                }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for idx, check := range scenario.Checks {
expectedResults := check.Assertions
for _, expected := range expectedResults {
exp := base.CheckResult_CHECK_RESULT_ALLOWED
if !expected {
exp = base.CheckResult_CHECK_RESULT_DENIED
}
Expect(bulkRes.Results[idx].Can).Should(Equal(exp), "Permission result mismatch for check %d, expected %v, got %v", idx, exp, bulkRes.Results[idx].Can)
}
}
resultIdx := 0
for idx, check := range scenario.Checks {
expectedResults := check.Assertions
for permission, expected := range expectedResults {
exp := base.CheckResult_CHECK_RESULT_ALLOWED
if !expected {
exp = base.CheckResult_CHECK_RESULT_DENIED
}
Expect(bulkRes.Results[resultIdx].Can).Should(Equal(exp), "Permission result mismatch for check %d, permission %s, expected %v, got %v", idx, permission, exp, bulkRes.Results[resultIdx].Can)
resultIdx++
}
}

Comment on lines +171 to +205
extensions: {
key: "x-codeSamples"
value: {
list_value: {
values: {
struct_value: {
fields: {
key: "lang"
value: {string_value: "go"}
}
fields: {
key: "label"
value: {string_value: "go"}
}
fields: {
key: "source"
value: {string_value:""}
}
}
}
values: {
struct_value: {
fields: {
key: "lang"
value: {string_value: "javascript"}
}
fields: {
key: "label"
value: {string_value: "node"}
}
fields: {
key: "source"
value: {string_value:""}
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add code samples to the OpenAPI annotations for the BulkCheck method

The OpenAPI annotations for the BulkCheck method include placeholders for code samples in Go, JavaScript, and cURL, but the source fields are empty. Providing code samples will enhance the API documentation and help users understand how to use the new endpoint.

Comment on lines +961 to +1010
// PermissionCheck defines a single permission check within a bulk request.
message SinglePermissionCheck {
// The index of this permission check within a bulk request, required.
int32 index = 1 [
json_name = "index",
(validate.rules).int32.gte = 0,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "The index of this permission check in the bulk request. Required."
}
];

// Metadata associated with this request, required.
PermissionCheckRequestMetadata metadata = 2 [
json_name = "metadata",
(validate.rules).message.required = true
];

// Entity on which the permission needs to be checked, required.
Entity entity = 3 [
json_name = "entity",
(validate.rules).message.required = true,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {example: "\"repository:1\""}
];

// Name of the permission or relation, required, must start with a letter and can include alphanumeric and underscore, max 64 bytes.
string permission = 4 [
json_name = "permission",
(validate.rules).string = {
pattern: "^[a-zA-Z_]{1,64}$"
max_bytes: 64
ignore_empty: false
},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "The action the user wants to perform on the resource"}
];

// Subject for which the permission needs to be checked, required.
Subject subject = 5 [
json_name = "subject",
(validate.rules).message.required = true
];

// Context associated with this request.
Context context = 6 [
json_name = "context",
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {description: "Contextual data that can be dynamically added to permission check requests. See details on [Contextual Data](../../operations/contextual-tuples)"}
];

// Additional arguments associated with this request.
repeated Argument arguments = 7 [json_name = "arguments"];
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider reusing existing message types to avoid duplication

The SinglePermissionCheck and SinglePermissionCheckResponse messages are structurally similar to the existing PermissionCheckRequest and PermissionCheckResponse messages. Consider reusing or extending these existing messages to reduce code duplication and maintain consistency.

Also applies to: 1029-1045

Comment on lines +933 to +959
// BulkPermissionCheckRequest is the request message for the BulkCheck method in the Permission service.
message BulkPermissionCheckRequest {
// Identifier of the tenant, required, and must match the pattern "[a-zA-Z0-9-,]+", max 64 bytes.
string tenant_id = 1 [
json_name = "tenant_id",
(validate.rules).string = {
pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$"
max_bytes: 128
ignore_empty: false
},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant <code>t1</code> for this field. Required, and must match the pattern \\“[a-zA-Z0-9-,]+\\“, max 64 bytes."
}
];

// A list of permission checks to perform, required.
repeated SinglePermissionCheck checks = 3 [
json_name = "checks",
(validate.rules).repeated = {
min_items: 0
max_items: 100
items: {
message: {required: true}
}
}
];
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Enforce at least one permission check in BulkPermissionCheckRequest

The checks field allows for zero items due to min_items: 0 in the validation rules. Since the checks field is required and the purpose of the request is to perform permission checks, consider setting min_items to 1 to ensure that at least one permission check is provided.

Apply this diff to fix the validation rule:

         (validate.rules).repeated = {
-          min_items: 0
+          min_items: 1
           max_items: 100
           items: {
             message: {required: true}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// BulkPermissionCheckRequest is the request message for the BulkCheck method in the Permission service.
message BulkPermissionCheckRequest {
// Identifier of the tenant, required, and must match the pattern "[a-zA-Z0-9-,]+", max 64 bytes.
string tenant_id = 1 [
json_name = "tenant_id",
(validate.rules).string = {
pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$"
max_bytes: 128
ignore_empty: false
},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant <code>t1</code> for this field. Required, and must match the pattern \\[a-zA-Z0-9-,]+\\, max 64 bytes."
}
];
// A list of permission checks to perform, required.
repeated SinglePermissionCheck checks = 3 [
json_name = "checks",
(validate.rules).repeated = {
min_items: 0
max_items: 100
items: {
message: {required: true}
}
}
];
}
// BulkPermissionCheckRequest is the request message for the BulkCheck method in the Permission service.
message BulkPermissionCheckRequest {
// Identifier of the tenant, required, and must match the pattern "[a-zA-Z0-9-,]+", max 64 bytes.
string tenant_id = 1 [
json_name = "tenant_id",
(validate.rules).string = {
pattern: "^([a-zA-Z0-9_\\-@\\.:+]{1,128}|\\*)$"
max_bytes: 128
ignore_empty: false
},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Identifier of the tenant, if you are not using multi-tenancy (have only one tenant) use pre-inserted tenant <code>t1</code> for this field. Required, and must match the pattern \\"[a-zA-Z0-9-,]+\\", max 64 bytes."
}
];
// A list of permission checks to perform, required.
repeated SinglePermissionCheck checks = 3 [
json_name = "checks",
(validate.rules).repeated = {
min_items: 1
max_items: 100
items: {
message: {required: true}
}
}
];
}

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Outside diff range and nitpick comments (14)
internal/engines/balancer/balancer.go (2)

36-39: Add documentation and implement the BulkCheck method

  1. Consider adding documentation for the BulkCheck method to explain its purpose and expected behavior.
  2. The current implementation with panic("unimplemented") is a placeholder. Remember to implement this method to handle bulk permission checks.
  3. When implementing, consider how this method will interact with other parts of the Balancer struct, such as schemaReader, checker, and client.

Here's a suggested structure for the method with documentation:

// BulkCheck performs multiple permission checks in a single call.
// It implements the invoke.Check interface for bulk operations.
func (c *Balancer) BulkCheck(ctx context.Context, request *base.BulkPermissionCheckRequest) (*base.BulkPermissionCheckResponse, error) {
    // TODO: Implement bulk permission checking logic
    // Consider using c.schemaReader for entity definitions
    // and c.client for distributed checks if necessary
    return nil, fmt.Errorf("BulkCheck not yet implemented")
}

36-39: Consider performance and potential refactoring for BulkCheck implementation

When implementing the BulkCheck method, consider the following:

  1. Performance implications of handling multiple permission checks in a single call. You may need to optimize database queries or use batch processing techniques.
  2. Potential for refactoring common logic between Check and BulkCheck methods to avoid code duplication.
  3. Error handling strategy for cases where some checks succeed while others fail within the same bulk request.

Consider creating helper functions for common operations that both Check and BulkCheck can use. For example:

func (c *Balancer) performSingleCheck(ctx context.Context, request *base.PermissionCheckRequest) (*base.PermissionCheckResponse, error) {
    // Extract common logic from the existing Check method
}

func (c *Balancer) BulkCheck(ctx context.Context, request *base.BulkPermissionCheckRequest) (*base.BulkPermissionCheckResponse, error) {
    responses := make([]*base.SinglePermissionCheckResponse, len(request.Checks))
    for i, check := range request.Checks {
        // Use the common logic for each individual check
        response, err := c.performSingleCheck(ctx, check)
        // Handle the response and error appropriately
    }
    // Combine individual responses into a BulkPermissionCheckResponse
}
internal/engines/cache/cache.go (2)

38-41: Extend caching strategy and telemetry for bulk operations.

While adding the BulkCheck method is a good start, consider the following improvements to fully support bulk operations:

  1. Extend the caching strategy to handle bulk requests efficiently. This might involve creating a new method for bulk cache operations or adapting the existing getCheckKey and setCheckKey methods.

  2. Add new telemetry metrics for bulk operations. For example:

    • Bulk cache hit rate
    • Bulk operation duration
    • Number of permissions checked in bulk operations
  3. Update the NewCheckEngineWithCache function to initialize any new fields required for bulk operations.

Here's an example of how you might add new telemetry metrics:

type CheckEngineWithCache struct {
    // ... existing fields ...
    bulkCacheCounter              api.Int64Counter
    bulkCacheHitDurationHistogram api.Int64Histogram
    bulkOperationDurationHistogram api.Int64Histogram
}

func NewCheckEngineWithCache(
    // ... existing parameters ...
) invoke.Check {
    return &CheckEngineWithCache{
        // ... existing initializations ...
        bulkCacheCounter:              telemetry.NewCounter(meter, "bulk_cache_check_count", "Number of bulk permission cached checks performed"),
        bulkCacheHitDurationHistogram: telemetry.NewHistogram(meter, "bulk_cache_hit_duration", "microseconds", "Duration of bulk cache hits in microseconds"),
        bulkOperationDurationHistogram: telemetry.NewHistogram(meter, "bulk_operation_duration", "microseconds", "Duration of bulk operations in microseconds"),
    }
}

Remember to use these new metrics in your BulkCheck implementation.


Line range hint 1-41: Summary: Bulk check implementation needs completion and integration

The addition of the BulkCheck method is a good start towards implementing the bulk permission check API. However, there are several areas that need attention to fully realize this feature:

  1. The BulkCheck method needs to be implemented, leveraging the existing Check method and caching infrastructure.
  2. The caching strategy should be extended or adapted to efficiently handle bulk operations.
  3. Telemetry and metrics should be added for bulk operations to maintain observability.
  4. The NewCheckEngineWithCache function may need updates to initialize new fields related to bulk operations.

Next steps:

  1. Implement the BulkCheck method with proper error handling and response formatting.
  2. Extend the caching strategy for bulk operations.
  3. Add telemetry for bulk operations.
  4. Update tests to cover the new bulk check functionality.
  5. Ensure that the implementation aligns with the PR objectives of adding a bulk permission check API.

Consider creating a separate BulkCheckEngineWithCache struct if the bulk check logic becomes complex, to maintain separation of concerns and keep the codebase clean.

internal/engines/lookup.go (4)

64-70: LGTM! Consider adding a comment for clarity.

The changes to the callback function improve flexibility and prepare for bulk operations. The use of BulkEntityCallbackParams is a good approach for structured parameter passing.

Consider adding a brief comment explaining the purpose of the BulkEntityCallbackParams struct for better code readability:

 callback := func(params ...interface{}) {
     mu.Lock()         // Safeguard access to the shared slice with a mutex
     defer mu.Unlock() // Ensure the lock is released after appending the ID
+    // Extract entity information from BulkEntityCallbackParams
     if resp, ok := params[0].(*BulkEntityCallbackParams); ok {
         entityIDs = append(entityIDs, resp.entityID)
         ct = resp.token
     }
 }

133-143: LGTM! Consider improving error handling.

The changes to the callback function are consistent with the LookupEntity method and maintain the existing streaming functionality while adapting to the new parameter structure.

Consider improving the error handling by logging the error before returning:

 callback := func(params ...interface{}) {
     if resp, ok := params[0].(*BulkEntityCallbackParams); ok {
         err := server.Send(&base.PermissionLookupEntityStreamResponse{
             EntityId:        resp.entityID,
             ContinuousToken: resp.token,
         })
         // If there is an error in sending the response, the function will return
         if err != nil {
+            // Log the error for debugging purposes
+            log.Printf("Error sending entity stream response: %v", err)
             return
         }
     }
 }

205-212: LGTM! Consider extracting the callback logic.

The changes to the callback function are consistent with the other methods and maintain the existing functionality while adapting to the new parameter structure.

Consider extracting the callback logic into a separate function for better code organization and reusability:

+func appendSubjectID(subjectIDs *[]string, continuousToken *string, params ...interface{}) {
+    if resp, ok := params[0].(*BulkSubjectCallbackParams); ok {
+        *subjectIDs = append(*subjectIDs, resp.subjectID)
+        *continuousToken = resp.token
+    }
+}

 // Callback function to handle the results of permission checks.
 // If an entity passes the permission check, its ID is stored in the subjectIDs slice.
 callback := func(params ...interface{}) {
     mu.Lock()         // Lock to prevent concurrent modification of the slice.
     defer mu.Unlock() // Unlock after the ID is appended.
-
-    if resp, ok := params[0].(*BulkSubjectCallbackParams); ok {
-        subjectIDs = append(subjectIDs, resp.subjectID)
-        ct = resp.token
-    }
+    appendSubjectID(&subjectIDs, &ct, params...)
 }

This refactoring improves code readability and makes it easier to reuse the logic if needed elsewhere in the codebase.


Line range hint 1-300: Overall, the changes look good and prepare the codebase for bulk operations.

The modifications to LookupEntity, LookupEntityStream, and LookupSubject methods are consistent and well-implemented. The introduction of BulkEntityCallbackParams and BulkSubjectCallbackParams for structured parameter passing is a good approach that improves code flexibility while maintaining existing functionality.

Some minor suggestions for improvement have been made, including:

  1. Adding comments for clarity
  2. Improving error handling
  3. Extracting callback logic for better code organization

These changes align well with the PR objective of introducing a bulk permission check API. The modifications to these core methods lay the groundwork for efficient bulk operations.

As you continue to develop this feature, consider the following architectural points:

  1. Ensure that the bulk operations maintain good performance as the number of entities/subjects increases.
  2. Consider implementing rate limiting or pagination for very large bulk requests to prevent system overload.
  3. Document the new bulk operation capabilities in the API documentation to guide users on efficient usage.
internal/engines/check_test.go (2)

698-812: LGTM! Consider adding a comment explaining the bulk check process.

The new test case "Github Sample: Case 4" is well-implemented and consistent with the existing test structure. It effectively tests the bulk permission check functionality, which aligns with the PR objectives.

Consider adding a brief comment before line 767 explaining the purpose of the bulk permission check and how it differs from individual checks. This would enhance code readability and make the test's intention clearer.


Line range hint 1499-1894: LGTM! Minor inconsistency in test case naming.

The new test case "Exclusion Sample: Case 7" is well-implemented and consistent with the existing test structure. It effectively tests the bulk permission check functionality for the polymorphic relations scenario.

There's a minor inconsistency in the test case naming. The test is called "Exclusion Sample: Case 7" but it's within the "Polymorphic Relations Sample" context. Consider renaming it to "Polymorphic Relations Sample: Case 2" for consistency.

Similar to the previous suggestion, consider adding a brief comment before line 1572 explaining the purpose of the bulk permission check and how it differs from individual checks. This would enhance code readability and make the test's intention clearer.

internal/engines/bulk.go (2)

258-264: Potential deadlock due to mutex locking

In the goroutine within ExecuteRequests, the mutex mu is locked before calling bc.processLookupResults(...). If processLookupResults blocks or takes significant time, it could cause other goroutines to wait, reducing concurrency and performance.

Consider minimizing the scope of the mutex lock or redesigning processLookupResults to avoid holding the lock during lengthy operations.


385-385: Remove or implement the empty ProcessResult method

The ProcessResult method in BulkCheckPublisher is empty. If it's not needed, consider removing it to keep the codebase clean. If it's intended for future use, add a comment explaining its purpose.

internal/invoke/invoke.go (1)

235-235: Update comment to match the metric being incremented

The comment mentions increasing the lookup entity count, but the code increments checkCounter, which tracks permission checks. Update the comment to accurately describe the operation.

Apply this diff to fix the comment:

-	// Increase the lookup entity count in the metrics.
+	// Increase the check count in the metrics.
internal/engines/check.go (1)

907-943: Add unit tests for the new BulkCheck method

The BulkCheck method introduces significant new functionality to the CheckEngine. To ensure reliability and prevent future regressions, it's important to include unit tests that cover:

  • Multiple permission checks with varying outcomes
  • Error handling when permissions checks fail
  • Concurrency aspects to ensure thread safety
  • Edge cases such as empty requests or invalid inputs

Would you like assistance in drafting unit tests for the BulkCheck method or should we open a new GitHub issue to track the addition of these tests?

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 7e96e31 and bb25aa7.

📒 Files selected for processing (11)
  • integration-test/specific/health_test.go (1 hunks)
  • integration-test/usecases/usecase_test.go (1 hunks)
  • internal/engines/balancer/balancer.go (1 hunks)
  • internal/engines/bulk.go (7 hunks)
  • internal/engines/cache/cache.go (1 hunks)
  • internal/engines/check.go (2 hunks)
  • internal/engines/check_test.go (3 hunks)
  • internal/engines/lookup.go (3 hunks)
  • internal/invoke/invoke.go (3 hunks)
  • internal/servers/permissionServer.go (1 hunks)
  • internal/storage/memory/schemaReader.go (0 hunks)
💤 Files with no reviewable changes (1)
  • internal/storage/memory/schemaReader.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/servers/permissionServer.go
🧰 Additional context used
🔇 Additional comments (5)
integration-test/specific/health_test.go (1)

17-17: Clarify the reason for changing the gRPC server address

The gRPC server address has been changed from "permify:3478" to "localhost:3478". While this change might improve portability, it raises a few concerns:

  1. This modification doesn't seem directly related to the PR's main objective of adding a bulk permission check API. Could you explain the reasoning behind this change?
  2. Using "localhost" assumes the server will always run on the same machine as the test. Is this assumption valid across all your test environments, including CI/CD pipelines?
  3. Is this change consistent with other tests in the codebase? If not, should we update them as well?

Consider adding a comment explaining the rationale behind this change for future maintainers.

To ensure consistency across the codebase, let's check for other occurrences of gRPC server addresses:

✅ Verification successful
<!-- [monologue]
[task review_file_1/1]
Review file `integration-test/specific/health_test.go`.
[observations]
- The shell script found two instances where "localhost:3478" is used in `grpc.DialContext` within Go test files:
  1. `integration-test/usecases/usecase_test.go`
  2. `integration-test/specific/health_test.go`
- Both instances use "localhost:3478", indicating consistency in the use of the gRPC server address across these tests.
[analysis]
- The change in `health_test.go` aligns with the usage in `usecase_test.go`, suggesting a standardized approach to specifying the gRPC server address as "localhost:3478" in integration tests.
- This consistency addresses the concern about whether the change is mirrored in other parts of the codebase.
- No further occurrences of different server addresses were found, which supports the decision to use "localhost" for portability.
[actions]
- Since the change is consistent with existing tests, the verification is successful.
- No additional shell scripts are necessary at this point.
-->

The change to use "localhost:3478" is consistent with other tests in the codebase, ensuring standardized server addresses across integration tests.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for gRPC server addresses in Go files
rg --type go 'grpc\.Dial.*:3478'

Length of output: 359

internal/engines/balancer/balancer.go (1)

36-39: Verify integration of BulkCheck with existing codebase

As you implement the BulkCheck method, ensure proper integration with the existing codebase:

  1. Verify that the NewCheckEngineWithBalancer function provides all necessary dependencies for bulk operations.
  2. Check if any changes are needed in the base.PermissionClient interface or its implementation to support bulk operations.
  3. Consider updating any relevant tests or adding new ones to cover the bulk check functionality.

To help verify the integration, you can run the following commands:

These commands will help identify any existing bulk operation methods and related tests, which can guide your implementation and integration of the BulkCheck method.

internal/engines/check_test.go (1)

Line range hint 1-2447: Overall, excellent addition of bulk permission check tests.

The new test cases "Github Sample: Case 4" and "Exclusion Sample: Case 7" (which should be renamed to "Polymorphic Relations Sample: Case 2") are well-implemented and effectively test the new bulk permission check functionality. These additions align perfectly with the PR objectives of introducing a bulk permission check API.

The implementation is consistent across both new test cases and follows the established patterns in the existing test suite. This ensures that the new functionality is thoroughly tested in different scenarios (Github-like and Polymorphic relations).

These changes significantly enhance the test coverage for the new bulk permission check feature, which will help ensure its reliability and correctness.

internal/engines/bulk.go (1)

194-194: ⚠️ Potential issue

Correct pointer increment syntax

The expression *processedIndex++ is invalid in Go. Incrementing the value pointed to by a pointer requires parentheses around the dereferenced pointer.

Apply this fix:

-*processedIndex++ // Move to the next index for processing
+(*processedIndex)++ // Move to the next index for processing

This change ensures proper syntax and increments the value correctly.

Likely invalid or redundant comment.

internal/engines/check.go (1)

931-931: Verify the argument passed to publisher.Publish

In publisher.Publish(base.CheckResult_CHECK_RESULT_UNSPECIFIED), the argument base.CheckResult_CHECK_RESULT_UNSPECIFIED is being passed. Please verify if this is the correct or intended value. Using an unspecified check result might lead to unintended behavior.

To confirm the correct usage, you can inspect the Publish method's expected arguments and check where publisher.Publish is used elsewhere in the codebase.

@@ -49,7 +49,7 @@ var _ = BeforeSuite(func() {

var err error
// Set up a connection to the server.
conn, err = grpc.DialContext(ctx, "permify:3478", grpc.WithTransportCredentials(insecure.NewCredentials()))
conn, err = grpc.DialContext(ctx, "localhost:3478", grpc.WithTransportCredentials(insecure.NewCredentials()))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Refactor Hardcoded Server Addresses in Tests

Multiple test files contain hardcoded server addresses, which can hinder flexibility and maintenance:

  • integration-test/specific/health_test.go:17
  • integration-test/usecases/usecase_test.go:52
  • internal/authn/oidc/authn_test.go:23
  • internal/config/config_test.go:101
  • internal/config/config_test.go:129

Recommendations:

  1. Centralize Configuration:

    • Introduce environment variables for all server addresses used in tests.
    • Example implementation:
      serverAddr := os.Getenv("PERMIFY_SERVER_ADDR")
      if serverAddr == "" {
          serverAddr = "localhost:3478"  // Default value
      }
      conn, err := grpc.DialContext(ctx, serverAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
  2. Update All Test Files:

    • Replace hardcoded addresses with the centralized configuration approach.
  3. Ensure Consistency:

    • Verify that all tests reference the environment variables or configuration files appropriately.
  4. Validate in CI/CD:

    • Ensure that the CI/CD pipeline sets the necessary environment variables to maintain test integrity across environments.

Implementing these changes will enhance the test suite's flexibility and ease of maintenance.

🔗 Analysis chain

Approve with suggestions: Consider using an environment variable for the server address.

The change from "permify:3478" to "localhost:3478" is noted. This modification might simplify local development and testing. However, it could potentially impact the portability of the tests across different environments.

To improve flexibility and maintain compatibility with various setups:

  1. Consider using an environment variable for the server address. This allows easy configuration for different environments without code changes.

  2. Verify that this change doesn't break the tests in your CI/CD pipeline or other environments where the server might not be on localhost.

Here's a suggested implementation using an environment variable:

-	conn, err = grpc.DialContext(ctx, "localhost:3478", grpc.WithTransportCredentials(insecure.NewCredentials()))
+	serverAddr := os.Getenv("PERMIFY_SERVER_ADDR")
+	if serverAddr == "" {
+		serverAddr = "localhost:3478"  // Default to localhost if not set
+	}
+	conn, err = grpc.DialContext(ctx, serverAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))

To ensure this change doesn't introduce issues in other environments, please run the following verification:

This will help identify any other occurrences of hardcoded server addresses that might need to be updated for consistency.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for any hardcoded server addresses in test files

echo "Checking for hardcoded server addresses in test files:"
rg --type go -n '(localhost|permify):[0-9]+' test

Length of output: 215


Script:

#!/bin/bash
# Description: Recursively search for hardcoded server addresses in Go test files

echo "Checking for hardcoded server addresses in Go test files:"
rg --type go --files | grep '_test.go$' | xargs rg -n '(localhost|permify):[0-9]+'

Length of output: 805

Comment on lines +38 to +41
// BulkCheck implements invoke.Check.
func (c *CheckEngineWithCache) BulkCheck(ctx context.Context, request *base.BulkPermissionCheckRequest) (response *base.BulkPermissionCheckResponse, err error) {
panic("unimplemented")
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Implement the BulkCheck method to fulfill the PR objectives.

The BulkCheck method is currently unimplemented, which doesn't align with the PR objectives of adding a bulk permission check API. This could lead to runtime errors if the method is called.

To address this:

  1. Implement the bulk check logic, possibly leveraging the existing Check method for individual checks.
  2. Consider caching strategies for bulk checks to optimize performance.
  3. Ensure error handling and proper response formatting.
  4. Add appropriate logging and telemetry.

Here's a skeleton to get started:

func (c *CheckEngineWithCache) BulkCheck(ctx context.Context, request *base.BulkPermissionCheckRequest) (response *base.BulkPermissionCheckResponse, err error) {
    response = &base.BulkPermissionCheckResponse{
        Results: make([]*base.PermissionCheckResponse, len(request.GetRequests())),
    }
    
    for i, singleRequest := range request.GetRequests() {
        // Perform individual check, possibly using c.Check method
        singleResponse, err := c.Check(ctx, singleRequest)
        if err != nil {
            // Handle error (you might want to continue checking other permissions)
            response.Results[i] = &base.PermissionCheckResponse{
                Can: base.CheckResult_CHECK_RESULT_DENIED,
                Metadata: &base.PermissionCheckResponseMetadata{
                    CheckCount: 0,
                },
            }
        } else {
            response.Results[i] = singleResponse
        }
    }
    
    return response, nil
}

Consider optimizing this implementation for bulk operations, possibly by batching database queries or implementing a bulk caching strategy.

@@ -54,7 +70,8 @@ type BulkChecker struct {

// callback is a function that handles the result of each permission check.
// It is called with the entity ID and the result of the permission check (e.g., allowed or denied).
callback func(entityID, ct string)
// callback func(entityID, ct string)
callback func(params ...interface{})
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid using ...interface{} in function signatures

Changing the callback field to func(params ...interface{}) reduces type safety and makes the code harder to understand and maintain. It's better to use explicit types in function signatures to leverage compile-time checks and improve code readability.

Consider defining a specific callback type based on the BulkCheckerType. For example:

 // callback is a function that handles the result of each permission check.
 // It is called with the entity ID and the result of the permission check (e.g., allowed or denied).
-// callback func(entityID, ct string)
-callback func(params ...interface{})
+callback func(params BulkCallbackParams)

And define a dedicated interface for BulkCallbackParams:

type BulkCallbackParams interface{}

Then, create concrete types that implement this interface for each check type.

Comment on lines +364 to +384
func (s *BulkCheckPublisher) Publish(result base.CheckResult) {
// Loop through all mapping requests inside s.request.Requests
for _, req := range s.request.Checks {
s.bulkChecker.RequestChan <- BulkCheckerRequest{
Request: &base.PermissionCheckRequest{
TenantId: s.request.GetTenantId(), // Manually set tenantID from the main request
Metadata: &base.PermissionCheckRequestMetadata{
SnapToken: req.GetMetadata().GetSnapToken(),
SchemaVersion: req.GetMetadata().GetSchemaVersion(),
Depth: req.GetMetadata().GetDepth(),
},
Entity: req.GetEntity(), // Use the specific request's entity
Permission: req.GetPermission(), // Use the specific request's permission
Subject: req.GetSubject(), // The subject passed into the function
Context: req.GetContext(), // The context passed into the function
},
Result: result, // Pass the result into the bulk checker
}
}
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure correct association of results with requests in Publish

In BulkCheckPublisher.Publish, the same result is applied to all permission check requests, which may not be accurate if each check should have its own result.

Modify the Publish method to accept a slice of results corresponding to each check:

-func (s *BulkCheckPublisher) Publish(result base.CheckResult) {
+func (s *BulkCheckPublisher) Publish(results []base.CheckResult) {

Then, iterate over both s.request.Checks and results:

for i, req := range s.request.Checks {
    s.bulkChecker.RequestChan <- BulkCheckerRequest{
        Request: &base.PermissionCheckRequest{
            TenantId:   s.request.GetTenantId(),
            Metadata:   req.GetMetadata(),
            Entity:     req.GetEntity(),
            Permission: req.GetPermission(),
            Subject:    req.GetSubject(),
            Context:    req.GetContext(),
        },
        Result: results[i],
    }
}

This ensures each request is associated with its correct result.

@@ -76,7 +93,7 @@ type BulkChecker struct {
// engine: the CheckEngine to use for permission checks
// callback: a callback function that handles the result of each permission check
// concurrencyLimit: the maximum number of concurrent permission checks
func NewBulkChecker(ctx context.Context, checker invoke.Check, typ BulkCheckerType, callback func(entityID, ct string), concurrencyLimit int) *BulkChecker {
func NewBulkChecker(ctx context.Context, checker invoke.Check, typ BulkCheckerType, callback func(params ...interface{}), concurrencyLimit int) *BulkChecker {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use explicit callback types in NewBulkChecker

In the NewBulkChecker function, the callback parameter uses func(params ...interface{}), which lacks type safety. This can lead to runtime errors and makes it difficult for others to understand what parameters are expected.

Modify the function signature to use explicit types:

-func NewBulkChecker(ctx context.Context, checker invoke.Check, typ BulkCheckerType, callback func(params ...interface{}), concurrencyLimit int) *BulkChecker {
+func NewBulkChecker(ctx context.Context, checker invoke.Check, typ BulkCheckerType, callback func(params BulkCallbackParams), concurrencyLimit int) *BulkChecker {

This change enhances type safety and clarifies the expected callback parameter.

Committable suggestion was skipped due to low confidence.


var schemaVersion string
schemaVersion, err = invoker.schemaReader.HeadVersion(ctx, request.GetTenantId())
fmt.Println(invoker.schemaReader.ReadSchema(ctx, request.GetTenantId(), schemaVersion))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid using fmt.Println for logging in production code

Using fmt.Println directly outputs to standard output, which is not suitable for production environments. It would be better to use a structured logging framework or remove this statement if it was intended for debugging purposes.

Apply this diff to remove the debug print statement:

-	fmt.Println(invoker.schemaReader.ReadSchema(ctx, request.GetTenantId(), schemaVersion))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fmt.Println(invoker.schemaReader.ReadSchema(ctx, request.GetTenantId(), schemaVersion))

}
}

fmt.Println(request.GetChecks())
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid using fmt.Println for logging in production code

Directly printing to standard output using fmt.Println is generally discouraged in production code. Consider removing this statement or replacing it with appropriate logging.

Apply this diff to remove the debug print statement:

-	fmt.Println(request.GetChecks())
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
fmt.Println(request.GetChecks())

@@ -2,6 +2,7 @@ package invoke

import (
"context"
"fmt"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Remove unused import of fmt package

After removing the fmt.Println statements, the fmt package import is no longer needed and can be safely removed to clean up the imports.

Apply this diff to remove the unused import:

 import (
 	"context"
-	"fmt"
 	"time"

Committable suggestion was skipped due to low confidence.

resp, err := invoker.cc.BulkCheck(ctx, request)

duration := time.Since(start)
invoker.lookupEntityDurationHistogram.Record(ctx, duration.Microseconds())
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use the correct histogram for recording BulkCheck duration

The duration is currently being recorded using invoker.lookupEntityDurationHistogram, which is intended for lookup entity operations. It should be recorded using invoker.checkDurationHistogram to accurately reflect BulkCheck operations.

Apply this diff to correct the histogram:

-	invoker.lookupEntityDurationHistogram.Record(ctx, duration.Microseconds())
+	invoker.checkDurationHistogram.Record(ctx, duration.Microseconds())
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
invoker.lookupEntityDurationHistogram.Record(ctx, duration.Microseconds())
invoker.checkDurationHistogram.Record(ctx, duration.Microseconds())

Comment on lines +914 to +923
callback := func(params ...interface{}) {
mu.Lock() // Safeguard access to the shared slice with a mutex
defer mu.Unlock() // Ensure the lock is released after appending the ID
if resp, ok := params[0].(*BulkCheckCallbackParams); ok {
results = append(results, &base.SinglePermissionCheckResponse{
Index: int32(resp.index),
Can: resp.result,
})
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure callback parameters are checked for length and type

In the callback function, there is an assumption that params[0] exists and is of type *BulkCheckCallbackParams. This can lead to a panic if params is empty or contains a different type. It's important to check the length of params and confirm the type before using it to prevent runtime errors.

Here's a suggested fix:

 callback := func(params ...interface{}) {
     mu.Lock()         // Safeguard access to the shared slice with a mutex
     defer mu.Unlock() // Ensure the lock is released after appending the ID
+    if len(params) == 0 {
+        // Handle the case when no parameters are passed
+        return
+    }
     if resp, ok := params[0].(*BulkCheckCallbackParams); ok {
         results = append(results, &base.SinglePermissionCheckResponse{
             Index: int32(resp.index),
             Can:   resp.result,
         })
+    } else {
+        // Handle the case when the parameter is of unexpected type
+        // Possibly log an error or return
+        return
+    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
callback := func(params ...interface{}) {
mu.Lock() // Safeguard access to the shared slice with a mutex
defer mu.Unlock() // Ensure the lock is released after appending the ID
if resp, ok := params[0].(*BulkCheckCallbackParams); ok {
results = append(results, &base.SinglePermissionCheckResponse{
Index: int32(resp.index),
Can: resp.result,
})
}
}
callback := func(params ...interface{}) {
mu.Lock() // Safeguard access to the shared slice with a mutex
defer mu.Unlock() // Ensure the lock is released after appending the ID
if len(params) == 0 {
// Handle the case when no parameters are passed
return
}
if resp, ok := params[0].(*BulkCheckCallbackParams); ok {
results = append(results, &base.SinglePermissionCheckResponse{
Index: int32(resp.index),
Can: resp.result,
})
} else {
// Handle the case when the parameter is of unexpected type
// Possibly log an error or return
return
}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant