Skip to content

Conversation

@chrisgacsal
Copy link
Collaborator

@chrisgacsal chrisgacsal commented Apr 4, 2025

Overview

Add initial/experimental version of the add-on API. Expect follow-up PRs with improvement, fixes, breaking changes.

Summary by CodeRabbit

  • New Features
    • Introducing an enhanced add-on module integrated into the product catalog, enabling users to create, update, publish, archive, and delete add-ons.
    • Expanded support for managing associated pricing configurations and rate cards, providing dynamic options and streamlined administration.
    • Improved API and HTTP endpoints with robust validation, error handling, and event notification to ensure a smoother user experience.

@chrisgacsal chrisgacsal added area/api release-note/feature Release note: Exciting New Features labels Apr 4, 2025
@chrisgacsal chrisgacsal self-assigned this Apr 4, 2025
@chrisgacsal chrisgacsal requested a review from a team as a code owner April 4, 2025 09:56
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Apr 4, 2025

📝 Walkthrough

Walkthrough

The changes add comprehensive support for managing add-ons and add-on rate cards throughout the system. New entities, CRUD operations, query builders, mutations, schema definitions, client integrations, service layers, HTTP drivers, event definitions, and corresponding tests have been introduced. The modifications extend both the database layer and product catalog modules, integrating add-on functionality into the server wiring, transactional clients, and API endpoints.

Changes

File(s) Change Summary
app/common/productcatalog.go,
cmd/server/(main.go, wire.go, wire_gen.go)
Added a new wire set and integrated an add-on service into RouterConfig, Application struct, and server initialization.
openmeter/ent/db/addon/*.go Introduced a new Addon entity model with fields, scanning/assignment methods, and CRUD builders (create, query, update, delete).
openmeter/ent/db/addonratecard/*.go Implemented a new AddonRateCard entity with constants, predicate functions, CRUD builders, and query methods for rate card management.
openmeter/ent/db/(client.go, ent.go, expose.go) Extended the database client by adding Addon and AddonRateCard clients; updated column validation and transaction methods accordingly.
openmeter/ent/db/feature*.go Added a new addon_ratecard edge to the Feature entity and introduced associated query, create, and update methods to manage add-on rate card relationships.
openmeter/ent/db/(hook/hook.go, migrate/schema.go, mutation.go, paginate.go, predicate/predicate.go, runtime.go, setorclear.go, tx.go) Enhanced the DB layer to support add-ons and add-on rate cards via new hooks, migration schemas, mutations, pagination functions, predicate extensions, runtime and Tx updates.
openmeter/ent/schema/(addon.go, feature.go) Defined new entity schemas for Addon and AddonRateCard; updated the Feature schema to include an edge for addon_ratecard.
openmeter/event/metadata/resourcepath.go Added a new constant EntityAddon to represent the addon resource type.
openmeter/productcatalog/addon/** Introduced a comprehensive product catalog addon module including schema definitions, adapters, mapping functions, assertions, error types, event types, HTTP drivers, repository and service layers, plus extensive tests.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (1.64.8)

Error: you are using a configuration file for golangci-lint v2 with golangci-lint v1: please use golangci-lint v2
Failed executing command with error: you are using a configuration file for golangci-lint v2 with golangci-lint v1: please use golangci-lint v2

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 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 generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @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
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: 5

🧹 Nitpick comments (58)
openmeter/ent/db/predicate/predicate.go (2)

9-21: Addon predicate and error handling properly implemented

The Addon predicate type and its corresponding OrErr function follow the established pattern in the codebase for creating predicates and error handlers. Note the typo in the comment where "nit" should be "nil", but this is consistent with the existing comments throughout the file.

There's a small typo in the comment - "if the error is not nit" should be "if the error is not nil". However, this appears to be consistent with similar comments throughout the file for other entities, so it's not critical to change unless fixing all occurrences.


23-35: AddonRateCard predicate and error handling properly implemented

The AddonRateCard predicate type and its corresponding OrErr function are correctly implemented, matching the pattern used for other entities in the codebase.

Same typo as above in the comment: "if the error is not nit" should be "if the error is not nil", but this is consistent with existing patterns.

openmeter/productcatalog/addon/assert.go (4)

28-52: Inconsistent nil pointer handling in Description field.

While most fields are checked with direct comparison after nil check, the Description field uses a special handling with lo.FromPtrOr(). Consider using the same approach for all fields to avoid potential nil pointer panics.

 if i.Name != nil {
-	assert.Equalf(t, *i.Name, a.Name, "update input: name mismatch")
+	assert.Equalf(t, *i.Name, a.Name, "update input: name mismatch")
 }

 if i.Description != nil {
 	assert.Equalf(t, lo.FromPtrOr(i.Description, ""), lo.FromPtrOr(a.Description, ""), "update input: description mismatch")
 }

 if i.Metadata != nil {
-	assert.Equalf(t, *i.Metadata, a.Metadata, "metadata mismatch")
+	assert.Equalf(t, *i.Metadata, a.Metadata, "update input: metadata mismatch")
 }

 if i.Annotations != nil {
-	assert.Equalf(t, *i.Annotations, a.Annotations, "annotations mismatch")
+	assert.Equalf(t, *i.Annotations, a.Annotations, "update input: annotations mismatch")
 }

54-63: Missing RateCards comparison and namespace check.

This function doesn't compare RateCards or check the Namespace field, which is inconsistent with the other assertion functions. Consider either adding these comparisons or adding a comment explaining why they're intentionally omitted.

func AssertAddonEqual(t *testing.T, expected, actual Addon) {
	t.Helper()

+	assert.Equalf(t, expected.Namespace, actual.Namespace, "namespace mismatch")
	assert.Equalf(t, expected.Key, actual.Key, "key mismatch")
	assert.Equalf(t, expected.Name, actual.Name, "name mismatch")
	assert.Equalf(t, expected.Description, actual.Description, "description mismatch")
	assert.Equalf(t, expected.Currency, actual.Currency, "currency mismatch")
	assert.Equalf(t, expected.Metadata, actual.Metadata, "metadata mismatch")
	assert.Equalf(t, expected.Annotations, actual.Annotations, "annotations mismatch")
+
+	// Compare RateCards if needed
+	AssertAddonRateCardsEqual(t, expected.RateCards, actual.RateCards)
}

65-102: Improve the map creation and error messages for better readability.

The current implementation uses immediately-invoked function expressions which are unusual in Go, and the error messages for missing keys don't indicate which key is missing.

func AssertAddonRateCardsEqual(t *testing.T, r1, r2 productcatalog.RateCards) {
	t.Helper()

	assert.Equalf(t, len(r1), len(r2), "number of RateCards mismatch")

-	r1Map := func() map[string]productcatalog.RateCard {
-		m := make(map[string]productcatalog.RateCard, len(r1))
-		for _, v := range r1 {
-			m[v.Key()] = v
-		}
-
-		return m
-	}()
+	r1Map := make(map[string]productcatalog.RateCard, len(r1))
+	for _, v := range r1 {
+		r1Map[v.Key()] = v
+	}

-	r2Map := func() map[string]productcatalog.RateCard {
-		m := make(map[string]productcatalog.RateCard, len(r2))
-		for _, v := range r2 {
-			m[v.Key()] = v
-		}
-
-		return m
-	}()
+	r2Map := make(map[string]productcatalog.RateCard, len(r2))
+	for _, v := range r2 {
+		r2Map[v.Key()] = v
+	}

	visited := make(map[string]struct{})
	for phase1Key, rateCard1 := range r1Map {
		rateCard2, ok := r2Map[phase1Key]
-		require.Truef(t, ok, "missing RateCard key")
+		require.Truef(t, ok, "missing RateCard key %s in second slice", phase1Key)

		AssertRateCardEqual(t, rateCard1, rateCard2)

		visited[phase1Key] = struct{}{}
	}

	for phase2Key := range r2Map {
		_, ok := visited[phase2Key]
-		require.Truef(t, ok, "missing RateCard key")
+		require.Truef(t, ok, "missing RateCard key %s in first slice", phase2Key)
	}
}

104-155: Add default case to type switches and consider refactoring to reduce duplication.

The current implementation doesn't handle unexpected RateCard types, and duplicates the type switch logic for both rate cards.

func AssertRateCardEqual(t *testing.T, r1, r2 productcatalog.RateCard) {
	t.Helper()

	assert.Equalf(t, r1.Type(), r2.Type(), "type mismatch")

	m1 := r1.AsMeta()
	m2 := r2.AsMeta()

	assert.Equalf(t, m1.Key, m2.Key, "key mismatch")
	assert.Equalf(t, m1.Name, m2.Name, "name mismatch")
	assert.Equalf(t, lo.FromPtr(m1.Description), lo.FromPtr(m2.Description), "description mismatch")

	assert.Truef(t, m1.Metadata.Equal(m2.Metadata), "metadata mismatch")

	f1 := lo.FromPtr(m1.Feature)
	f2 := lo.FromPtr(m2.Feature)

	assert.Equalf(t, f1.Key, f2.Key, "feature key mismatch")
	assert.Equalf(t, f1.Namespace, f2.Namespace, "feature namespace mismatch")

	assert.Truef(t, m1.EntitlementTemplate.Equal(m2.EntitlementTemplate), "entitlement template mismatch")

	assert.Truef(t, m1.TaxConfig.Equal(m2.TaxConfig), "tax config mismatch")

	assert.Truef(t, m1.Price.Equal(m2.Price), "price mismatch")

-	var billingCadence1, billingCadence2 isodate.Period
-
-	switch vv := r1.(type) {
-	case *productcatalog.FlatFeeRateCard:
-		billingCadence1 = lo.FromPtr(vv.BillingCadence)
-	case *FlatFeeRateCard:
-		billingCadence1 = lo.FromPtr(vv.FlatFeeRateCard.BillingCadence)
-	case *productcatalog.UsageBasedRateCard:
-		billingCadence1 = vv.BillingCadence
-	case *UsageBasedRateCard:
-		billingCadence1 = vv.UsageBasedRateCard.BillingCadence
-	}
-
-	switch vv := r2.(type) {
-	case *productcatalog.FlatFeeRateCard:
-		billingCadence2 = lo.FromPtr(vv.BillingCadence)
-	case *FlatFeeRateCard:
-		billingCadence2 = lo.FromPtr(vv.FlatFeeRateCard.BillingCadence)
-	case *productcatalog.UsageBasedRateCard:
-		billingCadence2 = vv.BillingCadence
-	case *UsageBasedRateCard:
-		billingCadence2 = vv.UsageBasedRateCard.BillingCadence
-	}
-
-	assert.Equal(t, billingCadence1, billingCadence2)
+	// Helper function to extract billing cadence from a RateCard
+	getBillingCadence := func(r productcatalog.RateCard) (isodate.Period, bool) {
+		switch vv := r.(type) {
+		case *productcatalog.FlatFeeRateCard:
+			return lo.FromPtr(vv.BillingCadence), true
+		case *FlatFeeRateCard:
+			return lo.FromPtr(vv.FlatFeeRateCard.BillingCadence), true
+		case *productcatalog.UsageBasedRateCard:
+			return vv.BillingCadence, true
+		case *UsageBasedRateCard:
+			return vv.UsageBasedRateCard.BillingCadence, true
+		default:
+			return isodate.Period{}, false
+		}
+	}
+
+	billingCadence1, ok1 := getBillingCadence(r1)
+	billingCadence2, ok2 := getBillingCadence(r2)
+
+	require.Truef(t, ok1, "unsupported RateCard type for first argument: %T", r1)
+	require.Truef(t, ok2, "unsupported RateCard type for second argument: %T", r2)
+	assert.Equal(t, billingCadence1, billingCadence2, "billing cadence mismatch")
}
openmeter/ent/db/mutation.go (2)

29512-29565: Managing 'addon_ratecard' in FeatureMutation.

  1. The new methods AddAddonRatecardIDs, RemoveAddonRatecardIDs, and ClearAddonRatecard follow the ent mutation handling pattern.
  2. Ensure adequate test coverage to confirm correct handling of add/remove vs. clear operations.

Consider adding logging or debug statements that clarify the difference between removing edge IDs and clearing the edge entirely.


29914-29920: Handling removed 'addon_ratecard' edge references.
These lines extend the existing removal logic for the FeatureMutation, allowing for explicit removal from the relationship. This is standard ent usage.

A brief explanatory comment might help future maintainers distinguish between removing specific IDs vs. clearing the whole edge.

Also applies to: 29933-29935

openmeter/productcatalog/addon/event.go (5)

56-64: Redundant error slice usage in AddonCreateEvent.Validate().
Currently, errs is declared but never appended to. Either remove the slice or append any validation errors to it before joining.

 func (e AddonCreateEvent) Validate() error {
-	var errs []error

  if e.Addon == nil {
    return fmt.Errorf("add-on is required")
  }

-	return errors.Join(errs...)
+	return nil
 }

99-107: Redundant error slice usage in AddonUpdateEvent.Validate().
Same pattern as AddonCreateEvent.Validate().

 func (e AddonUpdateEvent) Validate() error {
-	var errs []error

  if e.Addon == nil {
    return fmt.Errorf("add-on is required")
  }

-	return errors.Join(errs...)
+	return nil
 }

142-154: Redundant error slice usage in AddonDeleteEvent.Validate().
Although this method performs additional checks on DeletedAt, it still does not use the declared errors slice.

 func (e AddonDeleteEvent) Validate() error {
-	var errs []error

  if e.Addon == nil {
    return fmt.Errorf("add-on is required")
  }
  if e.Addon.DeletedAt == nil {
    return fmt.Errorf("add-on deleted at is required")
  }

-	return errors.Join(errs...)
+	return nil
 }

189-197: Redundant error slice usage in AddonPublishEvent.Validate().
Remove unused errs or consider appending errors if needed.


232-240: Redundant error slice usage in AddonArchiveEvent.Validate().
Same feedback regarding the unused error slice.

openmeter/productcatalog/addon/httpdriver/errors.go (1)

12-16: Consider expanding error handling for addon-specific errors.

The error encoder currently only handles feature-not-found errors. As this is a new addon API, consider expanding the error handling to include addon-specific errors that might occur during API operations (e.g., addon not found, invalid addon configuration, etc.).

func errorEncoder() encoder.ErrorEncoder {
	return func(ctx context.Context, err error, w http.ResponseWriter, r *http.Request) bool {
-		return commonhttp.HandleErrorIfTypeMatches[*feature.FeatureNotFoundError](ctx, http.StatusBadRequest, err, w)
+		return commonhttp.HandleErrorIfTypeMatches[*feature.FeatureNotFoundError](ctx, http.StatusBadRequest, err, w) ||
+			commonhttp.HandleErrorIfTypeMatches[*AddonNotFoundError](ctx, http.StatusNotFound, err, w) ||
+			commonhttp.HandleErrorIfTypeMatches[*InvalidAddonConfigError](ctx, http.StatusBadRequest, err, w)
	}
}
openmeter/productcatalog/addon/httpdriver/driver.go (3)

14-16: Provide a brief interface overview.

Adding a short docstring for the Handler interface would help other developers quickly grasp the overall responsibilities and usage context.


18-26: Evaluate interface granularity.

AddonHandler has several methods, all related to add-on lifecycle operations. Consider splitting them based on operations (read vs. write) or responsibilities to adhere to Interface Segregation Principle if the interface grows further.


36-43: Improve error context in resolveNamespace.

Currently, the returned error is quite generic. Including details about missing or invalid namespaces in the error message might aid troubleshooting.

openmeter/ent/db/feature_create.go (1)

173-186: Defensive checks in AddAddonRatecard.

Within the loop extracting IDs from AddonRateCard pointers, consider gracefully handling any unexpected nil references to avoid potential runtime panics.

cmd/server/wire_gen.go (2)

161-162: Consider validations for appsConfiguration.

Before initializing appService, ensure appsConfiguration has valid data. Current error checks seem fine but defensive checks can reduce runtime surprises.


186-187: Progress manager concurrency.

Initialization is sound. Double-check concurrency handling if multiple goroutines invoke progress manager methods simultaneously.

openmeter/productcatalog/addon/errors.go (1)

19-49: Consider simplifying message building.
Using multiple if statements for constructing the error string is clear, but a strings.Builder or a small helper could streamline message assembly and avoid repeated m += .... This is only a minor style preference.

openmeter/productcatalog/addon/adapter/adapter.go (2)

19-40: Leverage extended validation if necessary.
Validate() checks only for nil references. If more nuanced checks (e.g., verifying DB connectivity) are needed, consider extending or clarifying validation.


72-83: Transactional continuity is well-handled.
WithTx() returns a new adapter instance bound to the existing transaction driver, and Self() provides a straightforward way for code to reference the current adapter. Consider adding doc comments on Self() for clarity, although it’s self-explanatory.

openmeter/productcatalog/addon/adapter/mapping.go (2)

60-61: Rename or clarify error message to reflect 'Add-on' instead of 'plan phase'.

The error message here references a "plan phase" rather than an Add-on, which can be confusing in this context. Consider updating it to more closely describe the actual entity being validated.

- return nil, fmt.Errorf("invalid plan phase %s: %w", aa.ID, err)
+ return nil, fmt.Errorf("invalid addon %s: %w", aa.ID, err)

67-146: Consider refactoring the fromAddonRateCardRow function for readability.

The function contains several distinct responsibilities, such as parsing metadata, converting billing cadence, and building multiple rate card types. Consider extracting these steps into smaller helper functions to improve readability and maintainability.

openmeter/productcatalog/addon/ratecard_test.go (1)

19-174: Add test coverage for a missing or nil Feature field.

The JSON round-trip tests currently assume that a Feature object is present. Adding a test case with a nil or absent Feature would help ensure serialization and deserialization handle this scenario correctly.

openmeter/productcatalog/addon/adapter/adapter_test.go (2)

195-196: Consider renaming references to "plan".

Within this test block, the variable name and logs reference "plan" instead of "addon", which may create confusion. Rename these references to keep the naming consistent with "addon".

-assert.NoErrorf(t, err, "getting plan by key and version must not fail")
-assert.NotNilf(t, getAddonV1, "plan must not be nil")
+assert.NoErrorf(t, err, "getting add-on by key and version must not fail")
+assert.NotNilf(t, getAddonV1, "add-on must not be nil")

361-361: Rename function for clarity.

The helper function is called testListPlanStatusFilter, but it concerns add-on status filtering. Rename it to maintain clarity and consistency.

-func testListPlanStatusFilter(ctx context.Context, t *testing.T, repo *adapter) {
+func testListAddonStatusFilter(ctx context.Context, t *testing.T, repo *adapter) {
openmeter/productcatalog/addon/httpdriver/addon.go (3)

75-78: Fix the typo in the error message.

The word "pahse" seems like a typo. Consider correcting it to "phase."

return ListAddonsResponse{}, fmt.Errorf("failed to cast add-on pahse: %w", err)
+return ListAddonsResponse{}, fmt.Errorf("failed to cast add-on phase: %w", err)

323-324: Use consistent type in JSON response encoder.

Currently, PublishAddon uses commonhttp.JSONResponseEncoderWithStatus[GetAddonResponse] instead of PublishAddonResponse (aliased to api.Addon). Consider updating for clarity and consistent usage of the response type.

-commonhttp.JSONResponseEncoderWithStatus[GetAddonResponse](http.StatusOK),
+commonhttp.JSONResponseEncoderWithStatus[PublishAddonResponse](http.StatusOK),

367-368: Align type in JSON response encoder for archiving.

Similar to the Publish handler, ArchiveAddon currently uses commonhttp.JSONResponseEncoderWithStatus[GetAddonResponse]. Switch to ArchiveAddonResponse for consistency.

-commonhttp.JSONResponseEncoderWithStatus[GetAddonResponse](http.StatusOK),
+commonhttp.JSONResponseEncoderWithStatus[ArchiveAddonResponse](http.StatusOK),
openmeter/productcatalog/addon.go (1)

153-156: Avoid repeated description checks.

The description equality check appears twice, at lines 153–156 and again at 170–172, which is redundant and could introduce confusion or inconsistent future modifications. Consolidate into a single check.

--- a/addon.go
+++ b/addon.go
@@ -152,4 +152,0 @@
-	if lo.FromPtr(m.Description) != lo.FromPtr(v.Description) {
-		return false
-	}
...
-	if lo.FromPtr(m.Description) != lo.FromPtr(v.Description) {
-		return false
-	}

Also applies to: 170-172

openmeter/productcatalog/addon/adapter/addon.go (2)

24-152: Consider splitting the query-building logic into smaller functions.

The ListAddons method merges multiple filtering paths and pagination in a single, relatively large function body. Splitting out pieces of the query-building—for instance, constructing predicates for IDs, keys, statuses, or currencies in helper functions—can make the code more modular and easier to maintain.


401-504: Recreating ratecards on update introduces a potential gap.

Deleting and recreating ratecards can briefly remove the link between the add-on and its ratecards within a transaction. Because it’s wrapped in TransactingRepo, it’s likely safe from partial updates. However, for high-traffic scenarios, consider a more granular update approach to minimize churn, if relevant.

openmeter/ent/db/feature_query.go (2)

26-37: Added withAddonRatecard field in FeatureQuery struct.

This new pointer to an AddonRateCardQuery extends the struct’s capabilities to load or filter AddonRateCard relationships. Ensure that the rest of the code consistently checks this field to avoid nil pointer dereferences when it’s not set.


359-368: Use of WithAddonRatecard for eager loading.

Providing a functional option slice to configure the subquery is flexible. This pattern is consistent with Ent’s typical approach. Keep in mind that large eager loads might impact performance if many features reference numerous addon ratecards.

openmeter/productcatalog/addon/service.go (5)

25-83: ListAddonsInput validation.

While the validation method is currently empty, consider adding checks for invalid page parameters, misformatted currency codes, or contradictory filters. This will help catch early user input errors before hitting deeper logic in your repositories.


85-95: ListAddonsStatusFilter struct is overshadowed.

Currently, ListAddonsStatusFilter is not referenced inside ListAddonsInput, and a separate Status []productcatalog.AddonStatus is used. Clarify whether both are needed, or unify them to simplify the filter approach.


122-196: UpdateAddonInput logic and partial updates.

  • The Equal method helps check if any meaningful fields have changed. This is a neat approach to skip unnecessary DB updates.
  • The Validate logic is robust, ensuring Name and effective periods are correct.
  • Consider if you need more checks for RateCards references here, or if that is delegated solely to the repository layer.

198-230: GetAddonInput gracefully handles ID or Key usage.

Requiring at least one of ID or Key is sensible. However, if a user provides both, consider clarifying which field has precedence or whether that should be disallowed. This can prevent unexpected results in ambiguous queries.


316-341: NextAddonInput for rolling versions.

This method strikes a balance: reusing the same ID or key logic as in GetAddonInput, but serving the context of “finding the next version.” Consider clarifying usage scenarios in doc comments if they differ significantly from GetAddonInput.

openmeter/ent/db/addonratecard.go (1)

69-70: Clarify edge naming for the “features” field.
The edge is labeled “features” but is mapped to a single Feature pointer, which may confuse developers into expecting multiple features. Consider renaming it to “feature” or returning a slice of Feature objects if multiples are intended.

openmeter/productcatalog/addon/service/addon.go (2)

21-21: Replace “plan” references with “add-on” for consistency.
Multiple lines still reference “plan” instead of “add-on,” causing naming inconsistencies. Renaming these references will make the code more readable and consistent with the actual domain concept.

Apply this diff to fix naming in doc strings and log messages:

--- a/original
+++ b/modified
@@ -21 +21 @@
- return pagination.PagedResponse[addon.Addon]{}, fmt.Errorf("invalid list Plans params: %w", err)
+ return pagination.PagedResponse[addon.Addon]{}, fmt.Errorf("invalid list Addons params: %w", err)

@@ -108,5 +108,5 @@
- // * check their statuses to ensure that new plan with the same key is created only
- //   if there is no version in draft status
- // * calculate the version number for the new Plan
+ // * check their statuses to ensure that new add-on with the same key is created only
+ //   if there is no version in draft status
+ // * calculate the version number for the new add-on

@@ -142 +142 @@
- // Emit plan created event
+ // Emit add-on created event

@@ -165 +165 @@
- logger.Debug("deleting Plan")
+ logger.Debug("deleting add-on")

@@ -211 +211 @@
- // Emit plan deleted event
+ // Emit add-on deleted event

@@ -265 +265 @@
- logger.Debug("updating Plan")
+ logger.Debug("updating add-on")

@@ -305 +305 @@
- // Emit plan updated event
+ // Emit add-on updated event

@@ -475 +475 @@
- return nil, fmt.Errorf("failed to publish plan archived event: %w", err)
+ return nil, fmt.Errorf("failed to publish add-on archived event: %w", err)

Also applies to: 108-112, 142-142, 165-165, 211-211, 265-265, 305-305, 475-475


108-124: Consider enforcing uniqueness for draft versions to avoid concurrency issues.
When multiple processes create new add-on versions concurrently, there is a risk of multiple draft versions being created. Consider a unique constraint or a database lock to enforce the “only one draft version” policy consistently.

openmeter/productcatalog/addon/ratecard.go (1)

23-24: Remove or update the outdated “PhaseID” comment.
The comment on line 23 refers to “PhaseID,” but the code field is named “AddonID.” Renaming or removing this comment will remove confusion.

- // PhaseID
+ // AddonID
openmeter/productcatalog/addon/httpdriver/mapping.go (2)

66-202: Consider splitting up the large rate card mapping
This function handles multiple branches for different rate card types; refactoring into smaller, focused helpers could improve readability and make future changes less error-prone.


982-1055: Clarify usagePeriod fallback logic
Falling back to billingCadence when usagePeriod is nil may lead to unexpected behavior in certain scenarios. Re-evaluate whether additional configuration or explicit validation is needed.

openmeter/ent/db/addon/addon.go (1)

85-109: Validators and Defaults Setup

The validator and default function patterns (e.g., NamespaceValidator, DefaultCreatedAt) are standard for Ent. Ensure you have relevant tests or integration checks to confirm each validator’s correctness. For example, verifying currency values or version bounds would help catch potential bad inputs during runtime.

openmeter/ent/db/addonratecard/addonratecard.go (1)

132-140: TypeValidator Enum Checks

Using an explicit check for legitimate rate card types ("flat_fee", "usage_based") helps ensure data integrity. If you plan to add new rate card types later, remember to update this validator method. This is a good place to consider test coverage for negative test cases, ensuring it rejects unknown types.

openmeter/ent/db/addonratecard_query.go (2)

111-131: Ensure proper handling of the FirstX panic path.

Methods like FirstX automatically panic on errors. While this can be convenient, consider whether you need more graceful error handling to prevent abrupt terminations in production environments.


211-235: Evaluate logging or instrumentation for bulk fetch operations.

The All and AllX methods can potentially return large result sets. Consider adding debug logging, metrics instrumentation, or rate limiting if these calls are expected to influence performance at scale.

openmeter/ent/db/addon_query.go (1)

287-309: Consider user-defined validators on WithRatecards.

Eager loading can introduce performance overhead. If you anticipate large volumes of AddonRateCard entries, consider implementing constraints and validations (e.g., limiting queries, applying pagination) to maintain system responsiveness.

openmeter/ent/db/addonratecard_update.go (1)

917-974: Assess partial updates logic for features edge.

When updating the Feature association, partial updates (i.e., clearing and adding) can lead to potential data integrity issues if multiple rate cards share a feature. Ensure you have the correct locking or transactional strategies to prevent conflicts.

openmeter/ent/db/addon_update.go (1)

218-232: Consider implementing optimistic concurrency checks.

If concurrent updates to add-ons might happen, an optimistic concurrency control (checking if version changed between read and update) could help prevent overwriting each other's changes.

openmeter/ent/db/addon_create.go (3)

223-241: Validate the addon.DefaultID() uniqueness approach.

While setting a custom default ID is fine, confirm that the generated IDs won't collide at scale. A UUID-based approach might provide safer uniqueness in high-volume scenarios.


400-421: Be mindful of database support for ON CONFLICT.

Different databases have varying levels of support for DO NOTHING and upsert logic. Confirm the chosen dialect fully supports these conflict resolution strategies.


872-961: Bulk creation is convenient, but consider partial success handling.

When using AddonCreateBulk, a single error rolls back the entire batch. If partial success is desired, you might need a different strategy (e.g., a custom transaction per entity).

openmeter/ent/db/addonratecard_create.go (1)

451-467: M2O edge handling may discard extra IDs.
Currently, if multiple Addon or Feature IDs are set (e.g., through mutation.AddonIDs() or mutation.FeaturesIDs()), only the first element is used and stored. This is standard Ent behavior for M2O edges, but it could lead to confusion if multiple IDs are inadvertently passed.

You may want to return an explicit error if more than one ID is provided for a single M2O edge to avoid silent discarding of extra IDs.

Also applies to: 468-484

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3bece3e and e2157cb.

📒 Files selected for processing (58)
  • app/common/productcatalog.go (4 hunks)
  • cmd/server/main.go (1 hunks)
  • cmd/server/wire.go (2 hunks)
  • cmd/server/wire_gen.go (14 hunks)
  • openmeter/ent/db/addon.go (1 hunks)
  • openmeter/ent/db/addon/addon.go (1 hunks)
  • openmeter/ent/db/addon/where.go (1 hunks)
  • openmeter/ent/db/addon_create.go (1 hunks)
  • openmeter/ent/db/addon_delete.go (1 hunks)
  • openmeter/ent/db/addon_query.go (1 hunks)
  • openmeter/ent/db/addon_update.go (1 hunks)
  • openmeter/ent/db/addonratecard.go (1 hunks)
  • openmeter/ent/db/addonratecard/addonratecard.go (1 hunks)
  • openmeter/ent/db/addonratecard/where.go (1 hunks)
  • openmeter/ent/db/addonratecard_create.go (1 hunks)
  • openmeter/ent/db/addonratecard_delete.go (1 hunks)
  • openmeter/ent/db/addonratecard_query.go (1 hunks)
  • openmeter/ent/db/addonratecard_update.go (1 hunks)
  • openmeter/ent/db/client.go (13 hunks)
  • openmeter/ent/db/ent.go (2 hunks)
  • openmeter/ent/db/expose.go (1 hunks)
  • openmeter/ent/db/feature.go (3 hunks)
  • openmeter/ent/db/feature/feature.go (4 hunks)
  • openmeter/ent/db/feature/where.go (1 hunks)
  • openmeter/ent/db/feature_create.go (3 hunks)
  • openmeter/ent/db/feature_query.go (8 hunks)
  • openmeter/ent/db/feature_update.go (7 hunks)
  • openmeter/ent/db/hook/hook.go (1 hunks)
  • openmeter/ent/db/migrate/schema.go (3 hunks)
  • openmeter/ent/db/mutation.go (10 hunks)
  • openmeter/ent/db/paginate.go (1 hunks)
  • openmeter/ent/db/predicate/predicate.go (1 hunks)
  • openmeter/ent/db/runtime.go (2 hunks)
  • openmeter/ent/db/setorclear.go (1 hunks)
  • openmeter/ent/db/tx.go (3 hunks)
  • openmeter/ent/schema/addon.go (1 hunks)
  • openmeter/ent/schema/feature.go (1 hunks)
  • openmeter/event/metadata/resourcepath.go (1 hunks)
  • openmeter/productcatalog/addon.go (1 hunks)
  • openmeter/productcatalog/addon/adapter/adapter.go (1 hunks)
  • openmeter/productcatalog/addon/adapter/adapter_test.go (1 hunks)
  • openmeter/productcatalog/addon/adapter/addon.go (1 hunks)
  • openmeter/productcatalog/addon/adapter/mapping.go (1 hunks)
  • openmeter/productcatalog/addon/addon.go (1 hunks)
  • openmeter/productcatalog/addon/assert.go (1 hunks)
  • openmeter/productcatalog/addon/errors.go (1 hunks)
  • openmeter/productcatalog/addon/errors_test.go (1 hunks)
  • openmeter/productcatalog/addon/event.go (1 hunks)
  • openmeter/productcatalog/addon/httpdriver/addon.go (1 hunks)
  • openmeter/productcatalog/addon/httpdriver/driver.go (1 hunks)
  • openmeter/productcatalog/addon/httpdriver/errors.go (1 hunks)
  • openmeter/productcatalog/addon/httpdriver/mapping.go (1 hunks)
  • openmeter/productcatalog/addon/ratecard.go (1 hunks)
  • openmeter/productcatalog/addon/ratecard_test.go (1 hunks)
  • openmeter/productcatalog/addon/repository.go (1 hunks)
  • openmeter/productcatalog/addon/service.go (1 hunks)
  • openmeter/productcatalog/addon/service/addon.go (1 hunks)
  • openmeter/productcatalog/addon/service/service.go (1 hunks)
🧰 Additional context used
🧬 Code Definitions (40)
openmeter/productcatalog/addon/service/service.go (3)
openmeter/productcatalog/addon/repository.go (1)
  • Repository (12-20)
openmeter/productcatalog/addon/adapter/adapter.go (2)
  • New (42-51)
  • Config (19-22)
openmeter/productcatalog/addon/service.go (1)
  • Service (35-44)
openmeter/productcatalog/addon/repository.go (3)
openmeter/productcatalog/addon/service.go (5)
  • ListAddonsInput (48-79)
  • CreateAddonInput (98-101)
  • DeleteAddonInput (231-233)
  • GetAddonInput (198-211)
  • UpdateAddonInput (122-142)
openmeter/productcatalog/addon.go (1)
  • Addon (181-186)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/ent/db/feature/where.go (3)
openmeter/ent/db/predicate/predicate.go (2)
  • Feature (120-120)
  • AddonRateCard (24-24)
openmeter/ent/db/addonratecard/addonratecard.go (1)
  • Table (59-59)
openmeter/ent/db/feature/feature.go (3)
  • Table (44-44)
  • AddonRatecardTable (60-60)
  • AddonRatecardColumn (65-65)
openmeter/ent/db/feature.go (3)
openmeter/ent/db/addonratecard.go (2)
  • AddonRateCard (21-63)
  • AddonRateCard (99-122)
openmeter/ent/db/ent.go (1)
  • NotLoadedError (316-318)
openmeter/ent/db/addonratecard_query.go (1)
  • AddonRateCardQuery (22-34)
openmeter/ent/db/ent.go (2)
openmeter/ent/db/addon/addon.go (2)
  • Table (47-47)
  • ValidColumn (76-83)
openmeter/ent/db/addonratecard/addonratecard.go (2)
  • Table (59-59)
  • ValidColumn (99-106)
openmeter/ent/db/tx.go (4)
openmeter/ent/db/predicate/predicate.go (2)
  • Addon (10-10)
  • AddonRateCard (24-24)
openmeter/ent/db/addon.go (2)
  • Addon (17-51)
  • Addon (72-91)
openmeter/ent/db/client.go (3)
  • AddonClient (511-513)
  • AddonRateCardClient (660-662)
  • NewAddonClient (516-518)
openmeter/ent/db/addonratecard.go (2)
  • AddonRateCard (21-63)
  • AddonRateCard (99-122)
openmeter/productcatalog/addon/httpdriver/driver.go (1)
openmeter/productcatalog/addon/httpdriver/addon.go (7)
  • ListAddonsHandler (28-28)
  • CreateAddonHandler (102-102)
  • DeleteAddonHandler (197-197)
  • GetAddonHandler (247-247)
  • UpdateAddonHandler (149-149)
  • PublishAddonHandler (291-291)
  • ArchiveAddonHandler (336-336)
openmeter/ent/db/expose.go (2)
openmeter/ent/schema/addon.go (10)
  • Addon (17-19)
  • Addon (21-25)
  • Addon (27-49)
  • Addon (51-58)
  • Addon (60-75)
  • AddonRateCard (77-79)
  • AddonRateCard (81-85)
  • AddonRateCard (87-139)
  • AddonRateCard (141-153)
  • AddonRateCard (155-168)
openmeter/ent/db/client.go (2)
  • NewAddonClient (516-518)
  • NewAddonRateCardClient (665-667)
openmeter/ent/db/feature_create.go (3)
openmeter/ent/db/addonratecard/where.go (1)
  • ID (16-18)
openmeter/ent/db/addonratecard/addonratecard.go (2)
  • Table (59-59)
  • FieldID (19-19)
openmeter/ent/db/feature/feature.go (3)
  • Table (44-44)
  • AddonRatecardColumn (65-65)
  • FieldID (16-16)
openmeter/ent/db/paginate.go (5)
openmeter/ent/db/addon_query.go (1)
  • AddonQuery (22-33)
openmeter/ent/db/predicate/predicate.go (2)
  • Addon (10-10)
  • AddonRateCard (24-24)
openmeter/ent/db/addon.go (2)
  • Addon (17-51)
  • Addon (72-91)
openmeter/ent/db/addonratecard_query.go (1)
  • AddonRateCardQuery (22-34)
openmeter/ent/db/addonratecard.go (2)
  • AddonRateCard (21-63)
  • AddonRateCard (99-122)
openmeter/ent/db/runtime.go (2)
openmeter/ent/db/addon.go (2)
  • Addon (17-51)
  • Addon (72-91)
openmeter/ent/db/addonratecard.go (2)
  • AddonRateCard (21-63)
  • AddonRateCard (99-122)
openmeter/productcatalog/addon/adapter/adapter.go (2)
openmeter/productcatalog/addon/service/service.go (2)
  • Config (12-17)
  • New (19-42)
openmeter/productcatalog/addon/repository.go (1)
  • Repository (12-20)
openmeter/ent/db/feature/feature.go (2)
openmeter/ent/db/addon/addon.go (1)
  • Table (47-47)
openmeter/ent/db/addonratecard/addonratecard.go (1)
  • Table (59-59)
cmd/server/wire_gen.go (5)
openmeter/productcatalog/feature/connector.go (1)
  • NewFeatureConnector (84-100)
app/common/progressmanager.go (2)
  • ProgressManager (14-16)
  • NewProgressManager (19-39)
app/common/streaming.go (1)
  • NewStreamingConnector (21-43)
app/common/entitlement.go (1)
  • NewEntitlementRegistry (22-40)
openmeter/app/service/service.go (1)
  • Service (12-15)
openmeter/productcatalog/addon/adapter/mapping.go (4)
openmeter/productcatalog/addon.go (3)
  • Addon (181-186)
  • AddonMeta (94-117)
  • AddonEffectivePeriod (38-44)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/productcatalog/ratecard.go (5)
  • RateCards (350-350)
  • RateCard (28-38)
  • RateCardMeta (49-77)
  • FlatFeeRateCard (161-168)
  • UsageBasedRateCard (256-262)
openmeter/productcatalog/addon/ratecard.go (4)
  • RateCardManagedFields (19-25)
  • FlatFeeRateCard (102-105)
  • UsageBasedRateCard (200-203)
  • ManagedRateCard (57-59)
openmeter/ent/db/hook/hook.go (1)
openmeter/ent/db/mutation.go (2)
  • AddonMutation (112-138)
  • AddonRateCardMutation (1338-1366)
openmeter/ent/schema/addon.go (4)
openmeter/ent/db/addon.go (2)
  • Addon (17-51)
  • Addon (72-91)
openmeter/ent/db/predicate/predicate.go (3)
  • Addon (10-10)
  • AddonRateCard (24-24)
  • Feature (120-120)
openmeter/ent/db/addonratecard.go (2)
  • AddonRateCard (21-63)
  • AddonRateCard (99-122)
openmeter/ent/schema/feature.go (5)
  • Feature (13-15)
  • Feature (17-23)
  • Feature (25-34)
  • Feature (36-45)
  • Feature (47-59)
openmeter/ent/db/migrate/schema.go (2)
openmeter/ent/db/addon/addon.go (2)
  • Table (47-47)
  • Columns (58-73)
openmeter/ent/db/addonratecard/addonratecard.go (2)
  • Table (59-59)
  • Columns (77-96)
openmeter/productcatalog/addon/assert.go (4)
openmeter/productcatalog/addon/service.go (2)
  • CreateAddonInput (98-101)
  • UpdateAddonInput (122-142)
openmeter/productcatalog/addon.go (1)
  • Addon (181-186)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/productcatalog/ratecard.go (4)
  • RateCards (350-350)
  • RateCard (28-38)
  • FlatFeeRateCard (161-168)
  • UsageBasedRateCard (256-262)
openmeter/productcatalog/addon/adapter/adapter_test.go (6)
openmeter/productcatalog/addon/service.go (4)
  • CreateAddonInput (98-101)
  • GetAddonInput (198-211)
  • ListAddonsInput (48-79)
  • UpdateAddonInput (122-142)
openmeter/productcatalog/addon.go (4)
  • Addon (181-186)
  • AddonMeta (94-117)
  • AddonEffectivePeriod (38-44)
  • AddonStatus (22-22)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/productcatalog/addon/ratecard.go (3)
  • UsageBasedRateCard (200-203)
  • RateCardManagedFields (19-25)
  • FlatFeeRateCard (102-105)
openmeter/productcatalog/addon/service/service_test.go (1)
  • MonthPeriod (304-304)
openmeter/productcatalog/addon/assert.go (3)
  • AssertAddonCreateInputEqual (14-26)
  • AssertAddonEqual (54-63)
  • AssertAddonUpdateInputEqual (28-52)
openmeter/ent/db/predicate/predicate.go (2)
openmeter/ent/db/addon.go (2)
  • Addon (17-51)
  • Addon (72-91)
openmeter/ent/db/addonratecard.go (2)
  • AddonRateCard (21-63)
  • AddonRateCard (99-122)
openmeter/ent/db/addon_delete.go (5)
openmeter/ent/db/ent.go (3)
  • IsConstraintError (353-359)
  • ConstraintError (337-340)
  • NotFoundError (270-272)
openmeter/ent/db/mutation.go (1)
  • AddonMutation (112-138)
openmeter/ent/db/predicate/predicate.go (1)
  • Addon (10-10)
openmeter/ent/schema/addon.go (5)
  • Addon (17-19)
  • Addon (21-25)
  • Addon (27-49)
  • Addon (51-58)
  • Addon (60-75)
openmeter/ent/db/addon/addon.go (3)
  • Table (47-47)
  • FieldID (17-17)
  • Label (15-15)
openmeter/productcatalog/addon.go (2)
openmeter/productcatalog/ratecard.go (2)
  • RateCards (350-350)
  • RateCard (28-38)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/ent/db/feature_query.go (8)
openmeter/ent/db/addonratecard/addonratecard.go (4)
  • OrderOption (143-143)
  • Table (59-59)
  • FieldID (19-19)
  • FieldFeatureID (51-51)
openmeter/ent/db/feature/feature.go (5)
  • OrderOption (111-111)
  • Table (44-44)
  • FieldID (16-16)
  • AddonRatecardTable (60-60)
  • AddonRatecardColumn (65-65)
openmeter/ent/db/predicate/predicate.go (2)
  • Feature (120-120)
  • AddonRateCard (24-24)
openmeter/ent/db/addonratecard_query.go (1)
  • AddonRateCardQuery (22-34)
openmeter/ent/db/client.go (1)
  • AddonRateCardClient (660-662)
openmeter/ent/db/addonratecard.go (2)
  • AddonRateCard (21-63)
  • AddonRateCard (99-122)
openmeter/ent/db/addonratecard/where.go (2)
  • ID (16-18)
  • FeatureID (122-124)
openmeter/ent/db/feature/where.go (1)
  • ID (14-16)
openmeter/productcatalog/addon/httpdriver/addon.go (1)
openmeter/productcatalog/addon/httpdriver/mapping.go (3)
  • FromAddon (23-62)
  • AsCreateAddonRequest (478-507)
  • AsUpdateAddonRequest (1071-1092)
openmeter/ent/db/mutation.go (1)
openmeter/ent/db/predicate/predicate.go (2)
  • Addon (10-10)
  • AddonRateCard (24-24)
openmeter/ent/db/addon.go (7)
openmeter/ent/db/addonratecard.go (2)
  • AddonRateCard (21-63)
  • AddonRateCard (99-122)
openmeter/ent/db/predicate/predicate.go (2)
  • AddonRateCard (24-24)
  • Addon (10-10)
openmeter/ent/db/ent.go (2)
  • NotLoadedError (316-318)
  • Value (60-60)
openmeter/ent/db/feature/feature.go (7)
  • FieldID (16-16)
  • FieldNamespace (26-26)
  • FieldName (28-28)
  • FieldKey (30-30)
  • FieldCreatedAt (18-18)
  • FieldUpdatedAt (20-20)
  • FieldDeletedAt (22-22)
openmeter/ent/db/addonratecard_query.go (1)
  • AddonRateCardQuery (22-34)
openmeter/ent/db/client.go (1)
  • NewAddonClient (516-518)
openmeter/ent/db/addon_update.go (1)
  • AddonUpdateOne (386-391)
openmeter/ent/db/addonratecard_delete.go (4)
openmeter/ent/db/ent.go (1)
  • Hook (59-59)
openmeter/ent/db/mutation.go (1)
  • AddonRateCardMutation (1338-1366)
openmeter/ent/db/predicate/predicate.go (1)
  • AddonRateCard (24-24)
openmeter/ent/db/addonratecard/addonratecard.go (3)
  • Table (59-59)
  • FieldID (19-19)
  • Label (17-17)
openmeter/productcatalog/addon/service/addon.go (2)
openmeter/productcatalog/addon/service.go (8)
  • ListAddonsInput (48-79)
  • CreateAddonInput (98-101)
  • OrderBy (33-33)
  • GetAddonInput (198-211)
  • UpdateAddonInput (122-142)
  • PublishAddonInput (245-250)
  • ArchiveAddonInput (288-294)
  • NextAddonInput (316-326)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/ent/db/addonratecard.go (8)
openmeter/ent/db/addonratecard/where.go (12)
  • ID (16-18)
  • Namespace (71-73)
  • CreatedAt (76-78)
  • UpdatedAt (81-83)
  • DeletedAt (86-88)
  • Name (91-93)
  • Description (96-98)
  • Key (101-103)
  • FeatureKey (106-108)
  • BillingCadence (111-114)
  • AddonID (117-119)
  • FeatureID (122-124)
openmeter/ent/db/ent.go (2)
  • NotFoundError (270-272)
  • NotLoadedError (316-318)
openmeter/ent/db/addonratecard/addonratecard.go (19)
  • Label (17-17)
  • FieldMetadata (23-23)
  • FieldID (19-19)
  • FieldNamespace (21-21)
  • FieldName (31-31)
  • FieldDescription (33-33)
  • FieldKey (35-35)
  • FieldType (37-37)
  • FieldFeatureKey (39-39)
  • FieldBillingCadence (45-45)
  • FieldAddonID (49-49)
  • FieldFeatureID (51-51)
  • FieldCreatedAt (25-25)
  • FieldUpdatedAt (27-27)
  • FieldDeletedAt (29-29)
  • FieldEntitlementTemplate (41-41)
  • FieldTaxConfig (43-43)
  • FieldPrice (47-47)
  • FieldDiscounts (53-53)
openmeter/ent/db/feature/feature.go (9)
  • Label (14-14)
  • FieldMetadata (24-24)
  • FieldID (16-16)
  • FieldNamespace (26-26)
  • FieldName (28-28)
  • FieldKey (30-30)
  • FieldCreatedAt (18-18)
  • FieldUpdatedAt (20-20)
  • FieldDeletedAt (22-22)
openmeter/ent/db/addon_query.go (1)
  • AddonQuery (22-33)
openmeter/ent/db/client.go (1)
  • NewAddonRateCardClient (665-667)
openmeter/ent/db/feature_query.go (1)
  • FeatureQuery (24-37)
openmeter/ent/db/addonratecard_update.go (1)
  • AddonRateCardUpdateOne (493-498)
openmeter/ent/db/addonratecard/addonratecard.go (3)
openmeter/ent/db/addon/addon.go (20)
  • FieldID (17-17)
  • FieldNamespace (19-19)
  • FieldMetadata (21-21)
  • FieldCreatedAt (23-23)
  • FieldUpdatedAt (25-25)
  • FieldDeletedAt (27-27)
  • FieldName (29-29)
  • FieldDescription (31-31)
  • FieldKey (33-33)
  • Columns (58-73)
  • ByID (114-116)
  • OrderOption (111-111)
  • ByNamespace (119-121)
  • ByCreatedAt (124-126)
  • ByUpdatedAt (129-131)
  • ByDeletedAt (134-136)
  • ByName (139-141)
  • ByDescription (144-146)
  • ByKey (149-151)
  • Table (47-47)
openmeter/ent/db/feature/feature.go (18)
  • FieldID (16-16)
  • FieldNamespace (26-26)
  • FieldMetadata (24-24)
  • FieldCreatedAt (18-18)
  • FieldUpdatedAt (20-20)
  • FieldDeletedAt (22-22)
  • FieldName (28-28)
  • FieldKey (30-30)
  • Columns (69-81)
  • ByID (114-116)
  • OrderOption (111-111)
  • ByNamespace (134-136)
  • ByCreatedAt (119-121)
  • ByUpdatedAt (124-126)
  • ByDeletedAt (129-131)
  • ByName (139-141)
  • ByKey (144-146)
  • Table (44-44)
openmeter/ent/db/migrate/schema.go (1)
  • FeaturesTable (1306-1330)
openmeter/ent/db/client.go (4)
openmeter/ent/db/addon/where.go (1)
  • ID (14-16)
openmeter/ent/db/addonratecard/where.go (1)
  • ID (16-18)
openmeter/ent/db/addon/addon.go (4)
  • Table (47-47)
  • FieldID (17-17)
  • RatecardsTable (49-49)
  • RatecardsColumn (54-54)
openmeter/ent/db/addonratecard/addonratecard.go (6)
  • Table (59-59)
  • FieldID (19-19)
  • AddonTable (61-61)
  • AddonColumn (66-66)
  • FeaturesTable (68-68)
  • FeaturesColumn (73-73)
openmeter/ent/db/addonratecard_query.go (4)
openmeter/ent/db/predicate/predicate.go (3)
  • AddonRateCard (24-24)
  • Addon (10-10)
  • Feature (120-120)
openmeter/ent/db/addon_query.go (1)
  • AddonQuery (22-33)
openmeter/ent/db/feature_query.go (1)
  • FeatureQuery (24-37)
openmeter/ent/db/addonratecard/where.go (5)
  • Name (91-93)
  • AddonID (117-119)
  • IDIn (31-33)
  • ID (16-18)
  • FeatureID (122-124)
openmeter/ent/db/addon_query.go (4)
openmeter/ent/db/predicate/predicate.go (2)
  • Addon (10-10)
  • AddonRateCard (24-24)
openmeter/ent/db/addonratecard_query.go (1)
  • AddonRateCardQuery (22-34)
openmeter/ent/db/client.go (1)
  • AddonRateCardClient (660-662)
openmeter/ent/db/addonratecard.go (2)
  • AddonRateCard (21-63)
  • AddonRateCard (99-122)
openmeter/ent/db/addon_update.go (3)
openmeter/ent/db/mutation.go (1)
  • AddonMutation (112-138)
openmeter/ent/db/predicate/predicate.go (2)
  • Addon (10-10)
  • AddonRateCard (24-24)
openmeter/ent/db/addonratecard.go (2)
  • AddonRateCard (21-63)
  • AddonRateCard (99-122)
openmeter/ent/db/addonratecard_update.go (3)
openmeter/ent/db/mutation.go (1)
  • AddonRateCardMutation (1338-1366)
openmeter/ent/db/addonratecard.go (2)
  • AddonRateCard (21-63)
  • AddonRateCard (99-122)
openmeter/ent/db/predicate/predicate.go (3)
  • AddonRateCard (24-24)
  • Addon (10-10)
  • Feature (120-120)
openmeter/ent/db/addon/where.go (5)
openmeter/ent/db/addonratecard/where.go (19)
  • ID (16-18)
  • IDEQ (21-23)
  • IDNEQ (26-28)
  • IDIn (31-33)
  • IDNotIn (36-38)
  • IDGT (41-43)
  • IDGTE (46-48)
  • IDLT (51-53)
  • IDLTE (56-58)
  • IDEqualFold (61-63)
  • IDContainsFold (66-68)
  • Namespace (71-73)
  • CreatedAt (76-78)
  • UpdatedAt (81-83)
  • DeletedAt (86-88)
  • Name (91-93)
  • Description (96-98)
  • Key (101-103)
  • Not (972-974)
openmeter/ent/db/predicate/predicate.go (2)
  • Addon (10-10)
  • AddonRateCard (24-24)
openmeter/ent/db/addon/addon.go (13)
  • FieldID (17-17)
  • FieldNamespace (19-19)
  • FieldCreatedAt (23-23)
  • FieldUpdatedAt (25-25)
  • FieldDeletedAt (27-27)
  • FieldName (29-29)
  • FieldDescription (31-31)
  • FieldKey (33-33)
  • FieldVersion (35-35)
  • FieldCurrency (37-37)
  • FieldEffectiveFrom (39-39)
  • FieldEffectiveTo (41-41)
  • FieldMetadata (21-21)
openmeter/ent/db/addonratecard/addonratecard.go (9)
  • FieldID (19-19)
  • FieldNamespace (21-21)
  • FieldCreatedAt (25-25)
  • FieldUpdatedAt (27-27)
  • FieldDeletedAt (29-29)
  • FieldName (31-31)
  • FieldDescription (33-33)
  • FieldKey (35-35)
  • FieldMetadata (23-23)
openmeter/ent/db/feature/feature.go (8)
  • FieldID (16-16)
  • FieldNamespace (26-26)
  • FieldCreatedAt (18-18)
  • FieldUpdatedAt (20-20)
  • FieldDeletedAt (22-22)
  • FieldName (28-28)
  • FieldKey (30-30)
  • FieldMetadata (24-24)
openmeter/ent/db/addon_create.go (3)
openmeter/ent/db/mutation.go (1)
  • AddonMutation (112-138)
openmeter/ent/db/addonratecard.go (2)
  • AddonRateCard (21-63)
  • AddonRateCard (99-122)
openmeter/ent/db/addon.go (2)
  • Addon (17-51)
  • Addon (72-91)
openmeter/ent/db/addonratecard/where.go (2)
openmeter/ent/db/predicate/predicate.go (3)
  • AddonRateCard (24-24)
  • Addon (10-10)
  • Feature (120-120)
openmeter/ent/db/addonratecard/addonratecard.go (6)
  • FieldFeatureKey (39-39)
  • FieldBillingCadence (45-45)
  • FieldAddonID (49-49)
  • FieldFeatureID (51-51)
  • FieldPrice (47-47)
  • FieldDiscounts (53-53)
openmeter/ent/db/addonratecard_create.go (2)
openmeter/ent/db/mutation.go (1)
  • AddonRateCardMutation (1338-1366)
openmeter/ent/db/addonratecard.go (2)
  • AddonRateCard (21-63)
  • AddonRateCard (99-122)
⏰ Context from checks skipped due to timeout of 90000ms (7)
  • GitHub Check: E2E
  • GitHub Check: CI
  • GitHub Check: Lint
  • GitHub Check: Developer environment
  • GitHub Check: Commit hooks
  • GitHub Check: Test
  • GitHub Check: Analyze (go)
🔇 Additional comments (172)
openmeter/ent/db/expose.go (1)

92-94: New client types for Addon and AddonRateCard properly integrated

The additions follow the established pattern in the codebase for initializing client types in a transaction context. The new fields are properly placed in alphabetical order among the existing client types.

openmeter/ent/db/tx.go (3)

17-20: Properly declared and documented client fields

The new fields for Addon and AddonRateCard are appropriately added with proper documentation comments that match the style of existing fields.


220-221: Client initialization properly added

The initialization of the Addon and AddonRateCard clients follows the established pattern and is correctly integrated in alphabetical order with the other client initializations.


265-265: Documentation example updated to reference new entity

Good practice to update the example in the documentation to reference one of the new entities (Addon) rather than leaving the old reference to App.

openmeter/ent/db/ent.go (2)

15-16: New imports for addon-related packages

The imports for the new addon-related packages are correctly added in alphabetical order.


114-115: Column validation correctly added for new entities

The column validation for the new Addon and AddonRateCard entities is properly integrated into the columnCheck map, enabling the same validation functionality that exists for other entities.

openmeter/ent/db/runtime.go (3)

8-9: Imports for addon and addonratecard look fine.

These import statements reference the newly introduced schemas. This is consistent with Ent’s generated code, and there are no apparent issues.


55-94: New fields and validators for Addon are correctly set up; proceed with caution for manual edits.

This section auto-registers validators, default values, and type scanners for the Addon schema. Everything here follows the typical Ent pattern. As this file is auto-generated, any direct modifications may be overwritten. Ensure updates happen in the schema definition if behavior changes are required. Also consider adding tests or verifying coverage for all new validators (e.g., currency, version, key) to confirm they behave as intended.


95-137: AddonRateCard schema initialization is consistent with Ent’s auto-generation.

Similar to the Addon setup, these assignments correctly register validators, default timestamps, ID generation, and type scanners for fields like EntitlementTemplate, TaxConfig, and Price. No issues detected. Confirm that the default and validator logic align with your intended business rules (e.g., verifying addon_id references). Again, any changes should be made in the schema, not here, to avoid being overwritten.

openmeter/ent/db/hook/hook.go (2)

12-22: Well implemented adapter for Addon mutation functions

The implementation follows the established pattern in the file for other entity hook adapters. The AddonFunc type and its Mutate method are correctly implemented to adapt regular functions into entity mutators, with proper type checking and error handling.


24-34: Well implemented adapter for AddonRateCard mutation functions

The implementation follows the established pattern in the file for other entity hook adapters. The AddonRateCardFunc type and its Mutate method are correctly implemented to adapt regular functions into entity mutators, with proper type checking and error handling.

openmeter/productcatalog/addon/addon.go (3)

11-21: Good structure for the Addon entity

The Addon struct is well-designed, embedding the necessary base models (NamespacedID and ManagedModel) and implementing the models.Validator interface. The struct correctly includes both the metadata fields and a relationship to rate cards.


23-43: Thorough validation implementation

The validation method properly checks all components of the addon and aggregates errors. It follows the repository's pattern for validation by checking the embedded structs and the addon-specific fields.


45-54: Properly handles deleted addons in conversion method

The AsProductCatalogAddon method correctly checks if the addon is deleted before converting it, preventing access to deleted addons. This ensures data integrity in the product catalog.

openmeter/ent/db/feature/feature.go (3)

41-65: Consistently defined constants for AddonRatecard edge

The constants for the AddonRatecard edge follow the same naming and documentation patterns as existing edges. The table and column references are properly defined to establish the relationship between Features and AddonRateCards.


186-198: Well-implemented ordering functions for AddonRatecard

The ordering functions ByAddonRatecardCount and ByAddonRatecard follow the established pattern for entity relationship ordering, creating a consistent API for querying features by their associated addon rate cards.


213-219: Properly defined step function for AddonRatecard relationship

The newAddonRatecardStep function correctly establishes the one-to-many relationship from Features to AddonRateCards, matching the existing relationship definitions in the file and ensuring proper SQL generation.

openmeter/ent/db/migrate/schema.go (6)

12-28: Ensure currency default aligns with multi-currency requirements.
The default "USD" may be suitable now, but consider whether multi-currency usage is anticipated in the product roadmap. If yes, you might want to confirm that this default doesn’t conflict with other regions or future expansions.


29-74: Verify partial index conditions for soft deletion.
These indexes, such as "addon_namespace_key_version" with deleted_at IS NULL, are common for soft-delete logic. Just confirm they operate correctly across the application code (i.e., no collisions if multiple rows share the same key once older ones are logically deleted).


75-93: Looks good!
Storing discounts and price as JSONB allows flexibility. The enum "flat_fee" | "usage_based" is clear and easy to extend if more billing types are needed in the future.


96-152: Index design and foreign keys are consistent.
All references, especially the unique constraints on [addon_id, key] and [addon_id, feature_key], appear logically sound for linking to addons and features.


2095-2097: Inclusion in the overall schema is correct.
Thanks for adding AddonsTable and AddonRateCardsTable to Tables. Ensure corresponding migration files are properly generated and applied in the correct order.


2138-2139: Foreign key references established properly.
Linking AddonRateCardsTable to AddonsTable and FeaturesTable looks coherent with the intended relationship.

openmeter/productcatalog/addon/assert.go (2)

1-12: LGTM: Appropriate imports for testing and assertions.

The imports look suitable for a test helper file, using standard testing libraries and relevant internal packages.


14-26: LGTM: Well-structured test helper with clear assertions.

The function does a good job of comparing all relevant fields between a CreateAddonInput and an Addon with descriptive error messages, and properly calls t.Helper() to improve test output.

openmeter/ent/db/mutation.go (7)

19-20: Relevant imports for Addon and AddonRateCard.
These additions look correct for referencing the new addon and addonratecard entities used in the newly introduced mutation structs.


73-74: New node type constants.
Defining "TypeAddon" and "TypeAddonRateCard" aligns with the existing pattern for ent node type constants in this project.


28867-28869: Added edge-tracking fields in Feature for 'addon_ratecard'.
These new maps and booleans for tracking add/remove/clear states of the addon_ratecard edge mirror existing patterns for feature edges.


29885-29892: Including the new 'addon_ratecard' edge in FeatureMutation’s AddedEdges.
Listing this edge ensures that ent’s internal builder handles it during mutation. This is consistent with existing logic for other Feature edges.


29955-29961: Edge clearing logic for 'addon_ratecard' in FeatureMutation.
By enumerating the new edge in ClearedEdges, the mutation properly reflects when the entire edge is cleared.

Also applies to: 29974-29976


29988-29989: Switch case for cleared 'addon_ratecard'.
Supporting the new edge in EdgeCleared ensures that ent accurately detects when the relationship is fully cleared.


30012-30014: Resetting the 'addon_ratecard' edge.
Adding m.ResetAddonRatecard() finalizes the ent mutation API for this new relationship, letting developers revert to the initial state as needed.

openmeter/productcatalog/addon/event.go (5)

23-29: No issues found for NewAddonCreateEvent.
The helper function correctly initializes the event with the Addon and user ID from context.


66-72: No issues found for NewAddonUpdateEvent.
Similar creation logic as previous events.


109-115: No issues found for NewAddonDeleteEvent.
Code is consistent with other event initializers.


156-162: No issues found for NewAddonPublishEvent.
Implementation is straightforward and matches established patterns.


199-205: No issues found for NewAddonArchiveEvent.
Implementation follows the same pattern, no concerns.

openmeter/ent/db/addon/where.go (1)

1-785: Auto-generated file.
This file appears to be generated by Ent. Manual modifications are usually discouraged. No issues found in the generated predicates.

openmeter/ent/db/addonratecard/where.go (1)

1-975: Auto-generated file.
Likewise, this file is standard Ent-generated predicate code. No issues found, and no manual edits are recommended.

cmd/server/wire.go (2)

29-29: Good addition of the addon package import.

The import for the addon package has been properly added to support the new addon service integration.


44-44: Well-structured service integration.

The Addon service field has been appropriately added to the Application struct, following the same pattern as other services in the application. This change enables dependency injection of the addon service through the wire framework.

openmeter/event/metadata/resourcepath.go (1)

20-20: Clean addition of EntityAddon constant.

The EntityAddon constant is properly added, following the same naming convention as other entity constants. This addition will allow the system to properly identify and handle addon resources in event metadata.

openmeter/ent/schema/feature.go (1)

51-51: Good relationship definition between Feature and AddonRateCard.

The edge from Feature to AddonRateCard is properly defined, establishing the necessary relationship between these entities. This will allow features to be associated with addon rate cards in the database schema.

cmd/server/main.go (1)

151-151: LGTM: Properly integrating the Addon service into the router configuration.

The addition of the Addon field to the RouterConfig structure follows the established pattern and enables the server to handle addon-related API requests, which aligns with the introduction of the new add-on API.

openmeter/ent/db/feature.go (4)

53-54: LGTM: Proper extension of the Feature entity to support addon rate cards.

The addition of the AddonRatecard field to the FeatureEdges struct creates the necessary relationship between features and addon rate cards, enabling features to reference their associated addon rate cards.


57-57: LGTM: Updated array size to accommodate the new edge.

The expansion of the loadedTypes array from size 2 to 3 is necessary to track loading status for the new edge relationship.


78-85: LGTM: Well-implemented helper method for edge access.

The AddonRatecardOrErr method follows the established pattern for edge access methods, providing proper error handling when attempting to access an unloaded edge.


209-212: LGTM: Consistent implementation of the query method.

The QueryAddonRatecard method properly delegates to the FeatureClient for handling queries on the "addon_ratecard" edge, maintaining consistency with other edge query methods in the entity.

openmeter/ent/db/feature/where.go (2)

624-633: LGTM: Well-implemented predicate for checking addon rate card relationships.

The HasAddonRatecard function correctly implements a predicate that checks for the existence of related addon rate cards, following the established pattern for edge predicates in the codebase.


635-645: LGTM: Enhanced querying capabilities with conditional filtering.

The HasAddonRatecardWith function properly extends the basic relationship check by allowing additional filter conditions on the related addon rate cards, enabling more sophisticated queries.

openmeter/ent/db/paginate.go (4)

12-55: LGTM: Well-implemented pagination support for addon queries.

The Paginate method for AddonQuery follows the established pagination pattern in the codebase, properly handling counting, offset/limit settings, and returning a consistent response structure. This enables efficient retrieval of addon data with pagination support.


58-58: LGTM: Type safety verification.

The type check ensures that AddonQuery correctly implements the pagination.Paginator interface, preventing potential compilation issues.


60-103: LGTM: Consistent pagination implementation for addon rate cards.

The Paginate method for AddonRateCardQuery properly mirrors the pagination pattern used throughout the codebase, ensuring consistent behavior across all entity types.


106-106: LGTM: Type safety verification.

The type check ensures that AddonRateCardQuery correctly implements the pagination.Paginator interface, providing compile-time guarantees.

openmeter/productcatalog/addon/service/service.go (4)

1-11: Package structure and imports look good.

The imports are well-organized and contain all the necessary dependencies for the service implementation.


12-17: Clear and well-structured configuration struct.

The Config struct properly defines all the dependencies required for the addon service, following the dependency injection pattern used elsewhere in the codebase.


19-42: Thorough validation of dependencies in the New function.

Good implementation of the factory pattern with proper validation of all required dependencies. The error messages are clear and descriptive.


44-51: Good type assertion and service struct implementation.

The var _ addon.Service = (*service)(nil) assertion ensures the service struct properly implements the interface. The service struct mirrors the Config fields, which is consistent with the approach used elsewhere.

openmeter/productcatalog/addon/errors_test.go (1)

11-47: Comprehensive test coverage for the IsNotFound error function.

Tests cover all the important scenarios: direct error, wrapped error, and invalid error cases.

openmeter/productcatalog/addon/repository.go (2)

10-10: TODO for future enhancement noted.

Good practice to leave a TODO comment for planned bulk API functionality.


12-20: Well-defined Repository interface with appropriate methods.

The Repository interface is clearly defined with all necessary CRUD operations, following the same pattern as other repositories in the codebase. The interface extends entutils.TxCreator which indicates proper transaction support.

app/common/productcatalog.go (4)

13-15: New imports added correctly for addon functionality.

The required imports for the addon module have been properly added.


26-26: Addon wire set added to ProductCatalog.

The Addon module is correctly integrated into the ProductCatalog wire set.


37-39: New Addon wire set defined.

The Addon wire set is properly defined following the same pattern as other wire sets in the file.


74-94: Well-implemented NewAddonService factory function.

The NewAddonService function follows the same pattern as other service factory functions, properly initializing the adapter and service with appropriate error handling and consistent logging configuration.

openmeter/ent/db/client.go (10)

18-19: Well-organized import additions.

The new imports for addon and addonratecard modules are properly placed with the rest of the entity imports, maintaining consistency with the existing codebase structure.


63-66: Addon clients properly integrated into Client struct.

New Addon and AddonRateCard clients are correctly added to the Client struct, following the same pattern as other entity clients.


146-147: Correct initialization of new clients.

The initialization of Addon and AddonRateCard clients in the init() method follows the established pattern for client initialization.


274-275: Proper transaction handling for new clients.

Addon and AddonRateCard clients are correctly integrated into the transaction handling methods, ensuring they work properly in transactional operations.

Also applies to: 329-330


394-404: Consistent hooks and interceptors integration.

New clients are appropriately added to the client iteration methods (Use and Intercept), ensuring that hooks and interceptors can be applied to them.

Also applies to: 414-424


433-436: Proper mutation handler integration.

The mutation handler is correctly updated to handle the new entity types, following the same pattern as existing entity types.


510-643: Well-implemented AddonClient.

The AddonClient implementation follows the same structure and patterns as other entity clients, providing all necessary CRUD operations and relationship queries.


659-822: Well-implemented AddonRateCardClient.

The AddonRateCardClient is correctly implemented with all required methods for CRUD operations and relationship queries.


709-710: hooks and interceptors structures are correctly updated.

The hooks and interceptors structures are properly updated to include the new entity types, ensuring consistency with the rest of the codebase.

Also applies to: 720-721


552-566:

Details

✅ Verification successful

Verify table references in QueryAddonRatecard implementation.

The implementation of the relationship query follows the correct pattern, but there appears to be a potential issue with table references in the underlying AddonRateCard schema definition.

Based on the relevant code snippets, AddonTable and FeaturesTable in addonratecard.go are both set to "addon_rate_cards", which seems incorrect. Typically, these should reference the tables of the related entities (addons and features respectively).


🏁 Script executed:

#!/bin/bash
# Check the table definitions in addonratecard.go and addon.go

echo "Checking table definitions in addonratecard.go:"
grep -A 5 "Table =" openmeter/ent/db/addonratecard/addonratecard.go

echo -e "\nChecking AddonTable definition in addonratecard.go:"
grep -A 5 "AddonTable =" openmeter/ent/db/addonratecard/addonratecard.go

echo -e "\nChecking FeaturesTable definition in addonratecard.go:"
grep -A 5 "FeaturesTable =" openmeter/ent/db/addonratecard/addonratecard.go

echo -e "\nChecking Table definition in addon.go:"
grep -A 5 "Table =" openmeter/ent/db/addon/addon.go

echo -e "\nChecking RatecardsTable definition in addon.go:"
grep -A 5 "RatecardsTable =" openmeter/ent/db/addon/addon.go

Length of output: 3302


Table references verified as correct.

After reviewing the definitions in addonratecard.go and addon.go, the join table schema is implementing the intended pattern: both AddonTable and FeaturesTable correctly point to "addon_rate_cards" (the join table), while the inverse table references (AddonInverseTable and FeaturesInverseTable) properly point to the related entity tables ("addons" and "features" respectively). No changes to the QueryAddonRatecard implementation are necessary.

openmeter/productcatalog/addon/httpdriver/driver.go (1)

45-55: Factory construction looks good.

The New factory function is well-structured, returning a clearly initialized handler. The optional HandlerOption slice is a nice extension point for future enhancements.

openmeter/ent/db/feature_create.go (2)

15-15: Import aligns with AddonRateCard logic.

The new addonratecard import is correct for referencing the new entity, ensuring proper edge handling for add-on rate cards.


377-392:

Details

✅ Verification successful

Validate AddonRateCard edge constraints.

Appending the addon_ratecard edge spec is consistent. Verify that no additional uniqueness constraints are necessary, especially if multiple features can share the same rate card or vice versa.


🏁 Script executed:

#!/bin/bash
# Check for unique or foreign-key constraints in your schema definitions
rg -A 5 'addon_rate_cards' 

Length of output: 4973


AddonRateCard Edge Constraints Validated

The schema searches confirm that for the addon_rate_cards table, the only uniqueness constraint is on the primary key (the “id” column). The foreign key set on the “feature_id” column (used in the edge spec) does not enforce extra uniqueness, which is consistent with a one-to-many design where a feature can have multiple addon rate cards. Please confirm that this relationship behavior matches the intended domain design.

cmd/server/wire_gen.go (9)

26-26: New addon import.

Importing the add-on package here is aligned with the new add-on service integration.


90-98: Ensure cleanup on meter adapter failures.

The error handling block systematically cleans up deferred resources. This pattern helps prevent resource leaks on partial initialization failures.


150-151: Initialize feature connector and add-on service in order.

featureConnector must be ready before creating the addonService if the latter depends on it. The current approach appears correct, but keep an eye on cyclical dependencies.


172-173: Tracer initialization.

Defining a new tracer is consistent. Confirm that all modules requiring tracing have access to this instance.


197-197: Streaming connector initialization.

Looks good. If initialization partially succeeds, confirm that leftover resources are still released.


207-207: Check entitlement usage.

entitlement registry creation is correct. Ensure it’s consistently referenced wherever entitlements are enforced.


313-314: Sandbox provisioner setup.

Proper error handling and cleanup sequence. This approach helps prevent inconsistent states if provisioning fails mid-process.


367-367: Meter event service creation.

Constructing metereventService with connector and service is coherent with the existing architecture. No issues found.


424-425: Updated Application struct.

The added Addon and App fields neatly integrate new services into the broader application. Verify that they're utilized in the appropriate endpoints or handlers.

openmeter/productcatalog/addon/errors.go (3)

12-17: Ensure parameters are optional or validated as needed.
NotFoundErrorParams accommodates multiple optional fields (Namespace, ID, Key, Version). If these fields are mandatory in certain scenarios, consider adding validation or documentation to clarify usage.


53-63: Error composition is well-structured.
NotFoundError neatly wraps a models.GenericNotFoundError, ensuring compatibility with existing error-handling mechanisms. This is a solid error type design.


65-72: Robust error check with errors.As.
IsNotFound correctly leverages type assertion via errors.As, simplifying checks for this specific error type.

openmeter/productcatalog/addon/adapter/adapter.go (2)

42-51: Configuration injection is clear.
New(config Config) properly validates inputs and returns an adapter implementing addon.Repository. No issues found.


61-70: Transaction management is consistent.
Tx() appropriately hijacks and wraps the Ent transaction logic. Error handling is sound, returning contextual information if the transaction fails.

openmeter/ent/schema/addon.go (7)

17-25: Consistent schema definition with mixins.
Addon inherits UniqueResourceMixin for namespaced uniqueness. This approach is aligned with common Ent patterns.


27-49: Field definitions are appropriate.
Each field (like version with Min(1), currency as immutable, etc.) carefully enforces domain constraints. The JSONB-backed annotations field is also set up cleanly for Postgres.


60-75: Efficient indexing strategy.
Using a unique index on (namespace, key, version) combined with a partial index (deleted_at IS NULL) is a robust design. The GIN index on annotations effectively supports flexible querying.


77-85: Second entity Mixin usage.
AddonRateCard similarly employs the UniqueResourceMixin, ensuring consistent handling of resource identity across both entities.


87-139: Flexible, extensible fields.
Fields like entitlement_template, tax_config, and price are convertible to JSONB, offering flexibility for a wide range of add-on rate card data.


141-153: Clear relationship definitions.
Edges from AddonRateCard to Addon (Ref("ratecards")) and to Feature (Ref("addon_ratecard")) are well-specified.


155-168: Appropriate composite indexing.
Defining combined unique constraints on (addon_id, key) and (addon_id, feature_key) ensures logical uniqueness for rate cards, again filtered by deleted_at IS NULL.

openmeter/ent/db/addon_delete.go (1)

1-89: Auto-generated file by Ent
No manual modifications are recommended. If you need to alter its functionality, consider adjusting the Ent schema and re-generating the code.

openmeter/ent/db/addonratecard_delete.go (11)

1-4: Initialization and package declaration look good.
All standard boilerplate code generated by Ent. No concerns here.


5-13: Imports are appropriate.
Libraries from entgo.io/ent and local packages are imported correctly.


15-20: Struct definition is aligned with Ent's conventions.
The AddonRateCardDelete struct and its fields follow established patterns for delete builders in Ent.


22-26: Where() method usage is straightforward.
Appending predicates to the mutation is standard Ent usage.


28-31: Exec() method correctness confirmed.
It delegates execution to the withHooks wrapper, maintaining Ent’s hook lifecycle. No issues found.


33-40: ExecX() maintains the conventional “panic on error” approach.
This is consistent with Ent’s standard “X” methods. No concerns.


42-57: sqlExec() properly handles constraint errors.
The usage of sqlgraph.DeleteNodes and wrapping in ConstraintError is aligned with Ent’s recommended approach.


59-62: AddonRateCardDeleteOne struct mirrors the multi-entity variant.
Good structure for single-record deletes.


64-68: Where() method for single deletion also looks correct.
Reuses the same approach to append predicates.


70-81: Exec() handles the “not found” scenario with a custom error.
Returning NotFoundError if no record is deleted is consistent with Ent patterns.


83-88: ExecX() for AddonRateCardDeleteOne is consistent with standard naming.
Again, relies on panic for errors, matching other Ent code.

openmeter/ent/db/addon.go (11)

16-50: Definition of the Addon entity is well-structured.
All fields, including nested JSON fields (Metadata, Annotations), are handled with clear types.


53-60: AddonEdges struct properly marks loaded types.
This helps distinguish whether the related edges have been eagerly loaded.


62-69: RatecardsOrErr() edge-check is a standard approach for eager-loaded data.
By returning NotLoadedError, it cleanly differentiates between “empty slice” and “not loaded.”


71-91: scanValues() sets up field scanning types efficiently.
All field cases—strings, JSON, times—are covered. No issues noted.


93-196: assignValues() carefully unmarshals JSON fields.
Good error handling for unexpected types. This method accurately updates the struct’s fields.


198-202: Value() retrieves selected fields or modifiers.
This method ensures dynamic select queries are properly handled.


204-207: QueryRatecards() method returns a dedicated query builder.
Preserves Ent’s recommended pattern for chaining queries on edges.


209-214: Update() method returns the proper update builder.
This approach is consistent with typical Ent patterns.


216-225: Unwrap() logic ensures transactional consistency.
It correctly reverts driver references to the original driver if the entity came from a transaction.


227-280: String() method provides useful debugging info.
This is a clear format for logging entity fields, including optional fields.


282-284: Addons type alias is a handy list-like convenience.
No concerns.

openmeter/ent/db/feature_update.go (7)

14-14: New import for the addonratecard package is necessary and valid.
This is required for referencing the added “addon_ratecard” edge.


148-161: AddAddonRatecardIDs() and AddAddonRatecard() integrate add-on rate cards into Feature updates.
These methods parallel existing edge-management patterns for other related entities, enabling easy linking of AddonRateCard records.


210-229: ClearAddonRatecard() & RemoveAddonRatecard() methods ensure clean removal flows.
They align with the existing pattern for clearing and removing edges, maintaining consistency.


412-456: Updating sqlSave() with the “addon_ratecard” edge.
The logic for clearing and adding edges follows Ent’s typical O2M pattern. No obvious issues found.


591-604: AddAddonRatecardIDs() and AddAddonRatecard() for FeatureUpdateOne.
Mirrors the multi-entity version, preserving consistency in the single-entity update builder.


653-672: ClearAddonRatecard() and RemoveAddonRatecard() in the single-entity context.
Straightforward edge-clearing logic, consistent with Ent’s “XOne” builder approach.


885-929: FeatureUpdateOne.sqlSave() correctly wires in the AddonRatecard edges.
Edge definitions (cleared, removed, added) match the existing logic for other edges.

openmeter/productcatalog/addon/adapter/adapter_test.go (1)

305-325: Verify post-deletion expectations.

This test block verifies that an add-on can still be retrieved after deletion, suggesting soft-delete semantics. If the expected behavior is to deny retrieval of a deleted record, consider revisiting the logic and updating the test to reflect a “not found” scenario instead.

Do you expect the add-on to remain retrievable after deletion? If not, please confirm the correct post-deletion behavior by checking for a “not found” error.

openmeter/productcatalog/addon/adapter/addon.go (4)

218-253: Validate references for rate cards, if applicable.

The rateCardBulkCreate function effectively creates multiple rate cards in bulk. Make sure that any feature references (e.g., FeatureID) or AddonID foreign keys are validated or enforced by the schema to prevent orphaned or invalid references.


255-300: Marking the add-on as deleted is well-handled.

The soft-delete approach using a deletedAt field is straightforward. By returning an error if the add-on doesn't exist, you’re ensuring consistent state. This deletion flow looks good.


302-399: Check edge cases in retrieving the latest version.

The GetAddon method includes logic for returning the addon’s latest version if Version == 0 and IncludeLatest is set, or an "active" version otherwise. This branching can grow complicated if more states are introduced. Keep a watch for new status categories or reworks that might break these conditions.


506-511: Confirm if you should include ratecards marked for future deletion.

AddonEagerLoadRateCardsFn includes ratecards not yet past their DeletedAt times. This is efficient for normal usage. However, if archived or future-dated items should be excluded from some contexts, be sure to handle that logic upstream or adjust this function.

openmeter/ent/db/feature_query.go (5)

16-16: New import for AddonRateCard.

The addition of addonratecard ensures that the FeatureQuery can now reference AddonRateCard entities. This import aligns with the new edges and query methods introduced below.


114-134: New method QueryAddonRatecard().

This method parallels existing relationships (like QueryRatecard) and avoids confusion by using a dedicated edge. The logic to build a join step with sqlgraph.NewStep is consistent, and error handling looks straightforward.


445-452: Tracking loaded types with an index for the new field.

Adding the third boolean for withAddonRatecard effectively signals the presence of an eager-loaded relation. This approach is aligned with how Ent maintains edges for O2M or M2M relationships.


489-495: Conditional loadAddonRatecard check.

The if-statement ensures that fq.withAddonRatecard is not nil before loading the relationship. Make sure that your data remains consistent by verifying that all references (i.e., foreign keys in AddonRateCard) point to valid features.


562-594: loadAddonRatecard function.

The function populates an AddonRatecard edge in each queried Feature. The approach of storing references in nodeIDs and appending to the correct Feature entity in memory is a standard Ent pattern. The error checking for nil FeatureID is especially helpful for diagnosing data mismatches.

openmeter/productcatalog/addon/service.go (5)

1-19: Clear package structure and constants.

Defining the package addon and referencing standard time and error functionalities sets the stage well. The named constants (timeJitter, sorting orders) are suitably placed for broad usage across the file.


96-116: CreateAddonInput merges namespaced model with an add-on.

Bundling models.NamespacedModel and productcatalog.Addon is straightforward. The combined validation ensures users cannot skip either entity’s constraints. The approach is suitable for a single-step creation flow.


231-243: DeleteAddonInput requires a valid NamespacedID.

The soft validation is enough here. Because the actual deletion is performed by your repository, the function remains succinct and focused.


245-286: PublishAddonInput ensures future activation windows.

You’re requiring EffectiveFrom to be non-past, with jitter. This is helpful for preventing accidental backdating. Keep an eye out for time zone issues or user confusion around the “past” threshold if you have global usage.


288-314: ArchiveAddonInput ensures future deactivation windows.

Similar to PublishAddonInput, requiring a non-past EffectiveTo prevents immediate retroactive changes. This is consistent with any usage flow that disallows backdated archiving.

openmeter/productcatalog/addon/httpdriver/mapping.go (4)

23-62: Clean and thorough status handling
This function validates add-on statuses meticulously and returns an error for unknown statuses, improving reliability and clarity. Good job.


204-321: Thorough coverage of usage-based price types
This switch statement comprehensively addresses all price variants. The error handling is consistent and clear.


478-507: Add validations for required fields
Consider validating mandatory fields (e.g., key, name, and rate cards) to avoid creating partial or inconsistent add-ons.


700-743: Check discount precision and boundaries
Since discount quantities and percentages are parsed from decimal strings, ensure there’s no risk of negative or excessive values through proper validation.

openmeter/ent/db/setorclear.go (1)

16-238: Overall Consistency & Test Coverage for 'SetOrClear' Pattern

All of these functions follow the same conditional pattern: if the pointer argument is nil, the corresponding Clear<Field> method is called; otherwise, the relevant Set<Field> method is used with the dereferenced value. This is a common and appropriate approach for optional fields in Ent-generated code.

No obvious correctness, concurrency, or security issues appear. However, ensure you have adequate test coverage for the newly introduced methods (both Update and UpdateOne) so that each field’s set-or-clear behavior is validated, preventing future regressions.

Would you like to run a quick check searching for test references to these new methods? I can generate a script to gauge coverage if needed.

openmeter/ent/db/addon/addon.go (2)

13-83: Consistent Definition of 'Addon' Fields and Columns

The constants and columns appear aligned with the schema definitions (e.g., FieldID, FieldNamespace, etc.). The usage of Columns for validation and listing is standard Ent practice. No issues are apparent with naming or structure, and there are no concurrency or security concerns at this level. Good job keeping them clearly organized.


110-198: Ordering Helpers for Querying 'Addon'

Providing ordering options (e.g., ByID, ByNamespace, ByEffectiveFrom) is a clean, extensible approach for building dynamic queries. The usage of OrderOption is idiomatic in Ent. Just confirm that any client code (e.g., your service layer) leveraging these ordering helpers passes valid fields, especially for the neighbor-based orderings like ByRatecards.

openmeter/ent/db/addonratecard/addonratecard.go (3)

1-106: Definition of 'AddonRateCard' Fields & Columns

The top-level constants mirror those in the addon package but specialized for rate cards. The standard Columns slice and the ValidColumn function look correct and maintainable. Nice job ensuring consistency with how columns are enumerated in other Ent entities.


108-130: Validators, Defaults, and Custom Scanners

Defining field-specific validators (e.g., AddonIDValidator, TypeValidator) alongside default ID generators is a best practice. The ValueScanner struct for typed fields is beneficial for domain-level safety. All method signatures and naming are consistent with Ent’s generated approach.


142-257: Ordering Options & Relationship Steps

The ordering methods (ByID, ByNamespace, etc.) and the neighbor-based ordering (e.g., ByAddonField, ByFeaturesField) appear correct. The use of sqlgraph.OrderByNeighborTerms is consistent with Ent’s recommended patterns for advanced ordering across edges. Everything here should integrate smoothly with any query builders in your code.

openmeter/ent/db/addonratecard_query.go (1)

67-87: Confirm correct edge relationship for QueryAddon.

This method constructs an M2O edge from AddonRateCard to Addon. Ensure that the database schema and the Ent schema definition accurately reflect the intended relationship (i.e., each AddonRateCard belongs to exactly one Addon).

openmeter/ent/db/addon_query.go (2)

66-86: Validate edge cardinality for QueryRatecards.

This method indicates a one-to-many edge (sqlgraph.O2M), suggesting an Addon can have multiple AddonRateCard entries. Confirm that the corresponding Ent schema matches this design to avoid unexpected query results.


88-108: Confirm usage of panic in FirstX and FirstIDX.

Similar to other Ent-generated code, these methods panic on errors. Ensure this aligns with your team’s error-handling conventions and that you have adequate coverage or fallback in non-test environments.

openmeter/ent/db/addonratecard_update.go (2)

308-331: Review uniqueness constraint when clearing the Addon edge.

Clearing a required unique edge (Addon) throws an error if new nodes are also being added. Double-check business logic to ensure whether removing an existing Addon assignment without replacement is allowable in your domain model.


422-451: Verify M2O edge specification for Addon updates.

The code replaces or clears the add-on relationship by directly manipulating the foreign key. Confirm that the domain strictly requires a single Addon per AddonRateCard. If multiple associations are possible, reevaluate the relationship type.

openmeter/ent/db/addon_update.go (4)

19-24: AddonUpdate struct definition looks clear and aligned with Ent conventions.

This section provides a well-structured foundation for handling addon record updates. No major concerns here.


177-216: Confirm the One-to-Many relationship for ratecards.

These methods to add/remove ratecards suggest a one-to-many relationship. If you intended a many-to-many association, ensure the schema is accurately modeled, because Ent is generating an O2M approach here.


247-262: Validation and default logic are solid.

Setting default timestamps and validating the version field helps safeguard against malformed updates.


264-383: sqlSave method usage is consistent with Ent best practices.

Leveraging sqlgraph.UpdateNodes and systematically handling each cleared or set field ensures proper partial updates.

openmeter/ent/db/addon_create.go (4)

19-25: AddonCreate struct is neatly defined.

The fields, hooks, and conflict options form a standard Ent builder pattern for creating addon records.


243-287: Field validation is thorough.

All required fields (namespace, name, key, version, currency, etc.) are enforced, reducing the chance of inconsistent data.


289-313: sqlSave method is consistent with Ent best practices.

This method invokes createSpec and gracefully handles constraint errors, aligning well with Ent’s generated patterns.


315-397: createSpec logic is straightforward and well-organized.

Fields are assigned to _node properly, and the newly created entity structure is ready for database insertion.

openmeter/ent/db/addonratecard_create.go (5)

1-2: Auto-generated notice.
This file is a product of the Ent code generator. Generally, you should avoid manual edits to generated files, as changes will be overwritten by subsequent schema regenerations.


259-273: Sensible default assignments.
The default assignment of CreatedAt, UpdatedAt, and ID fields ensures proper timestamps and IDs when these fields are omitted. This pattern is consistent with best practices and helps maintain data integrity.


275-337: Effective field validation checks.
All required fields/edges (e.g., namespace, key, type, addon_id, and the addon edge) are validated before save. This ensures that invalid or incomplete data cannot be persisted. Nicely handled.


339-363: Standard node creation flow.
The sqlSave method correctly orchestrates the creation of the new node, including final ID assignment and constraint error handling. No concurrency or blocking issues identified.


803-1569: Bulk creation and upsert logic.
The bulk creation flow properly chains mutations and concludes with a single batch insert. The upsert configuration with optional conflict resolution is also well-structured and consistent with Ent patterns.

@chrisgacsal chrisgacsal force-pushed the feat/addons-api branch 2 times, most recently from 18b405f to 2ec8639 Compare April 4, 2025 10:50
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: 3

🧹 Nitpick comments (25)
openmeter/productcatalog/addon/ratecard_test.go (3)

19-174: Test coverage is solid; consider stabilizing time usage.

The JSON serialization tests for both FlatFee and UsageBased RateCards appear thorough, covering serialization and roundtrip deserialization. However, using time.Now() inline might introduce small timing discrepancies between serialization and deserialization in certain environments. Consider freezing or mocking time values to avoid potential flakiness.


176-315: Tests for FlatFeeRateCard are adequate.

The table-driven tests validate both valid/invalid scenarios. You might consider marking sub-tests with t.Parallel() to speed up test execution where feasible, but otherwise these tests look solid.


318-474: Usage-based tests mirror the flat fee structure well.

The parallel approach for valid and invalid usage-based rate cards is clear. Similar to the FlatFee tests, consider using t.Parallel() for sub-tests if concurrency is safe.

openmeter/productcatalog/addon/adapter/adapter_test.go (5)

65-67: Fix grammar in error message comment

There's a grammatical issue in the error message. The comment is missing a verb.

-			t.Errorf("failed to postgres driver: %v", err)
+			t.Errorf("failed to close postgres driver: %v", err)

312-312: Fix typo in error message

There's a small typo in the error message - it says "ad-on" instead of "add-on".

-			require.NoErrorf(t, err, "deleting ad-on must not fail")
+			require.NoErrorf(t, err, "deleting add-on must not fail")

361-362: Rename function to correctly reference add-ons

The function name still references "Plan" rather than "Addon", which appears to be a copy-paste error.

-func testListPlanStatusFilter(ctx context.Context, t *testing.T, repo *adapter) {
+func testListAddonStatusFilter(ctx context.Context, t *testing.T, repo *adapter) {

327-329: Update function call after renaming

Update the function call to match the renamed function.

-		t.Run("ListStatusFilter", func(t *testing.T) {
-			testListPlanStatusFilter(ctx, t, addonRepo)
-		})
+		t.Run("ListStatusFilter", func(t *testing.T) {
+			testListAddonStatusFilter(ctx, t, addonRepo)
+		})

364-364: Update variable name for consistency

The namespace variable name should be updated to be consistent with the function's purpose.

-	ns := "list-plan-status-filter"
+	ns := "list-addon-status-filter"
openmeter/productcatalog/addon/httpdriver/addon.go (3)

31-97: Typo fix in error message and optional page parameters usage.

  1. There's a minor typographical error at line 77: "pahse" → "phase".
  2. Consider verifying that using notification.DefaultPageSize and notification.DefaultPageNumber is acceptable and does not create an unintended dependency. If decoupling is desired, you might introduce pagination defaults directly in this package.

Possible fix for the typo:

- return ListAddonsResponse{}, fmt.Errorf("failed to cast add-on pahse: %w", err)
+ return ListAddonsResponse{}, fmt.Errorf("failed to cast add-on phase: %w", err)

288-331: Enhance clarity by aligning response type naming and address the TODO.

  1. The handler currently returns PublishAddonResponse but uses commonhttp.JSONResponseEncoderWithStatus[GetAddonResponse]. Even though both are api.Addon, consider renaming the generic parameter to match for consistency.
  2. Address the TODO comment on lines 302–303 if you need to support setting EffectivePeriod from the API request.

Would you like help in updating the TypeSpec definition for full EffectivePeriod support?


333-374: Handle TODO for EffectivePeriod.To.

Similarly to the PublishAddon endpoint, there's a TODO in lines 347–348 about supporting EffectivePeriod settings. Finalizing that will help unify the approach.

Do you want me to open an issue or draft a follow-up PR to address the effective period handling?

openmeter/productcatalog/addon.go (2)

37-44: Fix or remove the FIXME to unify effective period logic.

The comment hints at a plan to merge AddonEffectivePeriod with a broader EffectivePeriod structure. If you plan to keep them separate, consider removing or clarifying the FIXME.


54-81: Environment-based time usage note.

Status and StatusAt rely on time.Now(). For improved testability, consider using a clock interface (similar to other parts of the code) instead of directly calling time.Now().

openmeter/productcatalog/addon/service.go (1)

176-196: FIXME note on ratecards validation.

There's a FIXME at line 193 referencing missing ratecard validation. If partial updates can include new or modified ratecards, consider addressing the incomplete validation to prevent inconsistent states.

Would you like me to generate a potential approach for partial ratecard validation?

openmeter/productcatalog/addon/service/addon.go (7)

18-28: Double-check error message text naming.
In line 21, the error message references "invalid list Plans params" while the function is named ListAddons. Consider aligning the wording to avoid confusion and improve consistency.


80-151: Inconsistency in success messages vs. entity name.
Logging statements and error strings ("plan created event") seem to reference "plan" instead of "add-on" (e.g., line 144: "failed to publish plan created event"). Consider renaming to ensure consistent naming throughout the logic and logs.


153-222: Rename logging for clarity.
Line 165 logs "deleting Plan", but all references and function names revolve around "add-on." Consider updating for consistency and clarity.


191-191: Resolve the "FIXME" to return the deleted add-on.
You have a // FIXME: make delete endpoint to return deleted addon comment. If needed, I can help implement the returning logic and relevant tests.


224-252: Handle potential not-found scenario.
Line 242 has a // FIXME: not found error. If the GetAddon call returns a not-found error, consider returning a typed error (e.g., 404) to provide better context for downstream consumers.


254-317: Adjust "plan" references in logs for naming consistency.
Similar to the Create and Delete methods, line 265 logs "updating Plan" even though the function deals with add-ons. Aligning the terminology will avoid confusion.


302-302: Correct typo in error message.
Typo in "failed to udpate aa-on" makes logs less clear. Replace with "failed to update add-on".

- return nil, fmt.Errorf("failed to udpate aa-on: %w", err)
+ return nil, fmt.Errorf("failed to update add-on: %w", err)
openmeter/productcatalog/addon/ratecard.go (1)

100-196: Optional enhancement to unify repeated logic.
The FlatFeeRateCard struct has repetitive patterns (e.g., in Marshal/Unmarshal) similar to UsageBasedRateCard. Consider extracting shared code to reduce duplication.

openmeter/productcatalog/addon/event.go (3)

46-47: Validate Addon reference before composing resource path.

Each EventMetadata() method assumes e.Addon is non-nil when calling metadata.ComposeResourcePath(...). Although there's a Validate() method, if it's never invoked, a nil Addon here would cause a runtime panic. Consider adding a protective check if there's any chance the caller might skip validation before populating metadata.

Also applies to: 89-90, 132-133, 179-180, 222-223


57-64: Remove or utilize the unused errs aggregator.

In each Validate() method, you create an errs slice but never append to it, so errors.Join(errs...) is effectively nil when e.Addon is present or immediately returning an error when absent. Either remove the slice or consistently aggregate all validation errors to leverage errors.Join.

Also applies to: 100-107, 143-154, 190-197, 233-240


14-21: Consider extracting event version into a shared constant.

The "v1" string literal is repeated across all event definitions. For maintainability, define a single constant (e.g. const addonEventVersion = "v1") and reference it in each EventName() call to avoid version mismatches in the future.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e2157cb and 2ec8639.

📒 Files selected for processing (25)
  • app/common/productcatalog.go (4 hunks)
  • cmd/server/main.go (1 hunks)
  • cmd/server/wire.go (2 hunks)
  • cmd/server/wire_gen.go (14 hunks)
  • openmeter/event/metadata/resourcepath.go (1 hunks)
  • openmeter/productcatalog/addon.go (1 hunks)
  • openmeter/productcatalog/addon/adapter/adapter.go (1 hunks)
  • openmeter/productcatalog/addon/adapter/adapter_test.go (1 hunks)
  • openmeter/productcatalog/addon/adapter/addon.go (1 hunks)
  • openmeter/productcatalog/addon/adapter/mapping.go (1 hunks)
  • openmeter/productcatalog/addon/addon.go (1 hunks)
  • openmeter/productcatalog/addon/assert.go (1 hunks)
  • openmeter/productcatalog/addon/errors.go (1 hunks)
  • openmeter/productcatalog/addon/errors_test.go (1 hunks)
  • openmeter/productcatalog/addon/event.go (1 hunks)
  • openmeter/productcatalog/addon/httpdriver/addon.go (1 hunks)
  • openmeter/productcatalog/addon/httpdriver/driver.go (1 hunks)
  • openmeter/productcatalog/addon/httpdriver/errors.go (1 hunks)
  • openmeter/productcatalog/addon/httpdriver/mapping.go (1 hunks)
  • openmeter/productcatalog/addon/ratecard.go (1 hunks)
  • openmeter/productcatalog/addon/ratecard_test.go (1 hunks)
  • openmeter/productcatalog/addon/repository.go (1 hunks)
  • openmeter/productcatalog/addon/service.go (1 hunks)
  • openmeter/productcatalog/addon/service/addon.go (1 hunks)
  • openmeter/productcatalog/addon/service/service.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
  • openmeter/event/metadata/resourcepath.go
  • openmeter/productcatalog/addon/httpdriver/errors.go
  • cmd/server/main.go
  • cmd/server/wire.go
  • openmeter/productcatalog/addon/httpdriver/driver.go
  • openmeter/productcatalog/addon/repository.go
  • openmeter/productcatalog/addon/errors.go
  • openmeter/productcatalog/addon/adapter/adapter.go
  • openmeter/productcatalog/addon/assert.go
🧰 Additional context used
🧬 Code Definitions (9)
openmeter/productcatalog/addon/addon.go (2)
openmeter/productcatalog/addon.go (2)
  • Addon (181-186)
  • AddonMeta (94-117)
openmeter/productcatalog/ratecard.go (1)
  • RateCards (350-350)
openmeter/productcatalog/addon/errors_test.go (1)
openmeter/productcatalog/addon/errors.go (3)
  • NewNotFoundError (19-49)
  • NotFoundErrorParams (12-17)
  • IsNotFound (65-72)
openmeter/productcatalog/addon/ratecard_test.go (3)
openmeter/productcatalog/addon/ratecard.go (4)
  • FlatFeeRateCard (102-105)
  • RateCardManagedFields (19-25)
  • UsageBasedRateCard (200-203)
  • NewRateCardFrom (61-98)
openmeter/productcatalog/addon/adapter/adapter.go (1)
  • Config (19-22)
openmeter/productcatalog/addon/service/service.go (1)
  • Config (12-17)
openmeter/productcatalog/addon/adapter/adapter_test.go (6)
openmeter/productcatalog/addon/service.go (5)
  • CreateAddonInput (98-101)
  • GetAddonInput (198-211)
  • ListAddonsInput (48-79)
  • UpdateAddonInput (122-142)
  • DeleteAddonInput (231-233)
openmeter/productcatalog/addon.go (4)
  • Addon (181-186)
  • AddonMeta (94-117)
  • AddonEffectivePeriod (38-44)
  • AddonStatus (22-22)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/productcatalog/addon/ratecard.go (3)
  • UsageBasedRateCard (200-203)
  • RateCardManagedFields (19-25)
  • FlatFeeRateCard (102-105)
openmeter/productcatalog/addon/service/service_test.go (1)
  • MonthPeriod (303-303)
openmeter/productcatalog/addon/assert.go (3)
  • AssertAddonCreateInputEqual (14-26)
  • AssertAddonEqual (54-63)
  • AssertAddonUpdateInputEqual (28-52)
cmd/server/wire_gen.go (10)
openmeter/productcatalog/feature/connector.go (1)
  • NewFeatureConnector (84-100)
app/common/streaming.go (1)
  • NewStreamingConnector (21-43)
app/common/entitlement.go (1)
  • NewEntitlementRegistry (22-40)
openmeter/app/stripe/service.go (2)
  • BillingService (44-50)
  • Service (11-16)
openmeter/app/sandbox/app.go (1)
  • App (35-39)
openmeter/billing/service.go (1)
  • Service (9-19)
openmeter/customer/service.go (1)
  • Service (10-13)
openmeter/app/service/service.go (1)
  • Service (12-15)
openmeter/meter/service/service.go (1)
  • Service (13-15)
openmeter/progressmanager/service.go (1)
  • Service (9-11)
openmeter/productcatalog/addon/httpdriver/addon.go (1)
openmeter/productcatalog/addon/httpdriver/mapping.go (3)
  • FromAddon (23-62)
  • AsCreateAddonRequest (478-507)
  • AsUpdateAddonRequest (1071-1092)
openmeter/productcatalog/addon.go (2)
openmeter/productcatalog/ratecard.go (2)
  • RateCards (350-350)
  • RateCard (28-38)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/productcatalog/addon/service/addon.go (3)
openmeter/productcatalog/addon/service.go (8)
  • ListAddonsInput (48-79)
  • CreateAddonInput (98-101)
  • OrderBy (33-33)
  • DeleteAddonInput (231-233)
  • GetAddonInput (198-211)
  • UpdateAddonInput (122-142)
  • PublishAddonInput (245-250)
  • ArchiveAddonInput (288-294)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/productcatalog/addon/event.go (5)
  • NewAddonCreateEvent (24-29)
  • NewAddonDeleteEvent (110-115)
  • NewAddonUpdateEvent (67-72)
  • NewAddonPublishEvent (157-162)
  • NewAddonArchiveEvent (200-205)
openmeter/productcatalog/addon/event.go (3)
openmeter/productcatalog/addon.go (1)
  • Addon (181-186)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/event/metadata/resourcepath.go (2)
  • ComposeResourcePath (28-30)
  • EntityAddon (20-20)
⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: Test
  • GitHub Check: Quickstart
  • GitHub Check: Lint
  • GitHub Check: E2E
  • GitHub Check: Developer environment
  • GitHub Check: CI
  • GitHub Check: Build
  • GitHub Check: Commit hooks
  • GitHub Check: Analyze (go)
🔇 Additional comments (76)
openmeter/productcatalog/addon/addon.go (3)

1-21: Struct definition and fields look good.

No issues found in the package declaration, imports, and primary Addon struct definition. The embeddings (NamespacedID, ManagedModel, AddonMeta) and the RateCards field are well organized.


23-43: Validation logic is comprehensive.

The Validate method correctly aggregates validation errors from embedded structs and returns a joined error. Implementation is clear and consistent with typical Go validation patterns.


45-54: Conversion method is well-structured.

The AsProductCatalogAddon method properly checks the DeletedAt field to handle deleted add-ons. The returned object is correctly constructed. No major concerns here.

openmeter/productcatalog/addon/adapter/addon.go (6)

24-152: Potential empty OR filter leads to zero results.

When no IDs, Keys, KeyVersions, or Currencies are specified, the orFilters slice is empty, which might cause the query to return no rows. Verify whether this behavior is intentional or if a fallback to fetch all add-ons is desired.


154-216: Reminder: enforce composite uniqueness.

Creation logic is straightforward. However, as previously noted, consider ensuring a composite uniqueness constraint (e.g., (namespace, key, version)) in your schema to prevent duplicates when multiple concurrent calls create the same add-on.


218-253: Bulk creation of rate cards is straightforward.

The helper function correctly maps RateCard objects to database entities and appends them to a bulk create list. No issues spotted.


255-300: Soft-deletion flow is well-handled.

The DeleteAddon method marks add-ons as deleted by setting DeletedAt. The logic to handle already-deleted or non-existent add-ons is clear.


302-399: Check subselect correctness for fetching latest version.

The subselect logic for retrieving the latest add-on version by (namespace, key) is advanced but looks plausible. However, please confirm it aligns with your design requirements, especially for use cases where multiple versions exist.


506-511: Rate card eager loading is clean.

Filtering out future-deleted rate cards is a sensible approach. No comments on concurrency or performance concerns.

openmeter/productcatalog/addon/adapter/adapter_test.go (4)

320-324: Verify soft deletion behavior

After calling DeleteAddon, the test verifies the add-on can still be retrieved and has the same data, suggesting a soft deletion approach. Ensure this aligns with the intended behavior for handling deleted add-ons.


38-53: Well-structured test data setup

The addon input is well-structured with appropriate test data including metadata, annotations, and currency. The nested structure follows the domain model correctly.


89-147: Comprehensive test setup with proper test data

The test setup is thorough, creating prerequisite data (feature) before testing add-on operations. The rate card configuration is detailed and covers various pricing options including tiers, commitments, and tax configurations.


148-155: Good validation in Create test

The Create test properly validates the creation operation and uses the helper function to assert equality between input and created entity.

app/common/productcatalog.go (4)

13-15: Well-organized imports for add-on functionality

The imports are properly organized, bringing in the necessary packages for the add-on functionality.


23-27: Good integration into existing wire setup

The Addon wire set is correctly integrated into the ProductCatalog wire set.


37-39: Clean wire set declaration

The Addon wire set is declared consistently with other wire sets in the file.


74-94: Well-implemented service initialization function

The NewAddonService function follows the same pattern as other service initialization functions in the file. It properly configures the adapter, handles errors, and sets up appropriate logging with subsystem context.

openmeter/productcatalog/addon/errors_test.go (3)

11-47: Comprehensive test cases for IsNotFound function

The test cases for the IsNotFound function are comprehensive, covering valid NotFoundError, wrapped NotFoundError, and invalid error scenarios.


49-94: Complete test coverage for error message formatting

The test cases verify error message formatting with different combinations of parameters (ID, Key, Version), ensuring the error messages are correctly constructed.


61-62: Correctly updated error messages

Error messages now properly reference "add-on" instead of "plan", addressing the issue highlighted in previous reviews.

openmeter/productcatalog/addon/service/service.go (4)

12-17: Well-structured configuration struct

The Config struct is well-structured with all necessary dependencies for the service.


19-42: Thorough validation in service initialization

The New function properly validates all required dependencies and returns appropriate errors if any are missing.


44-44: Good practice using type assertion

Using a type assertion to verify that the service implements the addon.Service interface is a good practice to catch interface implementation issues at compile time.


46-51: Clean service struct implementation

The service struct is cleanly implemented with all the necessary dependencies.

cmd/server/wire_gen.go (14)

26-26: Add-on import statement
The added import for the addon package is correct and aligns with the subsequent usage of addonService.


90-98: Initialize meter adapter with cleanup
The new code snippet properly creates adapter via common.NewMeterAdapter and follows established cleanup patterns for error handling.


150-151: Feature and Add-on services
Initializing the new featureConnector and addonService looks consistent and correctly references common.NewAddonService.


161-162: App configuration and service
Fetching appsConfiguration from conf and creating appService maintains consistency with other service initializations.


172-174: Tracer and configuration references
These lines properly set up the tracer and retrieve entitlementsConfiguration and aggregationConfiguration, matching the project's pattern.


186-187: Progress manager service
Defining progressManagerConfiguration and initializing the progressmanagerService fits seamlessly into the existing setup.


197-198: Streaming connector creation
Implementing common.NewStreamingConnector with progressmanagerService and handling errors is consistent with the inject-and-cleanup strategy.


207-208: Entitlement and customer services
Introducing entitlement followed by customerService is structurally correct and leverages the new streaming connector.


261-262: Billing service injection
Using common.BillingService with all relevant dependencies (e.g., appService, customerService, featureConnector) is well-structured.


271-272: AppStripe service initialization
Incorporating the newly created secretserviceService into common.NewAppStripeService ensures secure data handling.


313-314: App sandbox provisioner
The new appSandboxProvisioner integrates seamlessly, following the defined error handling flow.


367-368: Meter event service
Creating metereventService with connector and service ensures event-driven operations are properly linked.


424-425: Embed new services in Application
Assigning addonService and appService to the Application struct fields keeps the dependency references centralized.


475-475: New Addon field in Application struct
Declaring Addon addon.Service aligns with its usage at line 424 for maintaining the add-on service reference.

openmeter/productcatalog/addon/adapter/mapping.go (4)

1-14: Package and imports
All imported packages appear necessary for the mapping logic and align with the file’s functionality.


16-65: Function: fromAddonRow
This function neatly converts the entdb.Addon entity into an addon.Addon model, with thorough validation and graceful handling of rate cards.


67-146: Function: fromAddonRateCardRow
The logic for constructing RateCard from entdb.AddonRateCard is well-structured, uses explicit type checks, and ensures billing cadence is validated.


148-189: Function: asAddonRateCardRow
This reverse mapping to entdb.AddonRateCard covers all rate card types via a clean type switch, with proper error handling for unsupported variants.

openmeter/productcatalog/addon/httpdriver/addon.go (4)

99-144: Looks good for add-on creation flow.

The CreateAddon function properly decodes and validates the request body, then delegates work to the service layer. No issues found.


146-192: Partial update approach looks solid.

The UpdateAddon function consistently applies partial updates (pointers) and delegates them to the service. Appropriate error handling is in place.


194-230: Delete handler is clear and concise.

The handler correctly constructs the input, calls the service, and returns a 204 status upon successful deletion.


232-286: Namespace and ID/Key resolution logic is clear.

The GetAddon handler properly interprets ID or key inputs, factoring in the version and the IncludeLatest flag. Implementation is straightforward.

openmeter/productcatalog/addon.go (5)

46-52: Valid range checks look appropriate.

The Validate method for AddonEffectivePeriod correctly flags invalid date ordering, returning AddonStatusInvalid when EffectiveTo is before EffectiveFrom.


83-88: Equality check is straightforward.

The Equal method properly compares both EffectiveFrom and EffectiveTo for equality.


119-139: Validation logic is comprehensive.

The AddonMeta.Validate() method handles key, name, and currency requirements, and also delegates to AddonEffectivePeriod.Validate().


188-213: Rate card validation logic is sound.

The loop checks for duplicate keys and individual validation errors. Ensure that referencing a different namespace in rate cards is intentionally permitted or further restricted if needed.


215-221: Addon equality check is neatly delegated.

Combining metadata and rate cards for an equality check is straightforward. Good partitioning of responsibilities between AddonMeta.Equal and RateCards.Equal.

openmeter/productcatalog/addon/service.go (10)

35-44: Service interface design is well structured.

The methods comprehensively cover the add-on lifecycle (CRUD + publish/archive). This lays a good foundation for future expansion.


46-84: ListAddonsInput is flexible and well-defined.

No immediate issues. The filters and pagination fields look consistent with typical usage scenarios.


85-95: No concerns on status filter struct.

The ListAddonsStatusFilter is straightforward and does not introduce any apparent pitfalls.


98-115: Creation input effectively reuses Addon validation.

CreateAddonInput properly composes NamespacedModel and Addon, delegating to each for thorough checks.


122-174: UpdateAddonInput: partial update approach is well-designed.

Pointers to optional fields (Name, Description, etc.) allow incremental updates. Equality logic is clear.


198-229: GetAddonInput validation is correct.

The input requires either Namespace + ID or Namespace + Key, enforcing that at least one reference is provided.


231-243: Deletion input is straightforward.

The required NamespacedID prevents accidental deletion outside a valid namespace.


245-286: Time-based publish validations are robust.

Checks ensure EffectiveFrom is in the future (within timeJitter tolerance). This prevents users from publishing an add-on in the past.


288-314: ArchiveAddonInput has analogous validations.

EffectiveTo must be set and not too far past. The approach is consistent with PublishAddonInput.


316-340: NextAddonInput design is aligned with the rest of the handlers.

The required fields mirror those in GetAddonInput, ensuring either ID or Key is present.

openmeter/productcatalog/addon/service/addon.go (4)

30-78: Validate concurrency usage of in-place feature expansion.
The expandFeatures method updates rate cards in-place, which may have concurrency implications if multiple goroutines call this function on shared data. Verify that usage patterns of this method do not introduce data-race conditions.


319-421: Consider verifying alignment across multiple rate cards.
Currently, if any card is misaligned, publishing is blocked. Ensure this logic is well-tested with mixed card scenarios to avoid partial publishes.


423-482: Good separation of archiving logic.
The steps to retrieve, validate, update, and publish an event for the archived add-on appear consistent and maintainable.


484-602: Prevent duplication of draft versions.
Lines 555-559 already check for an existing draft. This logic is critical to maintain version integrity. Confirm test coverage for corner cases, such as concurrent calls creating multiple drafts.

openmeter/productcatalog/addon/ratecard.go (4)

19-55: Validation approach looks good.
The RateCardManagedFields.Validate() method adequately checks required fields. No issues detected.


57-59: Interface naming is consistent.
ManagedRateCard is straightforward and aligns with the code’s usage.


61-98: Robust deserialization logic.
NewRateCardFrom cleanly differentiates between raw JSON, FlatFeeRateCard, and UsageBasedRateCard. The typed approach helps avoid confusion during runtime.


198-295: UsageBasedRateCard implementation appears consistent.
Methods for JSON handling, equality checks, and merging align well with the FlatFeeRateCard pattern.

openmeter/productcatalog/addon/httpdriver/mapping.go (7)

64-64: [Duplicate from prior feedback] Address the "FIXME" consolidation.
This echoes a past review comment suggesting that HTTP mappers be centralized to reduce duplication.


23-62: Clear transformation logic in FromAddon.
The function systematically maps core fields and gracefully handles invalid statuses. Implementation detail looks solid.


204-321: Good layering in FromRateCardUsageBasedPrice.
Each price type is neatly handled with custom error messages, making debugging easier. This is a well-structured approach.


478-507: Comprehensive input validation in AsCreateAddonRequest.
Covers both currency code validation and rate card conversion, preventing malformed requests from proceeding.


745-939: Sufficient error handling for complex price conversions.
AsPrice thoroughly deals with each price type. The error messages are descriptive and should help troubleshoot issues quickly.


982-1055: UsagePeriod fallback logic is helpful.
By defaulting UsagePeriod to billingCadence if not specified, you reduce boilerplate for most use cases. Be sure to test corner scenarios.


1071-1092: Conditional update of RateCards is well-implemented.
AsUpdateAddonRequest gracefully handles the scenario where RateCards may or may not be present in the payload.

@chrisgacsal chrisgacsal force-pushed the feat/addons-api branch 2 times, most recently from ef4e225 to 371a487 Compare April 4, 2025 11:06
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.

Caution

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 5

♻️ Duplicate comments (1)
openmeter/productcatalog/addon.go (1)

154-156: 🛠️ Refactor suggestion

Remove duplicate comparison of m.Description.

The equality check for m.Description appears at lines 154-156 and again at lines 170-172, causing redundant logic. Removing one of these checks would streamline the Equal method and avoid confusion.

154-156 (keeping):
 if lo.FromPtr(m.Description) != lo.FromPtr(v.Description) {
   return false
 }
-if lo.FromPtr(m.Description) != lo.FromPtr(v.Description) {
-  return false
-}

Also applies to: 170-172

🧹 Nitpick comments (38)
openmeter/productcatalog/addon/assert.go (2)

54-63: Consider adding RateCards comparison to AssertAddonEqual

While this function compares the basic fields of two Addon objects, it doesn't compare the RateCards collection, unlike the other assertion functions. Consider adding RateCards comparison for complete equality verification.

 func AssertAddonEqual(t *testing.T, expected, actual Addon) {
 	t.Helper()
 
 	assert.Equalf(t, expected.Key, actual.Key, "key mismatch")
 	assert.Equalf(t, expected.Name, actual.Name, "name mismatch")
 	assert.Equalf(t, expected.Description, actual.Description, "description mismatch")
 	assert.Equalf(t, expected.Currency, actual.Currency, "currency mismatch")
 	assert.Equalf(t, expected.Metadata, actual.Metadata, "metadata mismatch")
 	assert.Equalf(t, expected.Annotations, actual.Annotations, "annotations mismatch")
+	
+	// Compare RateCards
+	AssertAddonRateCardsEqual(t, expected.RateCards, actual.RateCards)
 }

104-155: Consider refactoring the type-switching logic for billing cadence extraction

The current implementation uses type switching twice (once for each RateCard) to extract the billing cadence. This could be simplified if the RateCard interface had a method to directly access the billing cadence, reducing duplicate code and making future type additions easier to maintain.

You could create a helper function to extract the billing cadence:

+// getBillingCadence extracts billing cadence from different RateCard types
+func getBillingCadence(r productcatalog.RateCard) isodate.Period {
+	switch vv := r.(type) {
+	case *productcatalog.FlatFeeRateCard:
+		return lo.FromPtr(vv.BillingCadence)
+	case *FlatFeeRateCard:
+		return lo.FromPtr(vv.FlatFeeRateCard.BillingCadence)
+	case *productcatalog.UsageBasedRateCard:
+		return vv.BillingCadence
+	case *UsageBasedRateCard:
+		return vv.UsageBasedRateCard.BillingCadence
+	}
+	return isodate.Period{}
+}

 func AssertRateCardEqual(t *testing.T, r1, r2 productcatalog.RateCard) {
 	// ...existing code...
 
-	var billingCadence1, billingCadence2 isodate.Period
-
-	switch vv := r1.(type) {
-	case *productcatalog.FlatFeeRateCard:
-		billingCadence1 = lo.FromPtr(vv.BillingCadence)
-	case *FlatFeeRateCard:
-		billingCadence1 = lo.FromPtr(vv.FlatFeeRateCard.BillingCadence)
-	case *productcatalog.UsageBasedRateCard:
-		billingCadence1 = vv.BillingCadence
-	case *UsageBasedRateCard:
-		billingCadence1 = vv.UsageBasedRateCard.BillingCadence
-	}
-
-	switch vv := r2.(type) {
-	case *productcatalog.FlatFeeRateCard:
-		billingCadence2 = lo.FromPtr(vv.BillingCadence)
-	case *FlatFeeRateCard:
-		billingCadence2 = lo.FromPtr(vv.FlatFeeRateCard.BillingCadence)
-	case *productcatalog.UsageBasedRateCard:
-		billingCadence2 = vv.BillingCadence
-	case *UsageBasedRateCard:
-		billingCadence2 = vv.UsageBasedRateCard.BillingCadence
-	}
+	billingCadence1 := getBillingCadence(r1)
+	billingCadence2 := getBillingCadence(r2)
 
 	assert.Equal(t, billingCadence1, billingCadence2)
 }
openmeter/productcatalog/addon.go (2)

37-37: Address the FIXME comment.

There's a FIXME at line 37 suggesting a consolidation of AddonEffectivePeriod with EffectivePeriod. If there's a plan to unify or refactor these classes, performing it soon can help avoid duplicated logic and maintain consistency across the codebase.


54-54: Update docstring reference to reflect "Addon" instead of "Plan".

The docstring for Status at line 54 mentions "Plan", but the function is defined on AddonEffectivePeriod. Consider renaming it to avoid confusion.

-// Status returns the current status of the Plan
+// Status returns the current status of the Addon
openmeter/productcatalog/addon/httpdriver/mapping.go (1)

64-64: Plan next steps for consolidation.

A FIXME at line 64 notes the possibility of consolidating HTTP mappers into the productcatalog package. Consider addressing this soon to minimize duplication and keep the codebase easier to maintain.

openmeter/productcatalog/addon/event.go (1)

56-64: Streamline error handling.

Each Validate() method uses a var errs []error but ultimately checks only one or two conditions before returning. If there are no further validations to accumulate in errs, consider removing the slice and returning errors immediately to simplify the code.

 func (e AddonCreateEvent) Validate() error {
-	var errs []error

 	if e.Addon == nil {
 		return fmt.Errorf("add-on is required")
 	}
-	return errors.Join(errs...)
+	return nil
 }

Also applies to: 99-107, 142-154, 189-197, 232-240

openmeter/productcatalog/addon/adapter/adapter.go (1)

17-22: Configuration struct is straightforward.

Fields for the PostgreSQL client and logger are cleanly defined. Consider adding doc comments describing intended usage for future maintainability.

openmeter/productcatalog/addon/adapter/mapping.go (2)

16-65: fromAddonRow function.

  • Properly populates the addon.Addon fields from the Ent row.
  • Calls aa.Validate() at the end (line 60) to ensure correctness.
  • Minor naming mismatch in the error message at line 61: it says “invalid plan phase” when referencing add-ons. Consider updating for clarity.
- return nil, fmt.Errorf("invalid plan phase %s: %w", aa.ID, err)
+ return nil, fmt.Errorf("invalid add-on %s: %w", aa.ID, err)

67-146: fromAddonRateCardRow function.

  • Nicely consolidates metadata, feature references, and billing cadence.
  • Switch-case on r.Type builds the correct rate card variant.
  • On line 138: returning an error with %w includes err, but err may be nil in that context. Suggest constructing a fresh error or removing the %w.
- return nil, fmt.Errorf("invalid AddonRateCard type %s: %w", r.Type, err)
+ return nil, fmt.Errorf("invalid AddonRateCard type %s", r.Type)
openmeter/productcatalog/addon/ratecard_test.go (1)

19-174: Consider freezing times used in tests for predictability
Using time.Now() within tests can lead to flaky outcomes due to system time variance. Consider using fixed timestamps or a time-freezing library to ensure consistent, reproducible test runs.

openmeter/ent/db/addonratecard_delete.go (1)

15-57: Caution with ExecX panic
Auto-generated ent code typically provides ExecX methods for convenience, but be mindful that it panics on error. Consider using Exec when a controlled error return is desired.

openmeter/productcatalog/addon/adapter/adapter_test.go (2)

240-303: Consider testing partial rate card updates.

The update test replaces existing rate cards entirely. If partial updates or incremental changes are anticipated, adding specialized tests would help ensure correctness under those scenarios.


305-325: Validate exclusion of soft-deleted add-ons when IncludeDeleted is false.

Although the delete test confirms that the add-on remains retrievable post-deletion (soft-delete), it's worth confirming that the same add-on does not appear in list results when IncludeDeleted = false. Consider adding a test case to validate the intended behavior.

openmeter/productcatalog/addon/httpdriver/addon.go (4)

75-77: Fix the typo in the error message.

The error message has a minor spelling mistake: "pahse" should be "phase."

-	return ListAddonsResponse{}, fmt.Errorf("failed to cast add-on pahse: %w", err)
+	return ListAddonsResponse{}, fmt.Errorf("failed to cast add-on phase: %w", err)

304-304: Address the TODO for EffectivePeriod.

There's a placeholder indicating that the PublishAddonRequest doesn't yet allow fully setting the EffectivePeriod. If this is needed soon, consider implementing or tracking in a new issue.


349-349: Follow up on the TODO for setting EffectivePeriod.To.

Similarly, there's another placeholder for archiving that relies on a future enhancement to the API schema. Make sure to handle this before release to avoid runtime confusion.


324-324: Use matching response type for PublishAddon.

This handler returns PublishAddonResponse but uses GetAddonResponse in the response encoder. Although both map to api.Addon, aligning the types helps avoid confusion.

-	commonhttp.JSONResponseEncoderWithStatus[GetAddonResponse](http.StatusOK),
+	commonhttp.JSONResponseEncoderWithStatus[PublishAddonResponse](http.StatusOK),
openmeter/productcatalog/addon/service.go (2)

81-83: Empty validation method for ListAddonsInput.

Currently, Validate() returns nil. If future constraints are needed (e.g., page bounds), consider adding them here.


122-196: UpdateAddonInput: handle “FIXME: validate ratecards”.

There’s a comment noting a missing validation step. Ensure it’s implemented when ratecards are fully integrated.

openmeter/ent/db/addonratecard.go (2)

65-70: Clarify the naming for the Features edge.
Despite being named "features," this edge is typed as a single *Feature. If multiple features are expected, you may want to rename it to Feature or switch the field type to a slice.


124-251: Be cautious with large or sensitive data stored in metadata.
metadata is stored as a JSON object. If it can include sensitive/PII or large data, consider implementing validations, encryption, or alternative storage.

Want help creating an approach to sanitize or encrypt metadata before saving?

openmeter/ent/db/mutation.go (1)

111-2839: Comprehensive mutation logic introduced for Addon and AddonRateCard entities.

  • The generated mutation methods (e.g., setters, getters, clearers) follow the standard ent pattern.
  • Concurrency aspects: the sync.Once usage for old-value retrieval is a common ent pattern, but ensure that concurrent updates rely on transactions to prevent stale reads or lost updates if multiple threads mutate the same entity at once.
  • Validation: consider adding hooks or constraints if certain fields (e.g., namespace, version, or deleted_at) require specific conditions or if certain states should not coexist (e.g., disallow clearing deleted_at while an entity is active, etc.).
  • Testing: ensure thorough unit tests cover the new edges (ratecards) for addition, removal, clearing, and updating, especially around partial or simultaneous updates.

Everything else aligns well with ent’s recommended approach.

openmeter/productcatalog/addon/service/addon.go (9)

21-21: Use consistent naming in error message.
The string "invalid list Plans params" should be adjusted to "invalid list Addons params" for consistency with the ListAddons function.

- return pagination.PagedResponse[addon.Addon]{}, fmt.Errorf("invalid list Plans params: %w", err)
+ return pagination.PagedResponse[addon.Addon]{}, fmt.Errorf("invalid list Addons params: %w", err)

139-141: Inconsistent log comment ("plan" vs "add-on").
Line 141 says “Emit plan created event” while line 139 logs "add-on created." Use “add-on” for both references to keep naming consistent.


165-165: Rename log message to “deleting add-on.”
Currently, the log says "deleting Plan" but this code deals with add-ons.

- logger.Debug("deleting Plan")
+ logger.Debug("deleting add-on")

199-199: Fix comment reference for deleted entity.
This comment references “deleted Plan,” but it should read “deleted add-on” for consistency.

- // Get the deleted Plan to emit the event
+ // Get the deleted add-on to emit the event

210-210: Inconsistent comment referencing “plan” instead of “add-on.”
Use “add-on” here for consistency with the rest of the code.

- // Emit plan deleted event
+ // Emit add-on deleted event

242-242: Graceful handling of not found errors.
There's a FIXME comment to handle a “not found” error properly. You can use the IsNotFound function from errors.go to return a more specific error.

Do you want me to open a new issue for refining the error handling here?


300-300: Typographical error.
Change "failed to udpate aa-on" to "failed to update add-on".

- return nil, fmt.Errorf("failed to udpate aa-on: %w", err)
+ return nil, fmt.Errorf("failed to update add-on: %w", err)

304-304: Inconsistent log message referencing “Plan.”
Use “add-on” to match the resource being updated.

- logger.Debug("Plan updated")
+ logger.Debug("add-on updated")

361-361: Minor grammar improvement.
Replace “then” with “than” to improve clarity.

- // the add-on to be published has higher version then 1
+ // the add-on to be published has higher version than 1
openmeter/productcatalog/addon/ratecard.go (2)

169-176: Call RateCardManagedFields.Validate() for comprehensive validation.
Currently, FlatFeeRateCard.Validate() invokes r.FlatFeeRateCard.Validate() but does not validate r.RateCardManagedFields. Consider also calling r.RateCardManagedFields.Validate() to catch empty namespace, ID, or add-on ID.

func (r *FlatFeeRateCard) Validate() error {
    var errs []error

+   if err := r.RateCardManagedFields.Validate(); err != nil {
+       errs = append(errs, err)
+   }

    if err := r.FlatFeeRateCard.Validate(); err != nil {
        errs = append(errs, err)
    }

    return models.NewNillableGenericValidationError(errors.Join(errs...))
}

268-276: Call RateCardManagedFields.Validate() for usage-based rate cards.
Similarly, include validation of r.RateCardManagedFields inside UsageBasedRateCard.Validate() so the namespace, ID, and add-on ID are verified.

func (r *UsageBasedRateCard) Validate() error {
    var errs []error

+   if err := r.RateCardManagedFields.Validate(); err != nil {
+       errs = append(errs, err)
+   }

    if err := r.UsageBasedRateCard.Validate(); err != nil {
        errs = append(errs, err)
    }

    return models.NewNillableGenericValidationError(errors.Join(errs...))
}
openmeter/ent/db/addon_update.go (1)

254-262: Consider extending validations in check() for additional fields if needed.
Currently, only the version field is strictly validated. For completeness, you might add boundary checks or structured constraints for other fields (e.g., name, effective_from, effective_to). This can prevent unexpected data states and enhance reliability.

openmeter/ent/db/addonratecard_update.go (1)

308-327: Validate completeness of field-level checks.
The check() method appropriately validates fields like entitlement_template, tax_config, price, and addon_id. If additional constraints or business rules (e.g., name or feature key constraints) exist, consider adding them here to centralize validation.

openmeter/ent/db/addonratecard_create.go (3)

22-29: Ensure any additional builder logic is added in custom code rather than the generated builder.
This struct (AddonRateCardCreate) is automatically generated by Ent. If you need complex or domain-specific validations or transformations before inserting records, consider configuring them either in custom templates or in your own helper functions instead of editing this generated file.

Would you like me to open a new issue or provide an example of how to incorporate custom logic in Ent code generation templates?


202-206: Avoid accidental divergence between SetAddon and SetAddonID.
The SetAddon(... *Addon) method simply delegates to SetAddonID(a.ID). If there's any logic required when linking an existing Addon, ensure that both calling patterns remain in sync. Otherwise, consumers of these methods may inadvertently bypass future checks if one method is modified.

You might reduce the potential for confusion by removing one of the methods or by adding clarifying documentation about their intended usage.


365-486: Efficient specification assembly but consider large JSON field implications.
The Metadata, EntitlementTemplate, TaxConfig, Price, and Discounts fields are likely stored in JSON columns. If these objects grow large or need partial querying, you might consider additional indexing or a more structured schema. Otherwise, performance is fine for smaller payloads.

If you expect these fields to be large or frequently queried by sub-field, consider using a dedicated table or a specialized search index for filtering.

🛑 Comments failed to post (5)
openmeter/productcatalog/addon/httpdriver/mapping.go (1)

548-556: ⚠️ Potential issue

Correct erroneous error message.

When casting a UsageBasedRateCard in the AsRateCard function (lines 548–556), the error string still references FlatFeeRateCard, which is misleading and likely a copy-paste leftover. Update it to refer to UsageBasedRateCard.

- if err != nil {
-   return nil, fmt.Errorf("failed to cast FlatFeeRateCard: %w", err)
- }
+ if err != nil {
+   return nil, fmt.Errorf("failed to cast UsageBasedRateCard: %w", err)
+ }
📝 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.

		usage, err := r.AsRateCardUsageBased()
		if err != nil {
			return nil, fmt.Errorf("failed to cast FlatFeeRateCard: %w", err)
		}

		usageBasedRateCard, err := AsUsageBasedRateCard(usage)
		if err != nil {
			return nil, fmt.Errorf("failed to cast UsageBasedRateCard: %w", err)
		}
openmeter/productcatalog/addon/adapter/addon.go (2)

55-55: 🛠️ Refactor suggestion

Handle empty orFilters to avoid unintended filtering behavior.

If no filter conditions (IDs, Keys, KeyVersions, Currencies) are specified, orFilters remains empty, causing query.Where(addondb.Or(orFilters...)) to evaluate to an empty predicate. This might result in a no-op or unexpected behavior. Consider skipping the Or(...) call when orFilters is empty, or adding a default true-filter if intended.


113-114: ⚠️ Potential issue

Use the correct ordering predicate for OrderByKey.

Currently, the OrderByKey case applies addondb.ByVersion(order...) instead of ordering by Key. This appears to be a logical error. Update the query to use addondb.ByKey(order...) for the OrderByKey case.

-case addon.OrderByKey:
-    query = query.Order(addondb.ByVersion(order...))
+case addon.OrderByKey:
+    query = query.Order(addondb.ByKey(order...))
📝 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.

		case addon.OrderByKey:
			query = query.Order(addondb.ByKey(order...))
openmeter/ent/db/setorclear.go (1)

156-161: 💡 Verification agent

🧩 Analysis chain

Potential nil-pointer dereference in double-pointer fields
Methods handling **productcatalog.EntitlementTemplate, **productcatalog.TaxConfig, **productcatalog.Price, and **productcatalog.Discounts risk a nil-pointer panic if value != nil but *value == nil. Consider adding an extra check to handle that edge case safely, for example:

 if value == nil || *value == nil {
-  return u.ClearEntitlementTemplate()
+  // Ensures we gracefully handle a pointer-to-nil scenario
  return u.ClearEntitlementTemplate()
 }
 return u.SetEntitlementTemplate(*value)

Would you like to run a quick code search to confirm actual usage patterns of these double-pointer functions?

Also applies to: 163-168, 170-175, 177-182, 198-203, 205-210, 226-231, 233-238


🏁 Script executed:

#!/bin/bash
# Searching usage of the double-pointer methods
rg -A 5 "SetOrClearEntitlementTemplate\\(|SetOrClearTaxConfig\\(|SetOrClearPrice\\(|SetOrClearDiscounts\\(" 

Length of output: 10228


Action: Add Additional Nil Check in Double-Pointer Methods

The current implementations in functions like SetOrClearEntitlementTemplate, SetOrClearTaxConfig, SetOrClearPrice, and SetOrClearDiscounts only check whether the pointer itself is nil. This can lead to a nil-pointer panic if value is not nil but *value is nil. To safeguard against this, please update the condition to check for both value == nil and *value == nil.

For example, change:

if value == nil {
	return u.ClearEntitlementTemplate()
}
return u.SetEntitlementTemplate(*value)

to:

if value == nil || *value == nil {
	// Gracefully handle pointer-to-nil scenario
	return u.ClearEntitlementTemplate()
}
return u.SetEntitlementTemplate(*value)

Affected functions (all in openmeter/ent/db/setorclear.go):

  • In both AddonRateCardUpdate and AddonRateCardUpdateOne methods for:
    • SetOrClearEntitlementTemplate
    • SetOrClearTaxConfig
    • SetOrClearPrice
    • SetOrClearDiscounts
  • In both PlanRateCardUpdate and PlanRateCardUpdateOne methods for the same fields.
  • In both SubscriptionItemUpdate and SubscriptionItemUpdateOne methods for the same fields.

Please apply similar changes to all double-pointer methods to prevent unexpected nil-pointer dereferences.

openmeter/ent/db/addon_create.go (1)

127-153: 🛠️ Refactor suggestion

Consider enforcing a logical relationship between EffectiveFrom and EffectiveTo.

Currently there is no domain check ensuring that EffectiveFromEffectiveTo, or that these fields obey any particular ordering (e.g., ensuring EffectiveTo is in the future). If such a constraint is required by your business rules, implementing a validation before saving will help avoid erroneous data in the database.

I can help you add a concise validation in ac.check() to ensure EffectiveTo is chronologically greater than or equal to EffectiveFrom. Would you like me to propose a snippet?

Also applies to: 365-395

tothandras
tothandras previously approved these changes Apr 4, 2025
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 (25)
app/common/productcatalog.go (1)

74-95: Consider wrapping the error from addonservice.New
While the code properly handles errors from addonadapter.New, if addonservice.New itself fails, it’s currently returned without context. To aid debugging, consider wrapping that error with additional context. Example:

 func NewAddonService(
     // ...
 ) (addon.Service, error) {
     adapter, err := addonadapter.New(...)
     if err != nil {
         return nil, fmt.Errorf("failed to initialize add-on adapter: %w", err)
     }

-    return addonservice.New(addonservice.Config{ ... })
+    srv, err := addonservice.New(addonservice.Config{ ... })
+    if err != nil {
+       return nil, fmt.Errorf("failed to initialize addonservice: %w", err)
+    }
+    return srv, nil
 }
openmeter/productcatalog/addon/adapter/mapping.go (1)

60-62: Misleading error message
The error says "invalid plan phase" instead of referencing the add-on. For clarity, the message should align with the actual entity (add-on).

- return nil, fmt.Errorf("invalid plan phase %s: %w", aa.ID, err)
+ return nil, fmt.Errorf("invalid add-on %s: %w", aa.ID, err)
openmeter/productcatalog/addon/assert.go (1)

54-63: Include RateCards comparison for holistic equality.
Currently, this function compares only the basic Addon metadata but not RateCards. If full equality checks are needed, consider invoking AssertAddonRateCardsEqual here for completeness.

 func AssertAddonEqual(t *testing.T, expected, actual Addon) {
    t.Helper()

    ...
    assert.Equalf(t, expected.Annotations, actual.Annotations, "annotations mismatch")
+   AssertAddonRateCardsEqual(t, expected.RateCards, actual.RateCards)
 }
openmeter/productcatalog/addon.go (1)

54-57: Status method’s reliance on current time is convenient.
This approach is fine for runtime checks, but note that test scenarios might require injecting time dependencies. You may want to mock or pass a time source for advanced scenarios.

openmeter/productcatalog/addon/httpdriver/mapping.go (2)

745-939: Consider refactoring this large switch-case for better maintainability.

The function AsPrice handles many price types in a single block, making it cumbersome to read and maintain. Splitting these cases into smaller helper functions or using a data-driven approach can improve clarity and testability.

Would you like me to open a new issue or draft a potential refactor for this function?


1-1093: Ensure comprehensive test coverage for these mapping functions.

Many of these mappers (e.g., FromAddon, FromRateCard, etc.) contain complex logic and numerous error paths. Verifying all major branches and error conditions can prevent subtle bugs.

Would you like help writing tests or setting up a coverage report for these new functions?

openmeter/productcatalog/addon/event.go (2)

57-64: Remove or utilize the unused slice of errors in the validation method.

Although errs is declared, it’s never populated, and you return immediately on error. Either remove errs and errors.Join, or accumulate multiple validation errors in the slice for a comprehensive report.


67-71: Use conventional lowercase parameter naming.

The parameter Addon is capitalized here, which is unusual for function parameters in Go. Following Go naming conventions (e.g., addon) improves readability and consistency.

openmeter/productcatalog/addon/repository.go (1)

12-20: Enhance documentation and testing for repository interface methods.

Adding doc comments clarifying the expected repository behavior can help team members implement these methods. Also, consider adding unit or integration tests to validate the repository’s CRUD operations.

openmeter/productcatalog/addon/httpdriver/driver.go (3)

14-16: Consider adding doc comments for clarity.

The Handler interface extends AddonHandler without additional documentation. Adding short doc comments can improve readability and comprehension for other developers, especially when expanding this interface in future iterations.


18-26: Interface size is acceptable, but consider grouping methods logically.

The AddonHandler interface enumerates many methods. While it appears consistent with the domain, you might group these methods or split them into smaller interfaces if they grow further, promoting maintainability.


36-43: Consider returning 4xx instead of 5xx for missing namespace.

When the namespace is missing, you return a 500 (Internal Server Error). Typically, missing or invalid request parameters are treated as client errors, e.g., 400 (Bad Request) or 422 (Unprocessable Entity). Re-check if this scenario aligns better with a 4xx status code.

-return "", commonhttp.NewHTTPError(http.StatusInternalServerError, errors.New("internal server error"))
+return "", commonhttp.NewHTTPError(http.StatusBadRequest, errors.New("namespace not provided"))
openmeter/productcatalog/addon/ratecard_test.go (1)

19-174: Enhance coverage with negative JSON scenarios.

TestRateCard_JSON tests successful serialization and deserialization for both "FlatFee" and "UsageBased" rate cards. Consider adding negative tests (e.g., invalid JSON structure) to ensure error handling paths are also verified.

openmeter/productcatalog/addon/httpdriver/addon.go (4)

75-77: Fix typo in error message.

In the error message, the word "pahse" seems to be a typographical error and should be "phase" (or simply "add-on").

-					return ListAddonsResponse{}, fmt.Errorf("failed to cast add-on pahse: %w", err)
+					return ListAddonsResponse{}, fmt.Errorf("failed to cast add-on phase: %w", err)

302-303: Address the TODO items for EffectivePeriod.

These TODO markers reference adding support for EffectivePeriod fields within the API request. If this enhancement is still needed, consider creating an issue or implementing it soon.

Do you want me to open a separate issue to track these TODOs?

Also applies to: 347-348


324-330: Unify handler response type naming.

For publishing an add-on, the request/response types are PublishAddonRequest and PublishAddonResponse, yet the encoder is using [GetAddonResponse]. While they may share the same underlying struct, using a clearly matching type (e.g., [PublishAddonResponse]) can reduce confusion.

Consider updating the encoder to keep naming consistently aligned with the publish action or unify the naming throughout.


367-374: Ensure consistent response type in the archiving endpoint.

Similar to the publish handler, ArchiveAddonHandler returns an ArchiveAddonResponse but encodes using [GetAddonResponse]. For clarity, either directly reuse the same type or rename the encoder’s type to match the action’s response alias consistently.

openmeter/productcatalog/addon/service.go (2)

81-83: Add validations for list parameters.

The Validate() method for ListAddonsInput is currently empty. Consider adding checks for valid page sizes, page numbers, or other fields, to ensure users' inputs are within expected ranges.


193-193: Implement the missing ratecard validation.

There's a FIXME indicating the need to validate ratecards in the UpdateAddonInput.Validate() method. Failing to validate ratecards could introduce unexpected behavior.

Would you like me to create a follow-up PR or issue to address and implement this validation logic?

openmeter/productcatalog/addon/service/addon.go (2)

139-139: Remove leftover “Plan” references in log messages and comments.

The code consistently refers to "add-ons" except in these lines, where it references "Plan." This can create confusion and inconsistency.

Example fix:

- logger.Debug("deleting Plan")
+ logger.Debug("deleting add-on")

- // Emit plan deleted event
+ // Emit add-on deleted event

- logger.Debug("Plan updated")
+ logger.Debug("add-on updated")

- // Emit plan updated event
+ // Emit add-on updated event

Also applies to: 163-163, 197-197, 209-209, 263-263, 303-303, 306-306


300-300: Correct spelling in error message.

"udpate aa-on" should be "update add-on."

- return nil, fmt.Errorf("failed to udpate aa-on: %w", err)
+ return nil, fmt.Errorf("failed to update add-on: %w", err)
openmeter/productcatalog/addon/ratecard.go (4)

61-98: Consider additional checks for empty or malformed input.

When v is json.RawMessage or []byte, it may be beneficial to verify that the raw data is not empty (or invalid JSON) to proactively catch malformed inputs before unmarshal.

Below is an example of how you might insert a brief check near line 70 for completeness:

 case json.RawMessage, []byte:
    b := any(v).([]byte)

+   if len(b) == 0 {
+       return nil, errors.New("empty rate card data provided")
+   }

    serde := &productcatalog.RateCardSerde{}
    ...

150-167: Remove redundant add-on ID equality check.

Line 153 already invokes RateCardManagedFields.Equal(...), which compares AddonID. Re-checking r.AddonID != vv.AddonID on line 157 is redundant. You can safely remove it to avoid duplication and reduce noise in the equality logic.

 if !r.RateCardManagedFields.Equal(vv.RateCardManagedFields) {
     return false
 }
-
-if r.AddonID != vv.AddonID {
-    return false
-}
 
 return r.FlatFeeRateCard.Equal(&vv.FlatFeeRateCard)

169-177: Validate the managed fields.

Currently, (*FlatFeeRateCard).Validate only invokes r.FlatFeeRateCard.Validate(). Consider also calling r.RateCardManagedFields.Validate() to ensure consistency with the required Namespace, ID, and AddonID checks.

func (r *FlatFeeRateCard) Validate() error {
    var errs []error

+   if err := r.RateCardManagedFields.Validate(); err != nil {
+       errs = append(errs, err)
+   }

    if err := r.FlatFeeRateCard.Validate(); err != nil {
        errs = append(errs, err)
    }
    ...
}

249-266: Remove redundant add-on ID equality check, and validate managed fields.

  1. As with FlatFeeRateCard, r.RateCardManagedFields.Equal(...) already compares AddonID, making the repeated check on line 256 redundant.
  2. (*UsageBasedRateCard).Validate() omits r.RateCardManagedFields.Validate(). Including it ensures consistency when validating critical fields.
func (r *UsageBasedRateCard) Equal(v productcatalog.RateCard) bool {
    switch vv := v.(type) {
    case *UsageBasedRateCard:
        if !r.RateCardManagedFields.Equal(vv.RateCardManagedFields) {
            return false
        }
-
-       if r.AddonID != vv.AddonID {
-           return false
-       }

        return r.UsageBasedRateCard.Equal(&vv.UsageBasedRateCard)
    ...
}

func (r *UsageBasedRateCard) Validate() error {
    var errs []error

+   if err := r.RateCardManagedFields.Validate(); err != nil {
+       errs = append(errs, err)
+   }

    if err := r.UsageBasedRateCard.Validate(); err != nil {
        errs = append(errs, err)
    }
    ...
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ef4e225 and 371a487.

📒 Files selected for processing (25)
  • app/common/productcatalog.go (4 hunks)
  • cmd/server/main.go (1 hunks)
  • cmd/server/wire.go (2 hunks)
  • cmd/server/wire_gen.go (14 hunks)
  • openmeter/event/metadata/resourcepath.go (1 hunks)
  • openmeter/productcatalog/addon.go (1 hunks)
  • openmeter/productcatalog/addon/adapter/adapter.go (1 hunks)
  • openmeter/productcatalog/addon/adapter/adapter_test.go (1 hunks)
  • openmeter/productcatalog/addon/adapter/addon.go (1 hunks)
  • openmeter/productcatalog/addon/adapter/mapping.go (1 hunks)
  • openmeter/productcatalog/addon/addon.go (1 hunks)
  • openmeter/productcatalog/addon/assert.go (1 hunks)
  • openmeter/productcatalog/addon/errors.go (1 hunks)
  • openmeter/productcatalog/addon/errors_test.go (1 hunks)
  • openmeter/productcatalog/addon/event.go (1 hunks)
  • openmeter/productcatalog/addon/httpdriver/addon.go (1 hunks)
  • openmeter/productcatalog/addon/httpdriver/driver.go (1 hunks)
  • openmeter/productcatalog/addon/httpdriver/errors.go (1 hunks)
  • openmeter/productcatalog/addon/httpdriver/mapping.go (1 hunks)
  • openmeter/productcatalog/addon/ratecard.go (1 hunks)
  • openmeter/productcatalog/addon/ratecard_test.go (1 hunks)
  • openmeter/productcatalog/addon/repository.go (1 hunks)
  • openmeter/productcatalog/addon/service.go (1 hunks)
  • openmeter/productcatalog/addon/service/addon.go (1 hunks)
  • openmeter/productcatalog/addon/service/service.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
  • cmd/server/wire.go
  • cmd/server/main.go
  • openmeter/productcatalog/addon/httpdriver/errors.go
  • openmeter/productcatalog/addon/errors_test.go
  • openmeter/productcatalog/addon/adapter/adapter.go
  • openmeter/productcatalog/addon/addon.go
  • openmeter/productcatalog/addon/errors.go
  • openmeter/event/metadata/resourcepath.go
  • openmeter/productcatalog/addon/service/service.go
🧰 Additional context used
🧬 Code Definitions (13)
app/common/productcatalog.go (2)
openmeter/productcatalog/feature/connector.go (1)
  • FeatureConnector (27-34)
openmeter/productcatalog/feature/feature.go (1)
  • Feature (87-110)
openmeter/productcatalog/addon/repository.go (3)
openmeter/productcatalog/addon/service.go (5)
  • ListAddonsInput (48-79)
  • CreateAddonInput (98-101)
  • DeleteAddonInput (231-233)
  • GetAddonInput (198-211)
  • UpdateAddonInput (122-142)
openmeter/productcatalog/addon.go (1)
  • Addon (177-182)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/productcatalog/addon/ratecard_test.go (3)
openmeter/productcatalog/addon/ratecard.go (4)
  • FlatFeeRateCard (102-105)
  • RateCardManagedFields (19-25)
  • UsageBasedRateCard (200-203)
  • NewRateCardFrom (61-98)
openmeter/productcatalog/addon/adapter/adapter.go (1)
  • Config (19-22)
openmeter/productcatalog/addon/service/service.go (1)
  • Config (12-17)
openmeter/productcatalog/addon/httpdriver/driver.go (1)
openmeter/productcatalog/addon/httpdriver/addon.go (7)
  • ListAddonsHandler (28-28)
  • CreateAddonHandler (102-102)
  • DeleteAddonHandler (197-197)
  • GetAddonHandler (247-247)
  • UpdateAddonHandler (149-149)
  • PublishAddonHandler (291-291)
  • ArchiveAddonHandler (336-336)
openmeter/productcatalog/addon/httpdriver/addon.go (1)
openmeter/productcatalog/addon/httpdriver/mapping.go (3)
  • FromAddon (23-62)
  • AsCreateAddonRequest (478-507)
  • AsUpdateAddonRequest (1071-1092)
openmeter/productcatalog/addon.go (2)
openmeter/productcatalog/ratecard.go (2)
  • RateCards (350-350)
  • RateCard (28-38)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/productcatalog/addon/assert.go (4)
openmeter/productcatalog/addon/service.go (2)
  • CreateAddonInput (98-101)
  • UpdateAddonInput (122-142)
openmeter/productcatalog/addon.go (1)
  • Addon (177-182)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/productcatalog/ratecard.go (4)
  • RateCards (350-350)
  • RateCard (28-38)
  • FlatFeeRateCard (161-168)
  • UsageBasedRateCard (256-262)
openmeter/productcatalog/addon/adapter/addon.go (3)
openmeter/productcatalog/addon/service.go (10)
  • ListAddonsInput (48-79)
  • OrderBy (33-33)
  • OrderByCreatedAt (29-29)
  • OrderByUpdatedAt (30-30)
  • OrderByKey (27-27)
  • OrderByID (26-26)
  • CreateAddonInput (98-101)
  • DeleteAddonInput (231-233)
  • GetAddonInput (198-211)
  • UpdateAddonInput (122-142)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/productcatalog/addon/errors.go (3)
  • IsNotFound (65-72)
  • NewNotFoundError (19-49)
  • NotFoundErrorParams (12-17)
cmd/server/wire_gen.go (12)
cmd/server/wire.go (1)
  • Application (40-76)
app/common/productcatalog.go (3)
  • NewFeatureConnector (41-49)
  • NewAddonService (74-94)
  • Addon (37-39)
openmeter/productcatalog/feature/connector.go (1)
  • NewFeatureConnector (84-100)
app/common/progressmanager.go (2)
  • ProgressManager (14-16)
  • NewProgressManager (19-39)
app/common/streaming.go (1)
  • NewStreamingConnector (21-43)
app/common/entitlement.go (1)
  • NewEntitlementRegistry (22-40)
openmeter/app/sandbox/app.go (1)
  • App (35-39)
openmeter/billing/service.go (1)
  • Service (9-19)
openmeter/customer/service.go (1)
  • Service (10-13)
openmeter/app/service/service.go (1)
  • Service (12-15)
openmeter/meter/service/service.go (1)
  • Service (13-15)
openmeter/progressmanager/service.go (1)
  • Service (9-11)
openmeter/productcatalog/addon/adapter/adapter_test.go (6)
openmeter/productcatalog/addon/service.go (5)
  • CreateAddonInput (98-101)
  • GetAddonInput (198-211)
  • ListAddonsInput (48-79)
  • UpdateAddonInput (122-142)
  • DeleteAddonInput (231-233)
openmeter/productcatalog/addon.go (4)
  • Addon (177-182)
  • AddonMeta (94-117)
  • AddonEffectivePeriod (38-44)
  • AddonStatus (22-22)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/productcatalog/addon/ratecard.go (3)
  • UsageBasedRateCard (200-203)
  • RateCardManagedFields (19-25)
  • FlatFeeRateCard (102-105)
openmeter/productcatalog/addon/service/service_test.go (1)
  • MonthPeriod (303-303)
openmeter/productcatalog/addon/assert.go (3)
  • AssertAddonCreateInputEqual (14-26)
  • AssertAddonEqual (54-63)
  • AssertAddonUpdateInputEqual (28-52)
openmeter/productcatalog/addon/httpdriver/mapping.go (2)
openmeter/productcatalog/addon/httpdriver/driver.go (1)
  • New (45-55)
openmeter/productcatalog/addon/httpdriver/addon.go (2)
  • CreateAddonRequest (100-100)
  • UpdateAddonRequest (147-147)
openmeter/productcatalog/addon/ratecard.go (1)
openmeter/productcatalog/ratecard.go (4)
  • FlatFeeRateCard (161-168)
  • UsageBasedRateCard (256-262)
  • RateCard (28-38)
  • RateCardSerde (40-42)
openmeter/productcatalog/addon/event.go (3)
openmeter/productcatalog/addon.go (1)
  • Addon (177-182)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/event/metadata/resourcepath.go (2)
  • ComposeResourcePath (28-30)
  • EntityAddon (20-20)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (go)
🔇 Additional comments (56)
app/common/productcatalog.go (3)

13-15: No issues found with the new add-on imports
The import statements appear correct and align with the new add-on functionality.


26-26: Addon injection set
Including Addon, within the ProductCatalog wire set looks appropriate and consistent with the existing plan and feature sets.


37-39: Wire set creation
Defining var Addon = wire.NewSet(NewAddonService) is consistent with the established wire pattern. No concerns here.

cmd/server/wire_gen.go (14)

26-26: Import of the addon package
This import is necessary to reference the new add-on functionality. No issues found.


90-98: Meter adapter creation
The snippet properly handles errors by invoking cleanup functions before returning. Looks good.


150-151: Initializing the FeatureConnector
Constructing the featureConnector with the required parameters is coherent with the existing pattern. No issues found.


161-162: Initialize appService
No immediate concerns. Error handling is consistent with the rest of the application’s service creation.


173-174: Tracer setup
The tracer is created following the existing telemetry pattern. No concerns.


186-187: Progress manager initialization
The function checks if the configuration is enabled and handles errors appropriately.


197-198: Streaming connector creation
Error cases are well-managed with cleanup calls. Implementation is consistent with other connectors.


207-207: Entitlement registry instantiation
The approach aligns with existing patterns. No issues found.


261-262: BillingService initialization
Constructing the billing service with multiple dependencies is handled gracefully. No concurrency or error-handling issues noted.


271-271: AppStripe service
The snippet ensures proper cleanup on errors, consistent with the pattern used in other services.


313-314: AppSandboxProvisioner
Dependencies are injected appropriately. Error handling and cleanup calls match the established approach.


367-367: MeterEventService
Constructing metereventService with (connector, service) follows the same injection pattern. No concerns.


424-425: Add-on and App injection into Application
Assigning addonService and appService to the application struct is correct for enabling new add-on capabilities.


475-475: New Addon field in the Application struct
This addition is consistent with the introduced add-on service.

openmeter/productcatalog/addon/adapter/mapping.go (4)

1-14: New adapter mapping file
All necessary imports for the add-on mapping logic appear correct and sufficiently scoped.


16-59: fromAddonRow function
Creates an addon.Addon object from a database row. The loop over Edges.Ratecards is clear, and the validation call is a good practice.


67-146: fromAddonRateCardRow function
Thoroughly constructs the RateCard from row data. Error conditions for billing cadence and unknown card types are properly handled.


148-189: asAddonRateCardRow function
Executes the reverse mapping from RateCard to database entities. The type-switch ensures correct handling of each rate card subtype.

openmeter/productcatalog/addon/assert.go (4)

14-26: Well-structured field-by-field comparison for Addon creation.
This function comprehensively checks each field in both the input and the resulting Addon, including RateCards. It provides clarity and is a sound approach for ensuring data integrity.


28-52: Good use of pointer logic for partial updates.
Only checking non-nil fields respects partial updates, preventing overwriting of unspecified fields. This is a neat approach to test real-world update scenarios.


65-102: Thorough RateCards comparison approach.
Mapping each slice into a map by key and verifying all entries ensures correct one-to-one matching. This is efficient and avoids misaligned index issues in a direct slice comparison.


104-155: Comprehensive RateCard equality checks.
Verifying type, metadata, feature, tax config, and billing cadence covers the critical fields. This ensures minimal risk of unnoticed discrepancies between RateCard instances.

openmeter/productcatalog/addon/adapter/adapter_test.go (8)

55-88: Well-organized test setup.
The use of test fixtures to initialize Postgres and the logging infrastructure is clear and maintainable. It provides a solid base for testing repository methods.


89-102: Feature creation step is logically placed.
Creating a feature before creating the addon ensures all dependencies are set up correctly. This aligns well with the test flow.


148-155: Test coverage for addon creation is excellent.
Validating the newly created addon with AssertAddonCreateInputEqual thoroughly checks correctness right after insertion.


157-201: Robust approach testing multiple retrieval paths.
Fetching add-ons by ID, Key, and Key+Version ensures comprehensive coverage of read scenarios. This test suite effectively verifies that all lookup mechanisms are correct.


203-239: Listing queries correctly validate filtering criteria.
Ensuring filters by IDs and keys individually or in combination helps confirm that the pagination and filtering logic behaves as expected.


241-303: Update test covers effective period and optional fields thoroughly.
Testing the entire update flow, including optional fields like Metadata and RateCards, helps guarantee that partial updates are applied correctly.


305-325: Confirming soft-delete behavior.
After deletion, the test retrieves the same addon and checks equality. If the intended design is for a "soft delete", this is correct. Otherwise, consider asserting the record is truly gone if a hard-delete is intended.

Is the deletion meant to be logical (soft-delete) or physical removal from the database? If physical, the test should confirm no record is retrievable afterward.


327-462: Status filter testing is quite comprehensive.
Testing draft, active, archived, and invalid states at different timeline scenarios validates the correctness of scheduling and versioned states. The thorough coverage of these cases is commendable.

openmeter/productcatalog/addon.go (10)

37-44: Clear definition of AddonEffectivePeriod.
The struct is straightforward, allowing optional from/to times. It's a good foundation for implementing versioned or time-bound add-on behaviors.


46-52: Validation handles invalid periods gracefully.
Returning an error when EffectiveTo is before EffectiveFrom is a good safeguard against illogical time ranges.


59-81: Flexible time-relative status checks.
StatusAt is a nice extension, supporting scenarios where historical or future statuses need to be known. Keep an eye on edge cases (e.g., if only EffectiveTo is set) to confirm behavior matches user expectations.

Would you like a script to seek occurrences where only EffectiveTo is set without an EffectiveFrom to ensure consistent status logic?


83-87: Equality logic for effective periods is concise.
Comparing the zero values as defaults works well, ensuring that absent times are considered logically equal to zero.


94-117: AddonMeta fields adequately capture addon metadata.
Key, version, name, and currency are well-defined, and descriptions are optional. The design fosters flexibility with minimal overhead.


119-139: Robust AddonMeta validation.
These checks prevent empty keys, names, or invalid currency. Aggregating errors with NewNillableGenericValidationError is a clean approach.


141-175: Comprehensive equality checks in AddonMeta.
All relevant fields (versioning, metadata, periods, etc.) are compared carefully. Using maps.Equal for annotations is a neat solution to compare map contents.


177-182: Struct composition fosters clarity.
Embedding AddonMeta inside Addon is a solid design choice that avoids repetitive fields and keeps the domain model consistent.


184-209: Validation ensures RateCards integrity.
Preventing duplicate RateCard keys and validating each entry helps reduce runtime errors. This is vital for a payment or subscription system.


211-217: Concat equality check for metadata and RateCards.
The combined Meta (via AddonMeta) and RateCards.Equal check ensures a thorough comparison for the entire Addon struct.

openmeter/productcatalog/addon/httpdriver/mapping.go (1)

64-64: Address the existing FIXME to consolidate common HTTP mappers.

This is the same reminder from a previous review: refactoring shared logic into a common package (e.g., productcatalog) will reduce duplication, improve consistency, and simplify maintainability.

openmeter/productcatalog/addon/httpdriver/driver.go (2)

30-34: Struct initialization appears straightforward.

The handler struct neatly aggregates the service, namespaceDecoder, and options. This design facilitates a clean separation of concerns, and no concurrency or correctness issues are apparent.


45-55: Factory function implementation is solid.

New correctly instantiates a private handler that implements the Handler interface. It's a clean pattern aligning with Go's recommendation to return interfaces from constructor functions.

openmeter/productcatalog/addon/ratecard_test.go (2)

176-316: Validation checks appear thorough.

TestFlatFeeRateCard rightly covers both valid and invalid rate card scenarios. The sub-tests adequately validate negative amounts, invalid payment terms, and questionable JSON configurations.


318-474: Usage-based tests mirror flat fee logic effectively.

TestUsageBasedRateCard provides strong coverage for valid and invalid states, including boundary checks (e.g., negative amounts, inverted commitments). The approach is consistent and effective.

openmeter/productcatalog/addon/adapter/addon.go (7)

24-153: Ordered, paginated list logic is well structured.

Aside from the potential empty filter concern, the rest of ListAddons effectively applies status-based filtering, eager loading of rate cards, sorting, and pagination. The approach aligns with best practices in ent.


154-216: Composite uniqueness for add-ons remains unenforced.

Creating add-ons does not appear to enforce (namespace, key, version) uniqueness. This was previously flagged in earlier reviews. Without a composite index or constraint, duplicates can still be inserted.


218-253: Rate card bulk creation implementation is straightforward.

The rateCardBulkCreate function correctly constructs a series of entdb.AddonRateCardCreate builders and returns them for bulk insertion, with no apparent issues.


255-300: Soft-delete logic is properly handled.

DeleteAddon sets DeletedAt instead of permanently removing the record, consistent with typical soft-delete patterns. Error handling for not-found cases is also well-structured.


302-399: Robust retrieval with fallback logic.

GetAddon handles multiple retrieval modes (by ID, by key/version, latest active) and includes thorough error handling. Eager loading of rate cards is integrated seamlessly.


462-479: Rate card updates overwrite existing entries.

This logic fully deletes existing rate cards before re-creating them, matching prior feedback about a "replace-all" approach. If partial updates or concurrency checks are required, further changes are recommended.


506-511: Eager loading with future DeletedAt support.

AddonEagerLoadRateCardsFn recognizes rate cards whose DeletedAt is either null or in the future, enabling planned inactivation. This approach appears aligned with scheduled removal patterns.

openmeter/productcatalog/addon/ratecard.go (1)

27-37: Looks Good: Cohesive field-based equality checks.

The RateCardManagedFields.Equal method correctly ensures that Namespace, ID, and AddonID are identical before returning true. This is a clear and straightforward approach to equality checks.

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: 3

🧹 Nitpick comments (21)
openmeter/productcatalog/addon/adapter/mapping.go (3)

42-58: Add debug logging for skipped nil ratecard edges.

When iterating over a.Edges.Ratecards, any nil edges are simply skipped. Consider logging or tracing that skip to aid troubleshooting if a partially loaded or missing related record leads to unexpected results.

 for _, edge := range a.Edges.Ratecards {
   if edge == nil {
+    // log skipped nil edge for tracing
     continue
   }
   ...

60-65: Ensure external callers handle wraparound validation errors.

The code calls aa.Validate() before returning. This consolidates errors nicely. However, ensure external or higher-level callers wrap or handle the returned error so that domain-related errors can be surfaced accurately to users or logs.


148-188: Consolidate repeated billing cadence extraction in asAddonRateCardRow.

In the switch statement for casting RateCard, each case calls v.<Variant>.BillingCadence.ISOStringPtrOrNil(). Although not harmful, it's slightly repetitive. You could unify the logic in a helper method (similar to getBillingCadence for reading) to ensure readability and consistency.

 func asAddonRateCardRow(r productcatalog.RateCard) (entdb.AddonRateCard, error) {
   ...
   switch v := r.(type) {
   case *productcatalog.FlatFeeRateCard, *addon.FlatFeeRateCard, *productcatalog.UsageBasedRateCard, *addon.UsageBasedRateCard:
-    ratecard.BillingCadence = v.XXX.BillingCadence.ISOStringPtrOrNil()
+    ratecard.BillingCadence = extractBillingCadencePtrOrNil(v)
   ...
 }
openmeter/productcatalog/addon/assert.go (4)

14-26: Improve negative test coverage for create input validation.

AssertAddonCreateInputEqual checks matches for normal fields but doesn’t explicitly test negative or edge cases (e.g., empty currency). It might be worth adding test scenarios within your suite to ensure that error states or unexpected data are caught early.


28-52: Handle optional fields more consistently in AssertAddonUpdateInputEqual.

The code correctly checks optional fields by verifying non-nil pointers. Consider adding a consistent fallback mechanism or a direct check if a field is set to default to prevent subtle mismatches, particularly around empty strings or cleared metadata.


54-65: Consider factoring repeated RateCard assertion logic into a helper.

AssertAddonEqual invokes AssertAddonRateCardsEqual, which in turn calls AssertRateCardEqual. If more assertion flows appear, you may centralize some of the repeated mapping logic (like building the r1Map and r2Map) to reduce code duplication.

Also applies to: 67-104


121-151: Ensure robust “metadata mismatch” coverage in AssertRateCardEqual.

This function uses .Equal checks for metadata, tax configurations, and more, which is great. For thorough coverage, consider adding specialized tests for each mismatch scenario (e.g., missing metadata keys, different tax structures) so that test failures remain highly actionable.

openmeter/productcatalog/addon.go (1)

177-217: Expand checks for RateCards concurrency or integrity.

The Addon struct’s Validate() ensures no duplicate keys, but doesn’t check concurrency or ordering constraints if multiple rate cards are updated in parallel. If multiple writes could conflict, consider applying concurrency controls or version checks to avoid data races or conflict overwrites.

openmeter/productcatalog/addon/event.go (3)

22-64: Robust creation event definition.

  1. The NewAddonCreateEvent function and AddonCreateEvent struct properly associate the session's UserID with the newly created add-on.
  2. Validate() ensures Addon is non-nil, preventing potential nil-pointer dereferences.

Everything looks good here, though consider adding unit tests for each event type to strengthen reliability.


155-196: Publish event logic is coherent.

  1. Using the add-on’s UpdatedAt timestamp for event emission is consistent with other event definitions.
  2. Validation ensures the add-on is non-nil.

Optionally, if you track a dedicated "PublishedAt" in the data model, you could reference that instead for more granularity.


198-239: Archiving event is well-implemented.

  1. Reliability is improved by reusing UpdatedAt to capture the time of archival.
  2. Maintains consistent event generation and metadata usage.

A small note: throughout the code, references to “add-on” sometimes differ in hyphenation from “addon.” Standardizing this across event definitions and error messages could enhance readability.

openmeter/productcatalog/addon/httpdriver/addon.go (1)

294-331: PublishAddon handler
Implementation references the new PublishAddon flow. Small typo in error message: "failed to punlish add-on".

Proposed fix:

- return PublishAddonResponse{}, fmt.Errorf("failed to punlish add-on [namespace=%s id=%s]: %w", ...)
+ return PublishAddonResponse{}, fmt.Errorf("failed to publish add-on [namespace=%s id=%s]: %w", ...)
openmeter/productcatalog/addon/service.go (3)

20-21: Use consistent naming in error messages.

Here, the error message states "invalid list Plans params", although the code deals with listing add-ons. To prevent confusion, consider renaming it to "invalid list Addons params."

- return pagination.PagedResponse[Addon]{}, fmt.Errorf("invalid list Plans params: %w", err)
+ return pagination.PagedResponse[Addon]{}, fmt.Errorf("invalid list Addons params: %w", err)

49-51: Consider handling pagination as optional.

The TODO comment suggests making pagination optional. If desired, you could adopt default pagination values or allow zero values for page size and page number to indicate unbounded queries. This might simplify the API for use cases where pagination is unnecessary.

Do you want me to open a new issue or propose a helper function for optional pagination?


193-193: Add ratecard validation logic.

There's a FIXME indicating ratecards aren't fully validated. Omitting validation may allow inconsistent ratecards to slip through. Consider implementing checks to ensure ratecards meet business rules (e.g., billing cadence alignment, feature references, etc.).

Would you like me to draft a ratecards validation method?

openmeter/productcatalog/addon/service/addon.go (1)

21-21: Eliminate inconsistent references to "Plan" and fix spelling errors.

Several lines still reference "Plan" instead of "Add-on" or contain typos:

  • Line 21: "invalid list Plans params"
  • Lines 108–109: "if there is no version in draft status for new plan"
  • Lines 139–142: "failed to publish plan created event"
  • Line 163: "logger.Debug(\"deleting Plan\")"
  • Line 300: "failed to udpate aa-on"

Using consistent naming improves readability and avoids confusion:

- return pagination.PagedResponse[addon.Addon]{}, fmt.Errorf("invalid list Plans params: %w", err)
+ return pagination.PagedResponse[addon.Addon]{}, fmt.Errorf("invalid list Add-ons params: %w", err)

- //   if there is no version in draft status for new plan
+ //   if there is no version in draft status for the new add-on

- return nil, fmt.Errorf("failed to publish plan created event: %w", err)
+ return nil, fmt.Errorf("failed to publish add-on created event: %w", err)

- logger.Debug("deleting Plan")
+ logger.Debug("deleting add-on")

- return nil, fmt.Errorf("failed to udpate aa-on: %w", err)
+ return nil, fmt.Errorf("failed to update add-on: %w", err)

Also applies to: 108-109, 139-142, 163-163, 300-300

openmeter/productcatalog/addon/ratecard.go (1)

57-59: Consider unifying or clarifying the role of ManagedRateCard.

The ManagedRateCard interface exposes a single method ManagedFields() RateCardManagedFields. If future ratecard variants share similar managed fields, consider integrating it into the primary RateCard interface, or expanding its purpose. This can simplify the type hierarchy, but be mindful of changes required across implementations.

openmeter/productcatalog/addon/httpdriver/mapping.go (4)

1-1: Consider adding tests for these mappings.
This file introduces multiple mapping functions, each covering complex logic with extensive type conversions. It's crucial to have robust unit test coverage to ensure that all edge cases and error paths are tested, particularly where custom error handling is performed.

Do you need help creating targeted unit tests for these mapping functions?


338-347: Clarify default payment term behavior.
In FromPaymentTerm, unrecognized payment term types fall back to PricePaymentTermInAdvance, which may lead to silent misconfigurations. Consider returning an error for unknown types or deliberately handling them with a dedicated case.


405-476: Unify discount logic.
The logic in FromDiscounts and FromDiscountPercentages overlaps significantly. Combining or refactoring them could reduce duplication and simplify code maintenance, especially around type checks and conversions.


745-939: Refactor nested switch in AsPrice.
This function handles multiple price types, each with its own conversion steps. Consider factoring out each case into smaller, dedicated helper functions or using a type-based strategy. This approach enhances readability and maintainability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 371a487 and 1f7c3c7.

📒 Files selected for processing (25)
  • app/common/productcatalog.go (4 hunks)
  • cmd/server/main.go (1 hunks)
  • cmd/server/wire.go (2 hunks)
  • cmd/server/wire_gen.go (14 hunks)
  • openmeter/event/metadata/resourcepath.go (1 hunks)
  • openmeter/productcatalog/addon.go (1 hunks)
  • openmeter/productcatalog/addon/adapter/adapter.go (1 hunks)
  • openmeter/productcatalog/addon/adapter/adapter_test.go (1 hunks)
  • openmeter/productcatalog/addon/adapter/addon.go (1 hunks)
  • openmeter/productcatalog/addon/adapter/mapping.go (1 hunks)
  • openmeter/productcatalog/addon/addon.go (1 hunks)
  • openmeter/productcatalog/addon/assert.go (1 hunks)
  • openmeter/productcatalog/addon/errors.go (1 hunks)
  • openmeter/productcatalog/addon/errors_test.go (1 hunks)
  • openmeter/productcatalog/addon/event.go (1 hunks)
  • openmeter/productcatalog/addon/httpdriver/addon.go (1 hunks)
  • openmeter/productcatalog/addon/httpdriver/driver.go (1 hunks)
  • openmeter/productcatalog/addon/httpdriver/errors.go (1 hunks)
  • openmeter/productcatalog/addon/httpdriver/mapping.go (1 hunks)
  • openmeter/productcatalog/addon/ratecard.go (1 hunks)
  • openmeter/productcatalog/addon/ratecard_test.go (1 hunks)
  • openmeter/productcatalog/addon/repository.go (1 hunks)
  • openmeter/productcatalog/addon/service.go (1 hunks)
  • openmeter/productcatalog/addon/service/addon.go (1 hunks)
  • openmeter/productcatalog/addon/service/service.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (11)
  • cmd/server/wire.go
  • openmeter/productcatalog/addon/httpdriver/errors.go
  • cmd/server/main.go
  • openmeter/event/metadata/resourcepath.go
  • openmeter/productcatalog/addon/service/service.go
  • openmeter/productcatalog/addon/httpdriver/driver.go
  • openmeter/productcatalog/addon/repository.go
  • openmeter/productcatalog/addon/addon.go
  • openmeter/productcatalog/addon/adapter/adapter.go
  • openmeter/productcatalog/addon/errors.go
  • openmeter/productcatalog/addon/adapter/addon.go
🧰 Additional context used
🧬 Code Definitions (7)
app/common/productcatalog.go (2)
openmeter/productcatalog/feature/connector.go (1)
  • FeatureConnector (27-34)
openmeter/productcatalog/feature/feature.go (1)
  • Feature (87-110)
openmeter/productcatalog/addon/httpdriver/addon.go (1)
openmeter/productcatalog/addon/httpdriver/mapping.go (3)
  • FromAddon (23-62)
  • AsCreateAddonRequest (478-507)
  • AsUpdateAddonRequest (1071-1092)
openmeter/productcatalog/addon/assert.go (4)
openmeter/productcatalog/addon/service.go (2)
  • CreateAddonInput (98-101)
  • UpdateAddonInput (122-142)
openmeter/productcatalog/addon.go (1)
  • Addon (177-182)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/productcatalog/ratecard.go (4)
  • RateCards (350-350)
  • RateCard (28-38)
  • FlatFeeRateCard (161-168)
  • UsageBasedRateCard (256-262)
openmeter/productcatalog/addon/ratecard_test.go (2)
openmeter/productcatalog/ratecard.go (4)
  • RateCard (28-38)
  • FlatFeeRateCard (161-168)
  • RateCardMeta (49-77)
  • UsageBasedRateCard (256-262)
openmeter/productcatalog/addon/ratecard.go (4)
  • FlatFeeRateCard (102-105)
  • RateCardManagedFields (19-25)
  • UsageBasedRateCard (204-207)
  • NewRateCardFrom (61-98)
openmeter/productcatalog/addon/adapter/adapter_test.go (6)
openmeter/productcatalog/addon/service.go (5)
  • CreateAddonInput (98-101)
  • GetAddonInput (198-211)
  • ListAddonsInput (48-79)
  • UpdateAddonInput (122-142)
  • DeleteAddonInput (231-233)
openmeter/productcatalog/addon.go (4)
  • Addon (177-182)
  • AddonMeta (94-117)
  • AddonEffectivePeriod (38-44)
  • AddonStatus (22-22)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/productcatalog/addon/ratecard.go (3)
  • UsageBasedRateCard (204-207)
  • RateCardManagedFields (19-25)
  • FlatFeeRateCard (102-105)
openmeter/productcatalog/addon/service/service_test.go (1)
  • MonthPeriod (303-303)
openmeter/productcatalog/addon/assert.go (3)
  • AssertAddonCreateInputEqual (14-26)
  • AssertAddonEqual (54-65)
  • AssertAddonUpdateInputEqual (28-52)
openmeter/productcatalog/addon/ratecard.go (3)
openmeter/productcatalog/addon/adapter/adapter.go (1)
  • New (42-51)
openmeter/productcatalog/addon/service/service.go (1)
  • New (19-42)
openmeter/productcatalog/ratecard.go (6)
  • FlatFeeRateCard (161-168)
  • UsageBasedRateCard (256-262)
  • RateCard (28-38)
  • RateCardSerde (40-42)
  • FlatFeeRateCardType (15-15)
  • UsageBasedRateCardType (16-16)
openmeter/productcatalog/addon/httpdriver/mapping.go (2)
openmeter/productcatalog/addon/httpdriver/driver.go (1)
  • New (45-55)
openmeter/productcatalog/addon/httpdriver/addon.go (2)
  • CreateAddonRequest (100-100)
  • UpdateAddonRequest (147-147)
⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: Quickstart
  • GitHub Check: Test
  • GitHub Check: Lint
  • GitHub Check: E2E
  • GitHub Check: Developer environment
  • GitHub Check: CI
  • GitHub Check: Commit hooks
  • GitHub Check: Build
  • GitHub Check: Analyze (go)
🔇 Additional comments (43)
openmeter/productcatalog/addon/adapter/mapping.go (3)

16-40: Consider validating Edges fields more defensively.

The fromAddonRow function initializes an addon.Addon by mapping fields from entdb.Addon. While it handles nil checks at the loop level, it may be worth confirming that other Edges fields (or nested references) are not nil before usage to avoid potential panics in less common scenarios.

Would you like a quick script to check for nil references in the adjacency edges across the codebase?


67-95: Review feature attachment logic for potential expansions.

The fromAddonRateCardRow function attaches a single feature if available (r.Edges.Features). This may suffice for now, but if multiple features per rate card are eventually required, further expansions could break the one-feature assumption. Keep this in view if future product requirements change.

Would you like a script to verify that no existing logic attempts to attach multiple features to a single rate card?


98-146:

Details

✅ Verification successful

Proactively handle potential unknown or less common rate card types.

The switch on r.Type only handles FlatFeeRateCardType and UsageBasedRateCardType, and defaults to an error for anything else. This is good for strictness; just ensure your broader application code doesn’t attempt to store or parse new types without an accompanying update here.


🏁 Script executed:

#!/bin/bash
# Check for references to potential unknown rate card types in the codebase
rg 'RateCardType\s+=\s+' -A 3

Length of output: 577


Attention: Rate Card Type Handling Verified

The verification confirmed that only flat_fee and usage_based rate card types are defined across the codebase, so handling unknown types by erroring out in the default case remains appropriate. Just ensure that any future rate card type additions are explicitly supported here to avoid runtime errors.

openmeter/productcatalog/addon.go (1)

94-175: Add unit tests for boundary currency validations.

AddonMeta.Validate() enforces m.Currency.Validate(). Ensure test coverage includes invalid or unsupported currency codes, verifying that the system gracefully handles or rejects them.

Would you like me to generate a quick script to grep for existing currency tests and confirm coverage of invalid codes?

openmeter/productcatalog/addon/event.go (4)

1-11: Great foundational setup for the add-on package.

Defining a dedicated package for add-on events helps keep concerns modular and maintainable. This includes proper imports for context, errors, ULID generation, and the session package.


13-20: Clear and consistent event naming constants.

Declaring subsystem and event names in a single block ensures a canonical reference for all event types. Helps maintain clarity in the codebase.


65-107: Good practice in the update event logic.

  1. NewAddonUpdateEvent captures the UserID context and populates the event.
  2. Validate() ensures an Addon is set before proceeding, aligning with best practices for data consistency.

Implementation appears sound with no obvious issues.


108-153: Appropriate handling of the delete event.

  1. Validate() enforces DeletedAt to be non-nil, ensuring that a delete event isn’t generated prematurely.
  2. The event’s time references the actual deletion timestamp for accurate record-keeping.

Approach is correct and thorough.

app/common/productcatalog.go (4)

13-15: Well-structured imports.

Including addon, addonadapter, and addonservice in a single section clarifies the new add-on dependency. No issues noted here.


26-26: Logical extension of the wire sets.

Adding Addon to the ProductCatalog set ensures smooth integration alongside Feature and Plan.


37-39: Dedicated wire set for add-ons.

Creating a separate var Addon wire set aligns with the existing pattern for Feature and Plan. It keeps dependencies organized and easier to maintain.


74-94: Initialization of the add-on service seems robust.

  1. The NewAddonService function properly validates dependencies with addonadapter.New and addonservice.New.
  2. Clear error messages guide the user when adapter creation fails.

This approach follows the established pattern for the plan and feature services, ensuring consistency.

openmeter/productcatalog/addon/errors_test.go (1)

1-95: Test coverage for the NotFoundError is comprehensive.

  1. TestIsNotFoundError covers valid, wrapped, and invalid error scenarios, verifying the IsNotFound function.
  2. TestIsNotFoundError_String accurately checks the error message format for various parameter combinations.

Thank you for addressing the prior feedback about referencing “addon” rather than “plan.” This file now looks well-aligned with add-on error definitions.

cmd/server/wire_gen.go (14)

26-26: Import used for add-on support
This newly introduced import references the addon package for the add-on functionality. Broadly looks correct, as it wires the new service into the application.


90-98: Consistent error-handling and resource cleanup
This block follows the established cleanup pattern for failures in the wire initialization. The logic is consistent and ensures resources are cleaned properly on error. No issues found.


150-151: New feature connector and add-on service instantiation
The instantiation sequence for featureConnector and addonService looks coherent. The dependency order (feature connector before add-on creation) is logical.


161-162: App service creation
Loading appsConfiguration from conf.Apps and then creating the appService is consistent with the standard approach in this file.


172-174: Tracer and entitlements initialization
Establishing the tracer before reading further configuration matches the existing pattern. This helps ensure instrumentation is available early.


187-187: Progress manager configuration
Assigning progressmanagerConfiguration := conf.ProgressManager cleanly separates configuration concerns. No issues here.


198-198: Creating new streaming connector
The streaming connector is constructed with the relevant services, ensuring the event pipeline is available. No concurrency issues evident.


207-207: Entitlement registry references
Passing connector, service, eventbusPublisher into the entitlement registry helps unify the domain logic. No concerns noted.


261-261: Billing service with new service references
Incorporating service (MeterService) and connector ensures billing logic can access meter data flows. This wiring looks correct.


271-271: AppStripe service creation
The new appstripeService relies on appService and other components. The ordering avoids dependency cycles.


313-313: App sandbox provisioner
Provisioning the sandbox environment for new apps is consistent with the approach used for other provisioning steps in this file.


367-367: MeterEventService references
metereventService correctly depends on connector and service for event streaming. Alignment with existing patterns is good.


424-425: Including Addon and App in Application return
Storing Addon: addonService and App: appService in the returned Application struct ensures these services are available downstream.


475-475: New Addon field in Application struct
Exposing Addon addon.Service in the Application struct is consistent with other top-level services. No issues with naming or structure.

openmeter/productcatalog/addon/ratecard_test.go (3)

19-174: Comprehensive JSON serialization tests
These tests for TestRateCard_JSON effectively validate both serialization and deserialization flows, ensuring the original and round-tripped RateCards are equivalent. Consider adding edge case tests (e.g. empty/partial JSON) in future expansions.


176-316: Flat fee rate card tests
The TestFlatFeeRateCard function covers both valid and invalid RateCard scenarios, ensuring robust validation checks. The test coverage is thorough.


318-474: Usage-based rate card tests
TestUsageBasedRateCard effectively covers the usage-based RateCard structure, verifying correct handling of billing cadence and commitments. Test coverage looks solid.

openmeter/productcatalog/addon/adapter/adapter_test.go (3)

55-331: TestPostgresAdapter with thorough sub-tests
Your sub-tests for create/get/list/update/delete comprehensively cover the lifecycle of an add-on in PostgreSQL. Validation steps using the AssertAddon* methods are well-structured.


333-359: Version creation helper
createAddonVersion ensures new versions are both created and updated with the correct EffectivePeriod. This approach is consistent with multi-version add-on steps.


361-462: Status filter coverage
testListPlanStatusFilter thoroughly tests the filtering based on add-on status (draft, active, archived). The timeline-based logic is verified with multiple test cases. Nicely done.

openmeter/productcatalog/addon/httpdriver/addon.go (6)

31-98: ListAddons HTTP handler
The request struct construction, filtering, and response pagination are clearly handled. Error paths are correctly surfaced.


105-144: CreateAddon HTTP handler
Decoding the JSON request and constructing a CreateAddonRequest aligns with the service expectations. The response uses a neat converter method. No conversion issues spotted.


152-192: UpdateAddon HTTP handler
The flow for reading request JSON, mapping to UpdateAddonRequest, and returning updated data is direct and consistent. Looks solid.


200-230: DeleteAddon HTTP handler
The deletion logic is straightforward, with a 204 No Content response. No issues with the fallback error path.


250-286: GetAddon HTTP handler
Resolving ID vs. key in idOrKey is a nice detail. This handler properly updates the request struct for retrieving a specific version or the latest.


339-374: ArchiveAddon handler
Similar structure to publishing. Setting an EffectiveTo time is inline with the archiving approach. No other issues noted.

openmeter/productcatalog/addon/ratecard.go (1)

61-98: Nicely done leveraging generics in NewRateCardFrom.

This approach cleanly handles multiple ratecard representations (FlatFeeRateCard, UsageBasedRateCard, or raw JSON) in a single function. It reduces boilerplate and keeps the code flexible.

openmeter/productcatalog/addon/httpdriver/mapping.go (3)

64-64: ** Address the FIXME to consolidate mappers.**
Consolidating HTTP mappers under a common package would reduce duplication and improve maintainability.


982-1055: Validate usage period alignment earlier.
When a metered entitlement template doesn’t define a UsagePeriod, the code infers it from billingCadence. If both are missing, an error is thrown late in the parsing process. Consider validating such constraints closer to input parsing to fail fast and simplify this function’s logic.


478-507: Double-check currency parsing during creation.
AsCreateAddonRequest sets the currency code and validates it. While this is good practice, ensure that upstream calling code properly handles validation errors and surfaces meaningful messages to users.

Would you like me to verify references to invalid CurrencyCode in your codebase to confirm that errors are handled appropriately?

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

♻️ Duplicate comments (1)
openmeter/productcatalog/addon.go (1)

170-172: ⚠️ Potential issue

Fix inverted logic in annotation comparison.

The method returns false if annotations are equal, which contradicts the intended equality check. Switch the condition to ensure equality is handled correctly.

-	if !maps.Equal(m.Annotations, v.Annotations) {
+	if !maps.Equal(m.Annotations, v.Annotations) {
		return false
	}
🧹 Nitpick comments (26)
app/common/productcatalog.go (1)

74-94: Provide minimal doc comments for NewAddonService

The function is well-structured and correctly initializes the adapter and service. Consider adding a doc comment to clarify its purpose and parameters.

+// NewAddonService provides a fully initialized add-on service instance
+// by creating an adapter and passing required dependencies.
 func NewAddonService(
     logger *slog.Logger,
     ...
 )
cmd/server/wire_gen.go (3)

90-98: Consider consolidating consecutive cleanup calls

If NewMeterAdapter fails, multiple cleanup functions are invoked one after another. You could wrap these calls in a helper to reduce repetition.

 if err != nil {
-	cleanup5()
-	cleanup4()
-	cleanup3()
-	cleanup2()
-	cleanup()
-	return Application{}, nil, err
+	cleanupAll(cleanup5, cleanup4, cleanup3, cleanup2, cleanup)
+	return Application{}, nil, err
 }

197-197: Check naming consistency for service

entitlement := common.NewEntitlementRegistry(... service, ...) might be confusing given multiple services in scope. Renaming the variable to meterService or a more specific descriptor can help clarify usage.


367-367: MeterEventService referencing service

Similar to the entitlement registry comment, consider renaming service to avoid ambiguity if multiple services are in scope.

openmeter/productcatalog/addon/adapter/mapping.go (2)

16-65: Implementing fromAddonRow

Building the core addon.Addon entity from a database row is cleanly structured. Validation after constructing the object helps detect invalid data early. Consider verifying if rate card collections may need concurrency handling (e.g., large sets) in the future.


148-189: Implementing asAddonRateCardRow

The type switch for different rate card types is clear and expressive. You might add brief doc comments to help maintainers understand usage.

+// asAddonRateCardRow converts a domain RateCard to the corresponding
+// entdb.AddonRateCard entity, handling both flat fee and usage-based types.
 func asAddonRateCardRow(r productcatalog.RateCard) (entdb.AddonRateCard, error) {
     ...
 }
openmeter/productcatalog/addon.go (3)

37-44: Track the consolidation of AddonEffectivePeriod with EffectivePeriod.

The FIXME comment indicates there's potential duplication with a broader EffectivePeriod concept. Consider creating a shared implementation to maintain DRY principles and ensure consistent behavior across similar time-based status concepts in the codebase.


70-72: Simplify the archive status condition.

The current condition for determining archived status is complex and could be simplified for better readability.

-	if from.Before(t) && (to.Before(t) && from.Before(to)) {
+	if from.Before(t) && to.Before(t) && from.Before(to) {
		return AddonStatusArchived
	}

191-195: Complete the comment for RateCard validation checks.

The comment starts with "Check for" but doesn't complete the sentence properly. Consider restructuring for clarity.

-	// Check for
-	// * duplicated rate card keys
-	// * namespace mismatch
-	// * invalid RateCards
+	// Check for:
+	// * duplicated rate card keys
+	// * namespace mismatch
+	// * invalid RateCards
openmeter/productcatalog/addon/event.go (1)

66-70: Standardize parameter naming in event constructors.

In this function and subsequent ones (lines 109, 156, 199), the parameter is named Addon with an uppercase A, while in NewAddonCreateEvent it's lowercase addon. Standardize parameter naming for consistency.

-func NewAddonUpdateEvent(ctx context.Context, Addon *Addon) AddonUpdateEvent {
+func NewAddonUpdateEvent(ctx context.Context, addon *Addon) AddonUpdateEvent {
	return AddonUpdateEvent{
-		Addon:  Addon,
+		Addon:  addon,
		UserID: session.GetSessionUserID(ctx),
	}
}
openmeter/productcatalog/addon/ratecard_test.go (2)

19-24: Remove unused ExpectedError field in test struct.

The ExpectedError field is defined in the test struct but never used in the actual test cases. The test always expects both serialization and deserialization to succeed (lines 164, 169).

	tests := []struct {
		Name          string
		RateCard      productcatalog.RateCard
-		ExpectedError bool
	}{

163-171: Consider testing error cases for JSON serialization/deserialization.

The test only covers the happy path where both serialization and deserialization succeed. Consider adding test cases that verify error handling for malformed JSON or incompatible types.

openmeter/productcatalog/addon/assert.go (1)

22-23: Maintain consistent error message prefixes.

The error messages for metadata and annotations comparison don't have the "create input: " prefix that other comparisons use, which makes error output inconsistent.

-	assert.Equalf(t, i.Metadata, a.Metadata, "metadata mismatch")
-	assert.Equalf(t, i.Annotations, a.Annotations, "annotations mismatch")
+	assert.Equalf(t, i.Metadata, a.Metadata, "create input: metadata mismatch")
+	assert.Equalf(t, i.Annotations, a.Annotations, "create input: annotations mismatch")
openmeter/productcatalog/addon/httpdriver/addon.go (2)

317-319: Fix minor spelling error: "punlish".

There's a typo in the error message:

- return PublishAddonResponse{}, fmt.Errorf("failed to punlish add-on [namespace=%s id=%s]: %w", request.Namespace, request.ID, err)
+ return PublishAddonResponse{}, fmt.Errorf("failed to publish add-on [namespace=%s id=%s]: %w", request.Namespace, request.ID, err)

302-303: Assist with TODO for effective periods.

You have TODO comments referencing updates to api.Request for EffectivePeriod. Let me know if you'd like assistance or if you want to track these steps in a new issue.

Also applies to: 347-348

openmeter/productcatalog/addon/service.go (5)

21-21: Use "add-ons" instead of "Plans" in error message.
This error message references "Plans" even though the method is for listing add-ons.

- return pagination.PagedResponse[addon.Addon]{}, fmt.Errorf("invalid list Plans params: %w", err)
+ return pagination.PagedResponse[addon.Addon]{}, fmt.Errorf("invalid list add-ons params: %w", err)

163-163: Use "add-on" instead of "Plan" in the debug log.
This debug statement references "Plan" when deleting an add-on, which might cause confusion.

- logger.Debug("deleting Plan")
+ logger.Debug("deleting add-on")

241-241: Provide a dedicated not-found error.
Currently, the code has a FIXME comment regarding the not-found scenario. Consider gracefully returning a well-defined "not found" error (e.g., a typed error or 404 equivalent) for clarity and consistency.

Would you like help implementing a structured "NotFound" error type and returning it here?


300-300: Fix the typo "udpate aa-on".
There's a small spelling mismatch in the error message.

- return nil, fmt.Errorf("failed to udpate aa-on: %w", err)
+ return nil, fmt.Errorf("failed to update add-on: %w", err)

553-557: Extract single-draft-version check into a shared helper.
Both “CreateAddon” (line 108 onward) and “NextAddon” (line 553 onward) enforce the single-draft-version rule. Refactoring this into a helper function would reduce duplication and improve maintainability.

// Example extracted helper (in a shared location):
func ensureNoDraftVersionExists(versions []addon.Addon) error {
	for _, v := range versions {
		if v.DeletedAt == nil && v.Status() == productcatalog.AddonStatusDraft {
			return models.NewGenericValidationError(
				fmt.Errorf("only a single draft version is allowed for add-on"),
			)
		}
	}
	return nil
}
openmeter/productcatalog/addon/service/addon.go (5)

21-21: Use "add-ons" instead of "Plans" in error message.
This error message references "Plans" even though the method is for listing add-ons.

- return pagination.PagedResponse[addon.Addon]{}, fmt.Errorf("invalid list Plans params: %w", err)
+ return pagination.PagedResponse[addon.Addon]{}, fmt.Errorf("invalid list add-ons params: %w", err)

163-163: Use "add-on" instead of "Plan" in the debug log.
This debug statement references "Plan" when deleting an add-on, which might cause confusion.

- logger.Debug("deleting Plan")
+ logger.Debug("deleting add-on")

241-241: Provide a dedicated not-found error.
Currently, the code has a FIXME comment regarding the not-found scenario. Consider gracefully returning a well-defined "not found" error (e.g., a typed error or 404 equivalent) for clarity and consistency.

Would you like help implementing a structured "NotFound" error type and returning it here?


300-300: Fix the typo "udpate aa-on".
There's a small spelling mismatch in the error message.

- return nil, fmt.Errorf("failed to udpate aa-on: %w", err)
+ return nil, fmt.Errorf("failed to update add-on: %w", err)

553-557: Extract single-draft-version check into a shared helper.
Both “CreateAddon” and “NextAddon” in the service logic enforce the single-draft-version rule. Refactoring this into a helper function would reduce duplication and improve maintainability.

// Example extracted helper (in a shared location):
func ensureNoDraftVersionExists(versions []addon.Addon) error {
	for _, v := range versions {
		if v.DeletedAt == nil && v.Status() == productcatalog.AddonStatusDraft {
			return models.NewGenericValidationError(
				fmt.Errorf("only a single draft version is allowed for add-on"),
			)
		}
	}
	return nil
}
openmeter/productcatalog/addon/ratecard.go (1)

61-98: Consider centralizing marshalling/unmarshalling logic for RateCards.
Multiple code paths in NewRateCardFrom handle JSON deserialization for FlatFeeRateCard and UsageBasedRateCard. You might reduce duplication by introducing a central function or bridging logic that can manage the unmarshal steps for all supported types.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1f7c3c7 and 2e8b13c.

📒 Files selected for processing (25)
  • app/common/productcatalog.go (4 hunks)
  • cmd/server/main.go (1 hunks)
  • cmd/server/wire.go (2 hunks)
  • cmd/server/wire_gen.go (14 hunks)
  • openmeter/event/metadata/resourcepath.go (1 hunks)
  • openmeter/productcatalog/addon.go (1 hunks)
  • openmeter/productcatalog/addon/adapter/adapter.go (1 hunks)
  • openmeter/productcatalog/addon/adapter/adapter_test.go (1 hunks)
  • openmeter/productcatalog/addon/adapter/addon.go (1 hunks)
  • openmeter/productcatalog/addon/adapter/mapping.go (1 hunks)
  • openmeter/productcatalog/addon/addon.go (1 hunks)
  • openmeter/productcatalog/addon/assert.go (1 hunks)
  • openmeter/productcatalog/addon/errors.go (1 hunks)
  • openmeter/productcatalog/addon/errors_test.go (1 hunks)
  • openmeter/productcatalog/addon/event.go (1 hunks)
  • openmeter/productcatalog/addon/httpdriver/addon.go (1 hunks)
  • openmeter/productcatalog/addon/httpdriver/driver.go (1 hunks)
  • openmeter/productcatalog/addon/httpdriver/errors.go (1 hunks)
  • openmeter/productcatalog/addon/httpdriver/mapping.go (1 hunks)
  • openmeter/productcatalog/addon/ratecard.go (1 hunks)
  • openmeter/productcatalog/addon/ratecard_test.go (1 hunks)
  • openmeter/productcatalog/addon/repository.go (1 hunks)
  • openmeter/productcatalog/addon/service.go (1 hunks)
  • openmeter/productcatalog/addon/service/addon.go (1 hunks)
  • openmeter/productcatalog/addon/service/service.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (11)
  • cmd/server/main.go
  • cmd/server/wire.go
  • openmeter/productcatalog/addon/httpdriver/errors.go
  • openmeter/event/metadata/resourcepath.go
  • openmeter/productcatalog/addon/errors_test.go
  • openmeter/productcatalog/addon/httpdriver/driver.go
  • openmeter/productcatalog/addon/repository.go
  • openmeter/productcatalog/addon/adapter/adapter.go
  • openmeter/productcatalog/addon/errors.go
  • openmeter/productcatalog/addon/service/service.go
  • openmeter/productcatalog/addon/addon.go
🧰 Additional context used
🧬 Code Definitions (8)
openmeter/productcatalog/addon/httpdriver/addon.go (1)
openmeter/productcatalog/addon/httpdriver/mapping.go (3)
  • FromAddon (23-62)
  • AsCreateAddonRequest (478-507)
  • AsUpdateAddonRequest (1071-1092)
openmeter/productcatalog/addon/ratecard_test.go (3)
openmeter/productcatalog/addon/ratecard.go (4)
  • FlatFeeRateCard (102-105)
  • RateCardManagedFields (19-25)
  • UsageBasedRateCard (204-207)
  • NewRateCardFrom (61-98)
openmeter/productcatalog/addon/adapter/adapter.go (1)
  • Config (19-22)
openmeter/productcatalog/addon/service/service.go (1)
  • Config (12-17)
openmeter/productcatalog/addon/adapter/adapter_test.go (6)
openmeter/productcatalog/addon/service.go (4)
  • CreateAddonInput (98-101)
  • GetAddonInput (198-211)
  • ListAddonsInput (48-79)
  • UpdateAddonInput (122-142)
openmeter/productcatalog/addon.go (4)
  • Addon (177-182)
  • AddonMeta (94-117)
  • AddonEffectivePeriod (38-44)
  • AddonStatus (22-22)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/productcatalog/addon/ratecard.go (2)
  • UsageBasedRateCard (204-207)
  • FlatFeeRateCard (102-105)
openmeter/productcatalog/addon/service/service_test.go (1)
  • MonthPeriod (303-303)
openmeter/productcatalog/addon/assert.go (3)
  • AssertAddonCreateInputEqual (14-26)
  • AssertAddonEqual (54-65)
  • AssertAddonUpdateInputEqual (28-52)
openmeter/productcatalog/addon/assert.go (4)
openmeter/productcatalog/addon/service.go (2)
  • CreateAddonInput (98-101)
  • UpdateAddonInput (122-142)
openmeter/productcatalog/addon.go (1)
  • Addon (177-182)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/productcatalog/ratecard.go (4)
  • RateCards (350-350)
  • RateCard (28-38)
  • FlatFeeRateCard (161-168)
  • UsageBasedRateCard (256-262)
openmeter/productcatalog/addon/service/addon.go (2)
openmeter/productcatalog/addon/service.go (7)
  • ListAddonsInput (48-79)
  • CreateAddonInput (98-101)
  • OrderBy (33-33)
  • UpdateAddonInput (122-142)
  • PublishAddonInput (245-250)
  • ArchiveAddonInput (288-294)
  • NextAddonInput (316-326)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/productcatalog/addon.go (2)
openmeter/productcatalog/ratecard.go (2)
  • RateCards (350-350)
  • RateCard (28-38)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/productcatalog/addon/event.go (3)
openmeter/productcatalog/addon.go (1)
  • Addon (177-182)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/event/metadata/resourcepath.go (2)
  • ComposeResourcePath (28-30)
  • EntityAddon (20-20)
openmeter/productcatalog/addon/httpdriver/mapping.go (2)
openmeter/productcatalog/addon/httpdriver/driver.go (1)
  • New (45-55)
openmeter/productcatalog/addon/httpdriver/addon.go (2)
  • CreateAddonRequest (100-100)
  • UpdateAddonRequest (147-147)
⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: E2E
  • GitHub Check: CI
  • GitHub Check: Quickstart
  • GitHub Check: Developer environment
  • GitHub Check: Lint
  • GitHub Check: Commit hooks
  • GitHub Check: Test
  • GitHub Check: Build
  • GitHub Check: Analyze (go)
🔇 Additional comments (26)
app/common/productcatalog.go (3)

13-15: Importing add-on dependencies looks good.

These imports are neatly organized, and the naming aligns well with the existing patterns for feature and plan modules.


26-26: Wiring Addon into ProductCatalog

Adding Addon to the ProductCatalog wire set is a straightforward change, ensuring the new functionality is properly injected.


37-39: New wire set for Addon

Defining Addon as a wire.NewSet follows the established structure for feature and plan. Consistent naming makes dependency injection more transparent.

cmd/server/wire_gen.go (10)

26-26: Import for add-on module

Adding the import for the add-on package clearly indicates usage within server initialization.


150-151: Addon service initialization

Creating the featureConnector and then addonService is in the correct sequence. This ensures the feature connector is available before the add-on is instantiated.


161-162: Introducing appService

These lines correctly wire the new appService into the initialization flow.


172-174: Tracer, entitlements, & aggregation setup

Loading these configurations in sequence is consistent with the rest of the initialization process.


186-188: Progress manager integration

Using NewProgressManager aligns with the approach used for other services. Proper error checking is in place.


261-262: Billing service with new references

These lines integrate the new appService and look correct for the billing flow.


271-272: NewAppStripeService usage

Passing in the newly created appService plus necessary dependencies is aligned with the approach used for other services in the codebase.


313-314: Sandbox provisioning

The appSandboxProvisioner is created consistently with the rest of the initialization steps, referencing the newly added services.


424-425: Augmenting Application with Addon & App

Including these fields in the Application struct ensures that add-on functionality is exposed in the server configuration.


475-475: New Addon addon.Service field

This field straightforwardly binds the new add-on functionality to the Application struct.

openmeter/productcatalog/addon/adapter/mapping.go (2)

1-15: Package & imports

The package name is consistent with typical Go patterns, and imports are relevant for add-on logic, particularly the lo library for convenience methods.


67-146: Implementing fromAddonRateCardRow

This function handles multiple rate card types with proper error checks and calls Validate(). The robust error messages aid investigation when encountering invalid data.

openmeter/productcatalog/addon/assert.go (1)

106-119: Validate coverage for fallback return in getBillingCadence.

getBillingCadence returns an empty isodate.Period{} in the default case, which might mask unexpected or unhandled rate card types. Ensure there's a test scenario verifying that the default path is never triggered, or handle it more explicitly with a log/error if it's genuinely unexpected.

Consider handling the default case by logging a warning or adding a panic in development mode:

func getBillingCadence(r productcatalog.RateCard) isodate.Period {
	switch vv := r.(type) {
	case *productcatalog.FlatFeeRateCard:
		return lo.FromPtr(vv.BillingCadence)
	case *FlatFeeRateCard:
		return lo.FromPtr(vv.FlatFeeRateCard.BillingCadence)
	case *productcatalog.UsageBasedRateCard:
		return vv.BillingCadence
	case *UsageBasedRateCard:
		return vv.UsageBasedRateCard.BillingCadence
	}
-	return isodate.Period{}
+	// This should never happen, log and return empty period
+	log.Printf("WARNING: Unknown rate card type: %T", r)
+	return isodate.Period{}
}
openmeter/productcatalog/addon/adapter/addon.go (3)

55-57: Confirm empty filter behavior.

When orFilters is empty, the code does not apply the query.Where() call, resulting in all rows being returned if no filter is provided. Double-check that this behavior aligns with your intended logic (returning every add-on for an empty filter set).


158-220: Revisit concurrency and composite uniqueness constraints.

Creation logic doesn't appear to enforce a uniqueness constraint for (namespace, key, version), potentially allowing concurrent operations to insert identical records. This issue was mentioned in a past review.


466-483: Consider partial updates for rate cards rather than a full overwrite.

The existing approach deletes all rate cards and recreates them for updates, which was also highlighted in a past review comment. If partial updates are required in the future, a more granular approach may be preferable.

openmeter/productcatalog/addon/adapter/adapter_test.go (2)

55-309: Good test coverage for core adapter logic.

These test cases provide comprehensive coverage for all primary CRUD operations, effectively validating both happy paths and edge cases.


284-304: Verify soft-deletion retrieval behavior.

After deleting the add-on, the test confirms that GetAddon still retrieves it. Ensure this soft-delete approach is intentional, as many users expect a "not found" result for deleted records.

openmeter/productcatalog/addon/ratecard.go (1)

183-200:

Details

❓ Verification inconclusive

Verify test coverage for Merge operations on rate cards.
The Merge methods for both FlatFeeRateCard and UsageBasedRateCard handle partial updates. It's worth confirming these code paths are tested, especially edge cases like merging different rate card types.

Please ensure that all Merge branches are covered by tests. If needed, you can run coverage commands similar to:

Also applies to: 286-303


🏁 Script executed:

#!/bin/bash
# Searching for tests of ratecard merge logic
rg -A 12 'func Test.*Merge.*RateCard' 

Length of output: 39


Action: Please manually verify that comprehensive tests exist for all Merge branches of rate cards.

The current grep search for tests covering Merge operations returned no results. As a reminder:

  • Confirm that tests are in place for both FlatFeeRateCard and UsageBasedRateCard Merge scenarios, covering various edge cases including merging different rate card types.
  • Verify that modifications in lines 183-200 (and similarly in lines 286-303) are exercised by tests.

If tests are not found, please add the necessary test cases before merging this change.

openmeter/productcatalog/addon/httpdriver/mapping.go (4)

64-64: Address the FIXME to consolidate mappers.

As previously suggested in the past reviews, unifying the HTTP mappers (e.g., from FromAddon, FromRateCard, etc.) in a common package or layer would help reduce duplication and simplify maintenance.


66-202: Refactor the lengthy switch statements.

These switches for different RateCard types are fairly repetitive (e.g., for FlatFee vs. UsageBased). Consider extracting shared logic or using a more centralized approach (e.g., a registry pattern) to reduce duplication and improve readability.


629-637: Validate discount percentage range.

If these discount percentages are limited to a specific range (e.g., 0–100), consider adding validation here to prevent invalid discount values:

+ if d.Percentage < 0 || d.Percentage > 100 {
+   return rc, fmt.Errorf("invalid discount percentage: must be between 0 and 100")
+ }

999-1015: Ensure test coverage for nil UsagePeriod or BillingCadence.

When metered.UsagePeriod is absent (and we fall back to billingCadence), errors arise if both are unset or empty. Please confirm that your test suite covers this edge case and gracefully handles invalid or missing values.

tothandras
tothandras previously approved these changes Apr 4, 2025
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: 5

🧹 Nitpick comments (18)
openmeter/productcatalog/addon/assert.go (1)

67-104: Consider simplifying map creation for better readability.

The map creation using immediately-invoked function expressions (IIFEs) is unnecessarily complex for Go. Consider extracting them to separate functions or simplifying the implementation.

- r1Map := func() map[string]productcatalog.RateCard {
-   m := make(map[string]productcatalog.RateCard, len(r1))
-   for _, v := range r1 {
-     m[v.Key()] = v
-   }
-   
-   return m
- }()
+ r1Map := make(map[string]productcatalog.RateCard, len(r1))
+ for _, v := range r1 {
+   r1Map[v.Key()] = v
+ }

- r2Map := func() map[string]productcatalog.RateCard {
-   m := make(map[string]productcatalog.RateCard, len(r2))
-   for _, v := range r2 {
-     m[v.Key()] = v
-   }
-   
-   return m
- }()
+ r2Map := make(map[string]productcatalog.RateCard, len(r2))
+ for _, v := range r2 {
+   r2Map[v.Key()] = v
+ }
openmeter/productcatalog/addon/adapter/addon.go (1)

405-508: UpdateAddon method functionality.

  1. Validates the request, fetches the existing add-on, and updates only changed attributes.
  2. Deletes existing rate cards entirely and recreates them if RateCards are provided. This “replace all rate cards” pattern can be valid if partial updates are not required but may risk concurrency overwrites.
  3. Returns an error if the updated add-on is not found, which aligns with the code’s approach in other methods.

If partial rate card updates are desirable, consider an approach that merges changes instead of blanket deletion. Otherwise, keep the “replace all” semantics but ensure concurrency control if multiple updates can happen in parallel.

openmeter/productcatalog/addon/adapter/adapter_test.go (2)

84-88: Consider concurrency or connection pooling tests.

The test initializes and uses an adapter directly but doesn't explore potential concurrency issues or connection pool limits. Concurrent creation/update of addons might reveal edge cases.


306-441: Ensure comprehensive coverage of scheduled transitions.

The testListPlanStatusFilter subtest provides robust coverage of different status transitions. Consider adding edge case tests for effective periods that straddle boundary conditions (e.g., immediate transition, large time gaps, invalid periods).

openmeter/productcatalog/addon/service.go (2)

50-51: Clarify pagination requirement.

A TODO indicates that pagination might be optional. Consider updating the code or documentation to reflect whether pagination is mandatory or truly optional, and ensure callers handle the results accordingly.


291-291: Consider clarifying time-based behavior for ArchiveAddonInput.

This struct only defines EffectiveTo, which indicates unpublishing at a specific time. If you plan multiple transitions (draft → active → archived) or partial archives, consider also storing EffectiveFrom or clarifying how partial intervals are handled.

openmeter/productcatalog/addon.go (1)

55-57: Use clock.Now() for consistency.

While Status calls time.Now(), other parts of the codebase rely on clock.Now(). Switching to clock.Now() ensures consistent and testable time references across the application.

openmeter/productcatalog/addon/ratecard.go (1)

19-37: Ensure AddonID comparison isn't duplicated elsewhere.

RateCardManagedFields.Equal() already compares AddonID. Double-check if FlatFeeRateCard.Equal() and UsageBasedRateCard.Equal() re-compare the same field, which could be redundant.

openmeter/productcatalog/addon/ratecard_test.go (1)

176-316: Validation test coverage is good.

Both valid and invalid scenarios are nicely tested for FlatFeeRateCard. Consider additional edge cases (e.g., extremely large Amount), if relevant to domain constraints.

openmeter/productcatalog/addon/httpdriver/mapping.go (1)

66-202: Switch statement is lengthy but clear.

FromRateCard leverages a type-based switch. Though repetitive, it’s still readable. A registry-based approach could reduce duplication. This aligns with the existing refactor note.

openmeter/productcatalog/addon/event.go (3)

31-56: Consider defensive checks in EventMetadata.
AddonCreateEvent.EventMetadata() may be called before Validate(), which could lead to a runtime error if e.Addon is nil. Though Validate() ensures Addon is non-nil, defensive checks or clear documentation to call Validate() first could prevent unexpected usage.


131-140: Zero-time fallback in delete event metadata.
Using lo.FromPtr(e.Addon.DeletedAt) prevents nil pointer dereferences, which is good. However, if DeletedAt is unexpectedly nil, this defaults to Unix epoch (zero time). Consider logging or clearly documenting this fallback behavior or returning an error to avoid silently assigning an unintended timestamp.


221-230: Validate e.Addon before referencing its fields.
Similar to other events, AddonArchiveEvent.EventMetadata() references e.Addon.Namespace unconditionally. Although Validate() checks for nil, ensuring that EventMetadata() does not get called when e.Addon is nil relies on convention. A short guard clause can protect against accidental misuse.

openmeter/productcatalog/addon/httpdriver/addon.go (2)

296-315: Revisit the TODO for PublishAddon.
There's a TODO(chrisgacsal) suggesting a design improvement for setting EffectivePeriod. If left unresolved, the published add-ons might not reflect the intended effective date range. Consider whether we should open a separate tracking issue or implement the fix now.


319-319: Typographical error in the error message.
There is a small spelling mistake: "failed to punlish add-on" instead of "publish". This could potentially confuse error logs.

- return PublishAddonResponse{}, fmt.Errorf("failed to punlish add-on [namespace=%s id=%s]: %w", request.Namespace, request.ID, err)
+ return PublishAddonResponse{}, fmt.Errorf("failed to publish add-on [namespace=%s id=%s]: %w", request.Namespace, request.ID, err)
openmeter/productcatalog/addon/service/addon.go (3)

30-78: Handle concurrency for multi-rate-card feature expansion.
expandFeatures merges and expands feature data for multiple rate cards. If multiple goroutines process expansions concurrently for the same features, consider concurrency control or clarifying that it is safe under concurrent use scenarios.


317-419: Potential concurrency for publish and archive operations.
Within PublishAddon, you check for a previously active add-on and then archive it. If two concurrent publish requests happen for the same key, data consistency issues could arise. It may be beneficial to add concurrency controls or unique constraints at the DB level.


482-600: Improve clarity in next version creation logic.
While NextAddon successfully enforces no multiple drafts, the approach with sourceAddonFilterFunc and multiple iteration checks can be slightly complex. Extracting filter logic into a helper method or clarifying the branching paths could enhance readability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e8b13c and 0d026b1.

📒 Files selected for processing (25)
  • app/common/productcatalog.go (4 hunks)
  • cmd/server/main.go (1 hunks)
  • cmd/server/wire.go (2 hunks)
  • cmd/server/wire_gen.go (14 hunks)
  • openmeter/event/metadata/resourcepath.go (1 hunks)
  • openmeter/productcatalog/addon.go (1 hunks)
  • openmeter/productcatalog/addon/adapter/adapter.go (1 hunks)
  • openmeter/productcatalog/addon/adapter/adapter_test.go (1 hunks)
  • openmeter/productcatalog/addon/adapter/addon.go (1 hunks)
  • openmeter/productcatalog/addon/adapter/mapping.go (1 hunks)
  • openmeter/productcatalog/addon/addon.go (1 hunks)
  • openmeter/productcatalog/addon/assert.go (1 hunks)
  • openmeter/productcatalog/addon/errors.go (1 hunks)
  • openmeter/productcatalog/addon/errors_test.go (1 hunks)
  • openmeter/productcatalog/addon/event.go (1 hunks)
  • openmeter/productcatalog/addon/httpdriver/addon.go (1 hunks)
  • openmeter/productcatalog/addon/httpdriver/driver.go (1 hunks)
  • openmeter/productcatalog/addon/httpdriver/errors.go (1 hunks)
  • openmeter/productcatalog/addon/httpdriver/mapping.go (1 hunks)
  • openmeter/productcatalog/addon/ratecard.go (1 hunks)
  • openmeter/productcatalog/addon/ratecard_test.go (1 hunks)
  • openmeter/productcatalog/addon/repository.go (1 hunks)
  • openmeter/productcatalog/addon/service.go (1 hunks)
  • openmeter/productcatalog/addon/service/addon.go (1 hunks)
  • openmeter/productcatalog/addon/service/service.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (12)
  • openmeter/productcatalog/addon/httpdriver/errors.go
  • openmeter/event/metadata/resourcepath.go
  • cmd/server/main.go
  • cmd/server/wire.go
  • openmeter/productcatalog/addon/service/service.go
  • openmeter/productcatalog/addon/adapter/adapter.go
  • openmeter/productcatalog/addon/httpdriver/driver.go
  • openmeter/productcatalog/addon/addon.go
  • openmeter/productcatalog/addon/errors.go
  • openmeter/productcatalog/addon/adapter/mapping.go
  • openmeter/productcatalog/addon/errors_test.go
  • openmeter/productcatalog/addon/repository.go
🧰 Additional context used
🧬 Code Definitions (6)
openmeter/productcatalog/addon/adapter/adapter_test.go (4)
openmeter/productcatalog/addon/service.go (3)
  • CreateAddonInput (98-101)
  • GetAddonInput (198-211)
  • UpdateAddonInput (122-142)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/productcatalog/addon/service/service_test.go (1)
  • MonthPeriod (303-303)
openmeter/productcatalog/addon/assert.go (3)
  • AssertAddonCreateInputEqual (14-26)
  • AssertAddonEqual (54-65)
  • AssertAddonUpdateInputEqual (28-52)
cmd/server/wire_gen.go (5)
openmeter/productcatalog/feature/connector.go (1)
  • NewFeatureConnector (84-100)
app/common/progressmanager.go (2)
  • ProgressManager (14-16)
  • NewProgressManager (19-39)
app/common/streaming.go (1)
  • NewStreamingConnector (21-43)
app/common/entitlement.go (1)
  • NewEntitlementRegistry (22-40)
openmeter/app/service/service.go (1)
  • Service (12-15)
openmeter/productcatalog/addon/assert.go (4)
openmeter/productcatalog/addon/service.go (2)
  • CreateAddonInput (98-101)
  • UpdateAddonInput (122-142)
openmeter/productcatalog/addon.go (1)
  • Addon (177-182)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/productcatalog/ratecard.go (4)
  • RateCards (350-350)
  • RateCard (28-38)
  • FlatFeeRateCard (161-168)
  • UsageBasedRateCard (256-262)
openmeter/productcatalog/addon/ratecard.go (3)
openmeter/productcatalog/addon/adapter/adapter.go (1)
  • New (42-51)
openmeter/productcatalog/addon/service/service.go (1)
  • New (19-42)
openmeter/productcatalog/ratecard.go (6)
  • FlatFeeRateCard (161-168)
  • UsageBasedRateCard (256-262)
  • RateCard (28-38)
  • RateCardSerde (40-42)
  • FlatFeeRateCardType (15-15)
  • UsageBasedRateCardType (16-16)
openmeter/productcatalog/addon/service.go (3)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
openmeter/productcatalog/ratecard.go (1)
  • RateCards (350-350)
openmeter/productcatalog/addon/service/service.go (1)
  • New (19-42)
openmeter/productcatalog/addon.go (2)
openmeter/productcatalog/ratecard.go (2)
  • RateCards (350-350)
  • RateCard (28-38)
openmeter/productcatalog/addon/addon.go (1)
  • Addon (13-21)
⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: Quickstart
  • GitHub Check: E2E
  • GitHub Check: CI
  • GitHub Check: Developer environment
  • GitHub Check: Commit hooks
  • GitHub Check: Test
  • GitHub Check: Build
  • GitHub Check: Analyze (go)
  • GitHub Check: Lint
🔇 Additional comments (55)
openmeter/productcatalog/addon/assert.go (6)

1-12: LGTM: Clear and concise imports.

The imports for this test assertion utility are well-organized and include all necessary packages for test assertions, utilities, and internal dependencies.


14-26: Good implementation of the helper function for testing create inputs.

The function properly compares all fields between CreateAddonInput and Addon instances, with clear error messages. The use of t.Helper() is correct for test utilities.


28-52: Appropriate handling of optional fields in update assertions.

The function correctly handles nil pointers in the UpdateAddonInput by only making assertions when fields are present, and properly uses lo.FromPtrOr to handle default values.


54-65: LGTM: Addon comparison is thorough and well-structured.

This function properly compares all relevant fields between two Addon instances and delegates rate card comparison to a specialized function.


106-119: Validate coverage for fallback return in getBillingCadence.

getBillingCadence returns an empty isodate.Period{} in the default case, which might mask unexpected or unhandled rate card types. Ensure there's a test scenario verifying that the default path is never triggered, or handle it more explicitly with a log/error if it's genuinely unexpected.


121-151: LGTM: Comprehensive rate card equality implementation.

The function properly checks all relevant fields from both rate cards, including metadata, feature information, and billing cadence. It handles optionality well using lo.FromPtr.

app/common/productcatalog.go (4)

13-15: Imports appear correct and consistent with module structure.
They properly reference the newly introduced add-on packages required for adapting and handling add-on logic, which aligns with the newly created wire set and service below.


26-26: Addition of Addon to the ProductCatalog wire set.
Linking Addon into the ProductCatalog composition ensures the add-on service is initialized alongside other product catalog functionality.


37-39: Creation of the Addon wire set.
Defining a dedicated wire set for add-ons isolates the new service from the existing plan and feature sets, mirroring the structure used for Plan and Feature. This encourages modular design.


73-94: New NewAddonService function.
The function correctly initializes the adapter and service, returning an error if adapter creation fails. This design follows the same pattern used for NewPlanService, promoting consistency in initialization logic.

cmd/server/wire_gen.go (14)

26-26: Added import for the new addon package.
This import is necessary for declaring the Addon field in the Application struct and wiring the add-on service.


90-99: Meter adapter and service creation logic.
The error handling sequence with multiple cleanups is consistent with the rest of the file. Initializing the meter adapter first, then passing it into NewMeterService, logically separates adapter creation from higher-level service usage.


150-151: Instantiation of featureConnector and addonService.
These calls are properly sequenced to ensure featureConnector is constructed before being passed to NewAddonService. The error handling block is thorough, matching the existing cleanup pattern.


161-171: appsConfiguration extraction and appService initialization.
The approach is consistent with other top-level services: retrieve configuration, pass it into the service builder, and handle errors with staged cleanups.


172-185: Telemetry and ClickHouse aggregator initialization.
The code thoroughly handles potential failures by cascading cleanups. This ensures no partial resource leaks if any step fails.


186-196: Progress manager initialization.
Properly checks the ProgressManagerConfiguration, then gracefully falls back or errors out as needed. The code’s structure is consistent with other initializations.


197-206: Creates the streaming connector with NewStreamingConnector.
Again, the staged approach ensures no leftover resources upon error. This is aligned with best practices for dependency injection in this codebase.


207-217: Entitlement registry creation.
Attaches the existing meterService and eventbusPublisher. This helps unify the newly integrated modules (such as add-ons) with the rest of the system’s entitlement logic.


261-270: Billing service creation references new appService and add-on dependencies.
The interplay between billingService, featureConnector, and the new service ensures add-on data can flow through existing billing flows.


271-280: appstripeService initialization.
Accepts the new appService dependency for integration with Stripe. The approach mirrors the existing pattern for third-party service modules.


313-321: appSandboxProvisioner creation.
Includes both appService and billingService, ensuring that sandbox environments can now also be provisioned with add-on capabilities if needed.


367-367: metereventService references the new service.
Ensures meter events can be integrated with the final, fully configured meter service instance—this preserves a consistent data flow for event ingestion.


421-425: Populating the Application struct with the new fields.
The new fields Addon: addonService and App: appService are consistent with the rest of the struct’s pattern. Including them in Application unifies usage under a single interface.


475-475: Declaration of Addon addon.Service within Application.
The newly introduced service is now fully integrated into the top-level application scope, ensuring it can be injected or consumed wherever necessary throughout the system.

openmeter/productcatalog/addon/adapter/addon.go (6)

1-22: New file for add-on adapter package initialization.
This file introduces the core logic and imports needed to support add-ons, providing an independent adapter layer. The package structure and imports appear consistent with existing product catalog patterns.


158-220: CreateAddon method implementation.

  1. Validates input parameters and sets a default version if none is specified.
  2. The logic inserts the main add-on entity, then bulk-creates associated rate cards.
  3. Refetches the newly created add-on with eager-loaded rate cards, ensuring a fully populated result.
  4. As noted in past reviews, consider enforcing uniqueness constraints for (namespace, key, version) if required to prevent concurrency issues and duplicates.

Would you like to check schema files to confirm that (namespace, key, version) is indeed enforced as unique? I can provide a script to search the schema for composite uniqueness if so.


222-257: rateCardBulkCreate utility function.

  1. Transforms user-provided rate card definitions into the ent creation statements in bulk, which is efficient.
  2. Consider partial updates if you wish to preserve existing rate cards, as the current logic in UpdateAddon fully deletes & re-inserts them.

259-304: DeleteAddon method implementation.

  1. Marks the add-on as deleted by setting DeletedAt, rather than fully removing the record. This is a safer “soft delete” approach.
  2. The function reuses GetAddon to ensure the entity exists before deletion.

306-403: GetAddon method implementation.

  1. Handles lookup either by explicit ID, or by (key, version) (or “latest/active” if version is zero).
  2. Utilizes custom SQL selectors for retrieving the latest version or an active add-on. This ensures flexible queries.
  3. Eager-loads rate cards for a complete view of the add-on.

510-515: AddonEagerLoadRateCardsFn eager load configuration.

  1. Filters out all rate cards that have a DeletedAt set in the past, effectively skipping rate cards considered “deleted.”
  2. This ensures the returned add-on only includes currently valid or future-dated rate cards.
openmeter/productcatalog/addon/adapter/adapter_test.go (1)

285-304: Verify soft-delete behavior.

After calling DeleteAddon, the test still retrieves the same addon without error. This suggests a soft-delete or "archived" logic rather than full removal. If the intent is to remove the addon completely, ensure the repository methods match that expectation.

openmeter/productcatalog/addon/ratecard.go (5)

39-55: Validation logic looks solid.

The structured approach to accumulating errors is good. Consider clarifying whether partial validation is acceptable if multiple fields are missing to improve error traceability.


57-59: Interface is clear.

Having a dedicated ManagedRateCard interface clarifies design intent and fosters extensibility.


61-98: Watch for pointer vs. value usage in generics.

NewRateCardFrom[T …](v T) uses a type switch with non-pointer cases (FlatFeeRateCard, UsageBasedRateCard). This is fine but ensure external calls don’t pass pointers inadvertently. A runtime mismatch could lead to subtle bugs.

Would you like to check the entire codebase to confirm we only pass values?


100-200: Consider removing the repeated AddonID check.

FlatFeeRateCard.Equal() repeats AddonID comparison, although RateCardManagedFields.Equal() does the same. Removing the duplicate comparison would simplify maintenance.
[ suggest_nitpick ]

-        if r.AddonID != vv.AddonID {
-            return false
-        }

202-303: Usage-based rate card mirroring is consistent.

The mirroring of logic from FlatFeeRateCard to UsageBasedRateCard is consistent. Watch out for duplication in Equal() and Merge() methods that partially replicate checks already done by embedded fields.

openmeter/productcatalog/addon/ratecard_test.go (2)

19-174: Comprehensive JSON serialization tests.

The test covers both flat-fee and usage-based JSON (de)serialization thoroughly. This ensures resilience against future encoding changes.


318-473: Usage-based tests parallel flat-fee coverage.

The usage-based validation testing follows a similar pattern. This consistency makes test maintenance simpler. No major issues.

openmeter/productcatalog/addon/httpdriver/mapping.go (14)

23-62: Solid mapping from domain model to API.

FromAddon properly handles fields and funneling rate-card conversions. The status check and error handling are sound.


64-64: Refactor is already noted.

The “FIXME” echoes the past suggestion to consolidate common mappers.


204-321: Usage-based price decomposition is well-structured.

FromRateCardUsageBasedPrice systematically branches each price type. Potential expansions (like new price types) will be easy to integrate.


323-347: Tax config and payment term mappings are straightforward.

Both functions are concise and robust. The fallback to InAdvance is sensible when uncertain.


349-476: Entitlement & discount mapping clarifications.

Functions FromEntitlementTemplate, FromDiscounts, and FromDiscountPercentages handle multiple subtypes. Error messages are clear, ensuring easier debugging.


478-526: Create request object is well-validated.

AsCreateAddonRequest ensures currency is valid and rate cards are parsed. This helps prevent invalid data from entering the system.


528-562: Controlled type switching in AsRateCard.

The approach neatly funnels into specialized transformation functions. This design fosters maintainability.


564-640: Flat-fee mapping is well-tested.

AsFlatFeeRateCard gracefully handles the optional fields (BillingCadence, FeatureKey, etc.) with minimal branching. Looks good.


642-698: Usage-based mapping matches domain usage.

Checks for BillingCadence and usage-specific fields are consistent with FlatFeeRateCard. Great alignment between the two.


700-743: Discount array transformations.

AsDiscounts picks the correct discount sub-type, ensuring domain rules are upheld. Errors on invalid types are explicit.


745-939: Extensive price variant handling.

AsPrice covers many price types. The code remains comprehensible, though it’s fairly long. The approach is thorough.


941-980: Tier logic is concise.

AsPriceTier sets up tier boundaries and optional flat or unit price. The decimal parsing is done carefully, avoiding silent failures.


982-1069: Entitlement template transformations.

AsEntitlementTemplate nicely merges domain constraints (e.g., billingCadence fallback). The error messaging is robust.


1071-1092: Update request object patterns mirror creation.

AsUpdateAddonRequest extends or overrides only the fields changed. Good approach for partial updates.

openmeter/productcatalog/addon/event.go (1)

178-187: Consistency check for publish event timestamp.
AddonPublishEvent sets the metadata time to e.Addon.UpdatedAt while other events set it to their respective fields (CreatedAt, DeletedAt). If e.Addon.UpdatedAt can be zero or out of date, consider whether a real-time timestamp or a safer fallback (time.Now()) is needed.

openmeter/productcatalog/addon/httpdriver/addon.go (1)

31-63: Validate query parameters thoroughly.
Within ListAddons, parameters such as params.Status, params.KeyVersion, etc., are directly mapped into slice fields. Although they are sanitized here, ensure that the underlying repository or service layer also validates them to prevent invalid or malicious inputs from impacting the system.

openmeter/productcatalog/addon/service/addon.go (1)

80-149: Validate Version increments more robustly.
When creating a new add-on, the logic increments params.Version if an existing version is higher. Ensure there are no race conditions where two requests create new add-ons simultaneously, leading to conflicting version numbers.

field.String("addon_id").
NotEmpty().
Comment("The add-on identifier the ratecard is assigned to."),
field.String("feature_id").
Copy link
Contributor

Choose a reason for hiding this comment

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

I will be posing this question once again, shouldn't we instead be referencing the feature only by key (&then if we purchase the Addon then resolve it to an ID?)

I know this is not how it works for plan, just stirring the pot here...

Comment on lines +61 to +62
from := lo.FromPtrOr(p.EffectiveFrom, time.Time{})
to := lo.FromPtrOr(p.EffectiveTo, time.Time{})
Copy link
Contributor

Choose a reason for hiding this comment

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

You could just do the nil checks as they're already nillable & then you wouldn't need IsZero's, eg then line 70 wouldn't need (to.Before(t) && from.Before(to)) just to.Before(t)

}

result := make([]addon.Addon, 0, len(paged.Items))
for _, item := range paged.Items {
Copy link
Contributor

Choose a reason for hiding this comment

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

FYI i've written a util for this on my branch, i'll rewrite these to use that once checked in

// WHERE namespace = '$1' and key = '$2'
// GROUP BY (namespace, key)
// )
query = query.Where(func(s *sql.Selector) {
Copy link
Contributor

Choose a reason for hiding this comment

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

As much as I dislike that pattern, should we use the query templating used for clickhouse too? (e.g. mySubQuery... then you can test how it templates, so we don't rely on comments?)

}

// If there are add-on versions with the same key do:
// * check their statuses to ensure that new plan with the same key is created only
Copy link
Contributor

Choose a reason for hiding this comment

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

*new addon

}

if aa.Version >= params.Version {
params.Version = aa.Version + 1
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't we error here? I think we should

return nil, fmt.Errorf("failed to get add-on: %w", err)
}

allowedStatuses := []productcatalog.AddonStatus{
Copy link
Contributor

Choose a reason for hiding this comment

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

just a thought, we could use a simple statemachine with transitions for it (similar to billing & subscriptions)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Agreed. I just wanted to do the refactor to use statemachine for addon and plan services in one go as they are pretty much the smae when it comes to business logic.

@@ -365,6 +365,22 @@ func (c RateCards) Billables() RateCards {
return billables
}

func (c RateCards) IsAligned() bool {
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's not call this alignment as Alignment means something else (aligning the periods to a shared phase start compared to item activations). This is a prerequisite of alignment.

@chrisgacsal
Copy link
Collaborator Author

@GAlexIHU Thanks for your review. I'll address your comments in a follow-up PR.

@chrisgacsal chrisgacsal merged commit b1abf4d into main Apr 4, 2025
30 checks passed
@chrisgacsal chrisgacsal deleted the feat/addons-api branch April 4, 2025 12:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/api release-note/feature Release note: Exciting New Features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants