Skip to content

fix: grant delete#3524

Merged
GAlexIHU merged 2 commits intomainfrom
fix/grant-delete
Oct 17, 2025
Merged

fix: grant delete#3524
GAlexIHU merged 2 commits intomainfrom
fix/grant-delete

Conversation

@GAlexIHU
Copy link
Contributor

@GAlexIHU GAlexIHU commented Oct 17, 2025

Overview

  • Adds hook so grants are deleted
  • If we're trying to interact with grants of a deleted entitlement now we map to proper error

Notes for reviewer

Summary by CodeRabbit

  • New Features

    • Grants owned by an entitlement are now automatically soft-deleted when that entitlement is removed.
  • Bug Fixes

    • More robust detection and mapping of owner-not-found errors across grant and entitlement flows.
  • Refactor

    • Standardized error handling patterns for owner-related checks.
  • Tests

    • Test setups updated to include the entitlement-to-grant cleanup hook.

@GAlexIHU GAlexIHU requested a review from a team as a code owner October 17, 2025 14:20
@GAlexIHU GAlexIHU added the release-note/bug-fix Release note: Bug Fixes label Oct 17, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 17, 2025

📝 Walkthrough

Walkthrough

Adds a soft-delete API to mark all grants owned by a specific owner as deleted, wires a new entitlement pre-delete hook to invoke that API, and standardizes several OwnerNotFoundError checks to use lo.ErrorsAs for type-safe error detection across multiple files.

Changes

Cohort / File(s) Summary
Grant deletion API
openmeter/credit/grant/repo.go, openmeter/credit/adapter/grant.go
Adds DeleteOwnerGrants(ctx context.Context, ownerID models.NamespacedID) error to the Repo interface and implements it to set deleted_at for all grants matching an owner (soft-delete).
Entitlement → grant hook integration
openmeter/credit/hook/entitlement_hook.go, openmeter/registry/builder/entitlement.go, test/billing/subscription_suite.go, test/entitlement/regression/framework_test.go
Introduces NewEntitlementHook and registers the hook in entitlement registry and test setups; hook calls DeleteOwnerGrants on entitlement pre-delete.
Error-constructor and usage
openmeter/credit/grant/owner_connector.go, openmeter/entitlement/metered/grant_owner_adapter.go
Adds NewOwnerNotFoundError(owner, attemptedOwner) and updates callers to use the constructor for OwnerNotFoundError creation.
Error handling refactor (lo.ErrorsAs)
openmeter/credit/balance.go, openmeter/entitlement/metered/entitlement_grant.go, openmeter/entitlement/metered/reset.go
Replaces direct type assertions for grant.OwnerNotFoundError with lo.ErrorsAs[*grant.OwnerNotFoundError](err) checks to detect that error type.
Test/mocks update
openmeter/server/server_test.go
Adds a no-op DeleteOwnerGrants(ctx context.Context, ownerID models.NamespacedID) error to NoopGrantRepo to satisfy the extended interface.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "fix: grant delete" is directly related to the main change in the changeset. The primary objective is to implement grant deletion via a new entitlement hook that deletes grants when entitlements are deleted, which is clearly communicated by the title. The title is concise and specific enough for a developer reviewing history to understand the core change involves grant deletion functionality. While the title could be more elaborate (e.g., mentioning the entitlement hook mechanism), it meets the requirement of being a clear, single-sentence summary that highlights the important change without unnecessary detail or vague language.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/grant-delete

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aac833f and 8c32f51.

📒 Files selected for processing (2)
  • openmeter/credit/balance.go (2 hunks)
  • openmeter/entitlement/metered/reset.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • openmeter/entitlement/metered/reset.go
  • openmeter/credit/balance.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Artifacts / Benthos Collector Container image
  • GitHub Check: Artifacts / Container image
  • GitHub Check: Code Generators
  • GitHub Check: Build
  • GitHub Check: Test
  • GitHub Check: Migration Checks
  • GitHub Check: Lint
  • GitHub Check: Analyze (go)
  • GitHub Check: Repository Scan

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@GAlexIHU GAlexIHU enabled auto-merge (squash) October 17, 2025 14:21
chrisgacsal
chrisgacsal previously approved these changes Oct 17, 2025
Copy link
Collaborator

@chrisgacsal chrisgacsal left a comment

Choose a reason for hiding this comment

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

Awesome! :shipit:

Copy link
Contributor

@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: 1

🧹 Nitpick comments (3)
openmeter/credit/grant/owner_connector.go (1)

59-65: LGTM! Constructor provides consistent error wrapping.

The new NewOwnerNotFoundError constructor properly wraps the specific error type into a generic not-found error, providing a clean API boundary for error handling.

Consider adding a blank line between line 58 and 59 for better readability:

 func (e OwnerNotFoundError) Error() string {
 	return fmt.Sprintf("Owner %s not found in namespace %s, attempted to find as %s", e.Owner.ID, e.Owner.Namespace, e.AttemptedOwner)
 }
+
 func NewOwnerNotFoundError(owner models.NamespacedID, attemptedOwner string) error {
openmeter/credit/adapter/grant.go (1)

66-72: Consider validating input parameters.

The method doesn't validate that ownerID.ID and ownerID.Namespace are non-empty. While this is a repository-layer method, defensive validation could prevent potential issues.

Apply this diff to add validation:

 func (g *grantDBADapter) DeleteOwnerGrants(ctx context.Context, ownerID models.NamespacedID) error {
+	if ownerID.ID == "" || ownerID.Namespace == "" {
+		return fmt.Errorf("ownerID and namespace must not be empty")
+	}
 	command := g.db.Grant.Update().
 		SetDeletedAt(clock.Now()).
 		Where(db_grant.OwnerID(ownerID.ID), db_grant.Namespace(ownerID.Namespace))
 
 	return command.Exec(ctx)
 }
openmeter/credit/hook/entitlement_hook.go (1)

41-41: Consider adding context to the error.

The error from DeleteOwnerGrants is returned directly. Adding context would improve debuggability.

Apply this diff to wrap the error:

-	return h.grantRepo.DeleteOwnerGrants(ctx, models.NamespacedID{Namespace: meteredEnt.Namespace, ID: meteredEnt.ID})
+	if err := h.grantRepo.DeleteOwnerGrants(ctx, models.NamespacedID{Namespace: meteredEnt.Namespace, ID: meteredEnt.ID}); err != nil {
+		return fmt.Errorf("failed to delete grants for entitlement %s: %w", meteredEnt.ID, err)
+	}
+	return nil
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4b90181 and aac833f.

📒 Files selected for processing (12)
  • openmeter/credit/adapter/grant.go (1 hunks)
  • openmeter/credit/balance.go (2 hunks)
  • openmeter/credit/grant/owner_connector.go (1 hunks)
  • openmeter/credit/grant/repo.go (1 hunks)
  • openmeter/credit/hook/entitlement_hook.go (1 hunks)
  • openmeter/entitlement/metered/entitlement_grant.go (1 hunks)
  • openmeter/entitlement/metered/grant_owner_adapter.go (2 hunks)
  • openmeter/entitlement/metered/reset.go (2 hunks)
  • openmeter/registry/builder/entitlement.go (2 hunks)
  • openmeter/server/server_test.go (1 hunks)
  • test/billing/subscription_suite.go (2 hunks)
  • test/entitlement/regression/framework_test.go (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (12)
openmeter/credit/grant/repo.go (1)
pkg/models/id.go (1)
  • NamespacedID (7-10)
openmeter/credit/grant/owner_connector.go (2)
pkg/models/id.go (1)
  • NamespacedID (7-10)
pkg/models/errors.go (1)
  • NewGenericNotFoundError (38-40)
openmeter/entitlement/metered/grant_owner_adapter.go (1)
openmeter/credit/grant/owner_connector.go (1)
  • NewOwnerNotFoundError (60-65)
openmeter/credit/adapter/grant.go (4)
pkg/models/id.go (1)
  • NamespacedID (7-10)
openmeter/ent/db/grant.go (2)
  • Grant (21-63)
  • Grant (86-105)
pkg/clock/clock.go (1)
  • Now (14-21)
openmeter/ent/db/grant/where.go (3)
  • OwnerID (90-92)
  • ID (15-17)
  • Namespace (70-72)
openmeter/credit/balance.go (1)
openmeter/credit/grant/owner_connector.go (1)
  • OwnerNotFoundError (51-54)
openmeter/registry/builder/entitlement.go (1)
openmeter/credit/hook/entitlement_hook.go (1)
  • NewEntitlementHook (23-29)
openmeter/entitlement/metered/entitlement_grant.go (1)
openmeter/credit/grant/owner_connector.go (1)
  • OwnerNotFoundError (51-54)
openmeter/server/server_test.go (1)
pkg/models/id.go (1)
  • NamespacedID (7-10)
test/entitlement/regression/framework_test.go (1)
openmeter/credit/hook/entitlement_hook.go (1)
  • NewEntitlementHook (23-29)
openmeter/credit/hook/entitlement_hook.go (4)
pkg/models/servicehook.go (2)
  • ServiceHook (9-15)
  • NoopServiceHook (167-167)
openmeter/credit/grant/repo.go (1)
  • Repo (72-86)
openmeter/entitlement/metered/entitlement.go (1)
  • ParseFromGenericEntitlement (73-126)
pkg/models/id.go (1)
  • NamespacedID (7-10)
openmeter/entitlement/metered/reset.go (1)
openmeter/credit/grant/owner_connector.go (1)
  • OwnerNotFoundError (51-54)
test/billing/subscription_suite.go (1)
openmeter/credit/hook/entitlement_hook.go (1)
  • NewEntitlementHook (23-29)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Artifacts / Benthos Collector Container image
  • GitHub Check: Artifacts / Container image
  • GitHub Check: Test
  • GitHub Check: Lint
  • GitHub Check: Migration Checks
  • GitHub Check: Code Generators
  • GitHub Check: Build
  • GitHub Check: Analyze (go)
🔇 Additional comments (14)
openmeter/credit/balance.go (2)

228-234: Improved error handling with error unwrapping.

The change from type assertion to lo.ErrorsAs is a good improvement. Unlike type assertion which only checks for exact type matches, lo.ErrorsAs (wrapping Go's errors.As) properly handles wrapped errors by unwrapping the error chain. This makes the error checking more robust when errors are wrapped with additional context (e.g., fmt.Errorf("context: %w", err)).


18-18: Import and lo.ErrorsAs usage verified as correct.

The lo.ErrorsAs function is a shortcut for errors.As, making it a type-safe wrapper that properly handles error wrapping chains. The import on line 18 is necessary and the usage on line 230 follows the correct pattern: lo.ErrorsAs[*ErrorType](err) returns a tuple that can be checked with ok.

This refactor improves error handling compared to direct type assertions, as errors.As properly unwraps error chains while type assertions only match exact types. The pattern is consistently used throughout the codebase (20+ locations verified) with no conflicts.

openmeter/registry/builder/entitlement.go (1)

13-13: LGTM! Hook registration follows the correct pattern.

The new EntitlementHook is properly imported and registered after the entitlement connector is initialized, consistent with the existing subscription hook registration pattern at line 114.

Also applies to: 115-115

openmeter/entitlement/metered/reset.go (1)

17-17: LGTM! Error handling refactored to use type-safe approach.

The change from type assertion to lo.ErrorsAs provides better type safety while maintaining the same behavior - mapping OwnerNotFoundError to entitlement.NotFoundError.

Also applies to: 50-50

test/billing/subscription_suite.go (1)

15-15: LGTM! Test configuration mirrors production setup.

The hook registration in tests ensures that the cascade deletion behavior will be exercised during test execution, maintaining consistency with the production configuration.

Also applies to: 298-298

openmeter/entitlement/metered/entitlement_grant.go (1)

83-83: LGTM! Consistent error handling refactor.

The change aligns with the error handling pattern used in reset.go, providing type-safe error checking across the codebase.

openmeter/entitlement/metered/grant_owner_adapter.go (1)

61-61: LGTM! Consistent use of error constructor.

Both call sites now use the centralized grant.NewOwnerNotFoundError constructor, improving maintainability by having a single point of error construction.

Also applies to: 125-125

openmeter/server/server_test.go (1)

1338-1340: LGTM! No-op implementation maintains interface compliance.

The no-op implementation of DeleteOwnerGrants is consistent with the existing test double pattern in NoopGrantRepo, allowing tests to compile without requiring a full repository implementation.

openmeter/credit/grant/repo.go (1)

81-82: Soft deletion properly implemented with appropriate foreign key cascade.

Verified that DeleteOwnerGrants performs a soft delete by setting deleted_at timestamp, confirmed in the implementation at openmeter/credit/adapter/grant.go:66-72. The foreign key constraint on the grants table is configured with ON DELETE CASCADE (migration 20250605102416_subscription-cascade.up.sql), which means soft deletion is used for explicit grant cleanup operations while the database handles cascading cleanup of grants when entitlements are deleted. The implementation is correct and intentional.

test/entitlement/regression/framework_test.go (2)

18-18: LGTM! Import correctly added for the new entitlement hook.

The aliased import follows the existing pattern and is used appropriately at line 203.


201-204: LGTM! Hook registration correctly integrated.

The entitlement hook is properly wired alongside the subscription hook, enabling grant cascade deletion in the test suite.

openmeter/credit/hook/entitlement_hook.go (2)

12-21: LGTM! Clean hook structure using composition.

The type aliases and struct design follow the service hook pattern correctly. Embedding NoopHook provides default implementations while allowing selective override of lifecycle methods.


23-29: LGTM! Constructor follows standard patterns.

The constructor properly initializes the hook with its dependency and returns the interface type.

openmeter/credit/adapter/grant.go (1)

66-72: Transaction coordination is already properly handled; code is correct.

The implementation correctly coordinates transactions. The entitlement service wraps the entire deletion flow—including PreDelete hooks and grant deletion—within a single transaction.Run() at line 172. This ensures that if the grant soft-delete fails, the entitlement deletion is rolled back automatically. The context passed through the hook chain retains transaction state from the service layer, making the grant adapter's soft-delete operation part of the same atomic transaction.

@GAlexIHU GAlexIHU merged commit 929c685 into main Oct 17, 2025
27 checks passed
@GAlexIHU GAlexIHU deleted the fix/grant-delete branch October 17, 2025 14:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/bug-fix Release note: Bug Fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants