Skip to content

Conversation

@turip
Copy link
Member

@turip turip commented Apr 30, 2025

Overview

This patch adds invoice update events.

This patch provides a friendlier representation of the App type in terms of Events:

The app is not json unmarshalable, this change makes sure we have an interim representation that can travel on the event bus and can be deserialized.

Given the current structure of the code, we are using an map[string]any encoding for the app data, and the apps are expected to provide this data. We are relying on the json library for the conversion for now, so that we don't have to create custom mappers.

Summary by CodeRabbit

  • New Features

    • Enhanced event data structures for apps and invoices, enabling richer, JSON-serializable app-specific data in events.
    • Introduced invoice updated events and integrated event emission during invoice state transitions.
    • Added API mapping functions for event-based app and invoice data representations.
    • Added explicit error handling for event creation and publishing in billing services.
  • Bug Fixes

    • Fixed field naming inconsistencies and improved data handling in event and invoice structures.
  • Refactor

    • Refactored app structs to embed new metadata types implementing event parsing interfaces.
    • Updated event constructors and handlers to support new event app data patterns.
    • Streamlined test utilities for draft invoice creation with improved input validation.
  • Tests

    • Added and updated test suites verifying event creation, serialization, and app data integrity.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Apr 30, 2025

📝 Walkthrough

Walkthrough

This change introduces a new event data abstraction for app entities, extending the App interface with a GetEventAppData() method and refactoring app implementations (custom invoicing, sandbox, stripe) to embed a new Meta struct that handles event app data parsing and serialization. The event types for app updates and deletes are updated to carry richer, structured app data, and corresponding event creation logic is updated to handle errors and use the new abstraction. The billing event system is updated to carry full app event data in invoice events, and new mapping functions are added to convert event app and invoice data to API representations. Related test suites and helpers are updated to accommodate these structural changes.

Changes

Files/Paths Change Summary
openmeter/app/app.go Extended App interface with GetEventAppData() method.
openmeter/app/custominvoicing/app.go,
openmeter/app/stripe/entity/app/app.go,
openmeter/app/sandbox/app.go
Refactored app structs to embed a new Meta struct implementing EventAppParser and added GetEventAppData() methods.
openmeter/app/custominvoicing/factory.go,
openmeter/app/stripe/service/factory.go,
openmeter/app/sandbox/app.go
Updated factory methods to construct apps using the new Meta struct.
openmeter/app/event.go Introduced EventApp, EventAppParser, and EventAppData abstractions; updated app event types to use EventApp and handle richer app data; changed event versioning.
openmeter/app/httpdriver/customer.go,
openmeter/app/httpdriver/mapper.go
Updated mapping functions to use .Meta field and handle new event app data structure; added MapEventAppToAPI.
openmeter/app/sandbox/mock.go Added GetEventAppData() to mock app instance.
openmeter/app/service/app.go Updated event creation and emission logic to use new event app abstraction and handle errors.
openmeter/app/stripe/entity/input.go Added JSON struct tags to AppData fields.
openmeter/billing/events.go Replaced AppBase with EventApp in invoice events, updated event creation functions to handle errors, and introduced InvoiceUpdatedEvent.
openmeter/billing/httpdriver/invoice.go Added MapEventInvoiceToAPI to map event invoices with app data to API representation.
openmeter/billing/service/invoice.go,
openmeter/billing/service/invoiceline.go
Added error handling for event creation before publishing invoice events.
openmeter/billing/service/invoicestate.go Added event emission for invoice state transitions; introduced Publisher field to state machine.
test/app/custominvoicing/event_test.go Added tests for custom invoicing event creation and serialization.
test/billing/event_test.go Updated tests to handle new event structure and error handling in event creation.
test/billing/invoice_test.go Removed legacy draft invoice helpers; updated tests to use new CreateDraftInvoice method.
test/billing/suite.go Added DraftInvoiceInput struct and CreateDraftInvoice helper to support test changes.

Possibly related PRs

  • openmeterio/openmeter#2705: Also introduces a Meta struct for custom invoicing app, event app parsing, and GetEventAppData methods, directly overlapping with the main PR's code-level changes.
  • openmeterio/openmeter#2678: Introduces the custom invoicing app type and its API schema support, which is foundational for the event and API mapping changes in this PR.

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


📜 Recent review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between ed3228b and d96c47b.

📒 Files selected for processing (2)
  • test/app/custominvoicing/event_test.go (1 hunks)
  • test/billing/event_test.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • test/app/custominvoicing/event_test.go
  • test/billing/event_test.go
⏰ Context from checks skipped due to timeout of 90000ms (13)
  • GitHub Check: Artifacts / Container image
  • GitHub Check: Artifacts / Benthos Collector Container image
  • GitHub Check: Test
  • GitHub Check: Quickstart
  • GitHub Check: Migration Checks
  • GitHub Check: Lint
  • GitHub Check: E2E
  • GitHub Check: Developer environment
  • GitHub Check: Build
  • GitHub Check: Commit hooks
  • GitHub Check: CI
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Analyze (go)
✨ Finishing Touches
  • 📝 Generate Docstrings

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

❤️ Share
🪧 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 generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

@turip turip force-pushed the fix/allow-marshaling-event-invoice-into-api-types branch from dd61bc0 to 9fb7916 Compare April 30, 2025 09:27
@turip turip marked this pull request as ready for review April 30, 2025 09:28
@turip turip requested a review from a team as a code owner April 30, 2025 09:28
@turip turip requested a review from chrisgacsal April 30, 2025 09:28
@turip turip added the release-note/misc Miscellaneous changes label Apr 30, 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: 6

🔭 Outside diff range comments (2)
openmeter/app/httpdriver/mapper.go (1)

16-35: ⚠️ Potential issue

Type assertion may panic when the concrete app is a pointer

MapAppToAPI asserts the incoming app.App to value types.
If any caller happens to store a *appsandbox.App (pointer) in the interface the assertion will fail at runtime.

A safer pattern is a type-switch that handles both forms:

 switch item.GetType() {
 case app.AppTypeStripe:
-	stripeApp := item.(appstripeentityapp.App)
+	switch v := item.(type) {
+	case appstripeentityapp.App:
+		stripeApp = v
+	case *appstripeentityapp.App:
+		stripeApp = *v
+	default:
+		return api.App{}, fmt.Errorf("unexpected concrete type for Stripe app: %T", item)
+	}

Apply the same approach to the Sandbox and Custom-Invoicing branches.

openmeter/app/event.go (1)

192-200: ⚠️ Potential issue

Nil-pointer dereference when DeletedAt is unset

AppDeleteEvent.EventMetadata() unconditionally dereferences e.AppBase.DeletedAt, which will panic if the field is nil (e.g. if someone manually crafts or migrates an event without setting the timestamp).

-        Time:    *e.AppBase.DeletedAt,
+        Time: func() time.Time {
+            if e.AppBase.DeletedAt != nil {
+                return *e.AppBase.DeletedAt
+            }
+            // Fallback – choose whichever strategy fits your domain better.
+            // Returning `time.Now()` keeps the event valid; returning `time.Time{}` surfaces the error to consumers.
+            return time.Now()
+        }(),

At minimum, guard the dereference or extend Validate() to assert DeletedAt != nil before this method is called.

🧹 Nitpick comments (7)
openmeter/app/custominvoicing/factory.go (1)

120-120: Variable renamed for clarity.

Changed variable name from appBase to newApp to better reflect that it's a complete app instance rather than just the base fields.

openmeter/billing/service/invoice.go (1)

473-481: Improved error handling for invoice event creation

The code now properly separates event creation from publishing with explicit error handling. This is a good practice that ensures events are only published if they can be successfully created.

Consider adding a debug log statement before publishing the event to help with troubleshooting. This would provide visibility into the event creation process.

openmeter/billing/service/invoicestate.go (1)

249-249: Publisher properly initialized

The Publisher field is correctly assigned from the service's publisher in the WithInvoiceStateMachine method.

Consider also clearing this field in the defer block at line 254 to avoid potential resource leaks:

defer func() {
    sm.Invoice = billing.Invoice{}
    sm.Calculator = nil
+   sm.Publisher = nil
    invoiceStateMachineCache.Put(sm)
}()
test/billing/suite.go (2)

292-296: Namespace field is never used — remove or honour it

CreateDraftInvoice ignores in.Namespace and always derives the namespace from in.Customer.Namespace.
Either drop the redundant field or decide which one is the authoritative source and validate that they match to avoid silent inconsistencies.


313-316: Use the project clock helper for determinism

time.Now() makes the helper non-deterministic and harder to debug.
Elsewhere the codebase relies on clock.Now(); re-using it keeps tests consistent and simplifies time-based assertions.

openmeter/app/sandbox/app.go (1)

154-156: Return nil instead of an empty map to minimise event payload size

GetEventAppData currently returns app.EventAppData{} which serialises to {} and will always be emitted, even though Sandbox has no app-specific data.
Returning nil allows the field to be omitted (provided the struct tag is omitempty in the parent), trimming event size.

-func (a App) GetEventAppData() (app.EventAppData, error) {
-	return app.EventAppData{}, nil
+func (a App) GetEventAppData() (app.EventAppData, error) {
+	return nil, nil
 }
openmeter/app/event.go (1)

24-36: Avoid double JSON round-trips when input is already a map

NewEventAppData marshals, then immediately unmarshals, even if v is already a map[string]any or json.RawMessage. That costs allocations and can coerce numbers to float64.

Consider a fast-path:

 func NewEventAppData(v any) (EventAppData, error) {
-    jsonBytes, err := json.Marshal(v)
-    if err != nil {
-        return nil, err
-    }
-
-    var data EventAppData
-    if err := json.Unmarshal(jsonBytes, &data); err != nil {
-        return nil, err
-    }
-
-    return data, nil
+    switch x := v.(type) {
+    case map[string]any:
+        return EventAppData(x), nil
+    case EventAppData:
+        return x, nil
+    default:
+        var data EventAppData
+        jsonBytes, err := json.Marshal(v)
+        if err != nil {
+            return nil, err
+        }
+        if err := json.Unmarshal(jsonBytes, &data); err != nil {
+            return nil, err
+        }
+        return data, nil
+    }
 }

This keeps the flexible API while eliminating unnecessary work in the common case.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0824bd7 and 9fb7916.

📒 Files selected for processing (21)
  • openmeter/app/app.go (1 hunks)
  • openmeter/app/custominvoicing/app.go (2 hunks)
  • openmeter/app/custominvoicing/factory.go (2 hunks)
  • openmeter/app/event.go (6 hunks)
  • openmeter/app/httpdriver/customer.go (3 hunks)
  • openmeter/app/httpdriver/mapper.go (7 hunks)
  • openmeter/app/sandbox/app.go (4 hunks)
  • openmeter/app/sandbox/mock.go (1 hunks)
  • openmeter/app/service/app.go (3 hunks)
  • openmeter/app/stripe/entity/app/app.go (2 hunks)
  • openmeter/app/stripe/entity/input.go (1 hunks)
  • openmeter/app/stripe/service/factory.go (1 hunks)
  • openmeter/billing/events.go (5 hunks)
  • openmeter/billing/httpdriver/invoice.go (2 hunks)
  • openmeter/billing/service/invoice.go (1 hunks)
  • openmeter/billing/service/invoiceline.go (1 hunks)
  • openmeter/billing/service/invoicestate.go (4 hunks)
  • test/app/custominvoicing/event_test.go (1 hunks)
  • test/billing/event_test.go (2 hunks)
  • test/billing/invoice_test.go (3 hunks)
  • test/billing/suite.go (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (14)
openmeter/app/sandbox/mock.go (1)
openmeter/app/event.go (1)
  • EventAppData (19-19)
openmeter/billing/service/invoiceline.go (1)
openmeter/billing/events.go (1)
  • NewInvoiceCreatedEvent (97-104)
openmeter/app/app.go (1)
openmeter/app/event.go (1)
  • EventAppData (19-19)
openmeter/app/service/app.go (1)
openmeter/app/event.go (2)
  • NewAppUpdateEvent (121-131)
  • NewAppDeleteEvent (168-176)
openmeter/app/custominvoicing/factory.go (3)
openmeter/app/custominvoicing/app.go (2)
  • Meta (39-42)
  • Configuration (25-28)
openmeter/app/appbase.go (1)
  • AppBase (38-46)
openmeter/app/app.go (1)
  • CreateAppInput (145-152)
test/billing/event_test.go (3)
test/billing/suite.go (1)
  • DraftInvoiceInput (293-296)
openmeter/billing/events.go (1)
  • NewInvoiceCreatedEvent (97-104)
openmeter/app/stripe/entity/input.go (1)
  • AppBase (409-412)
openmeter/billing/httpdriver/invoice.go (3)
openmeter/billing/events.go (1)
  • EventInvoice (39-42)
api/api.gen.go (3)
  • Invoice (3240-3348)
  • BillingProfileApps (1276-1285)
  • BillingProfileAppsOrReference (1301-1303)
openmeter/app/httpdriver/mapper.go (1)
  • MapEventAppToAPI (110-151)
openmeter/billing/service/invoice.go (1)
openmeter/billing/events.go (1)
  • NewInvoiceCreatedEvent (97-104)
openmeter/app/sandbox/app.go (4)
openmeter/app/appbase.go (1)
  • AppBase (38-46)
openmeter/app/event.go (3)
  • EventAppParser (15-17)
  • EventApp (52-55)
  • EventAppData (19-19)
openmeter/app/custominvoicing/app.go (2)
  • Meta (39-42)
  • App (56-61)
openmeter/app/app.go (1)
  • App (14-35)
openmeter/app/stripe/entity/input.go (2)
openmeter/ent/db/appstripe/where.go (5)
  • StripeAccountID (89-91)
  • APIKey (99-101)
  • MaskedAPIKey (104-106)
  • StripeWebhookID (109-111)
  • WebhookSecret (114-116)
openmeter/secret/entity/secret.go (1)
  • SecretID (12-16)
openmeter/app/custominvoicing/app.go (4)
openmeter/app/appbase.go (1)
  • AppBase (38-46)
openmeter/app/event.go (4)
  • EventAppParser (15-17)
  • EventApp (52-55)
  • EventAppData (19-19)
  • NewEventAppData (24-36)
openmeter/app/sandbox/app.go (2)
  • Meta (36-38)
  • App (48-52)
openmeter/app/app.go (1)
  • App (14-35)
openmeter/billing/service/invoicestate.go (2)
openmeter/watermill/eventbus/eventbus.go (1)
  • Publisher (61-73)
openmeter/billing/events.go (1)
  • NewInvoiceUpdatedEvent (129-136)
test/app/custominvoicing/event_test.go (4)
test/app/custominvoicing/invocing_test.go (1)
  • CustomInvoicingTestSuite (30-34)
openmeter/app/custominvoicing/app.go (2)
  • Configuration (25-28)
  • Meta (39-42)
test/billing/suite.go (1)
  • DraftInvoiceInput (293-296)
openmeter/billing/events.go (2)
  • NewInvoiceCreatedEvent (97-104)
  • InvoiceCreatedEvent (93-95)
test/billing/invoice_test.go (1)
test/billing/suite.go (1)
  • DraftInvoiceInput (293-296)
⏰ Context from checks skipped due to timeout of 90000ms (9)
  • GitHub Check: Quickstart
  • GitHub Check: E2E
  • GitHub Check: CI
  • GitHub Check: Lint
  • GitHub Check: Commit hooks
  • GitHub Check: Developer environment
  • GitHub Check: Test
  • GitHub Check: Build
  • GitHub Check: Analyze (go)
🔇 Additional comments (35)
openmeter/app/app.go (1)

24-24: Interface extension adds marshaling capability for event data.

Adding the GetEventAppData() method to the App interface enables consistent serialization of app-specific data within event payloads. This change supports the new event abstraction system for app entities.

openmeter/app/sandbox/mock.go (1)

210-212: Implementation satisfies the extended App interface.

The mock implementation correctly returns an empty app.EventAppData map and nil error, which is appropriate for a mock object without actual data requirements.

openmeter/billing/service/invoiceline.go (1)

204-211: Improved error handling for invoice event creation.

The code now properly separates event creation errors from publishing errors, adding explicit error handling for the event creation step. This is more robust and provides better error context for debugging.

This change correctly handles the new error return value from billing.NewInvoiceCreatedEvent(), which now returns an error when it fails to construct the event payload with app data.

openmeter/app/custominvoicing/factory.go (2)

105-110: Refactored App structure to use Meta embedding.

The App struct now embeds app data in a Meta struct containing both AppBase and Configuration, which supports the new event app data architecture and standardizes the app implementation pattern across the codebase.


128-128: Updated to use proper accessor method.

Now correctly calls GetAppBase() on the app instance rather than using it directly, which is consistent with the new app structure where AppBase is embedded within the Meta struct.

openmeter/billing/httpdriver/invoice.go (1)

566-597: LGTM: Well-structured marshaling of event invoice to API types

This new function correctly maps billing.EventInvoice to api.Invoice by:

  1. First mapping the base invoice
  2. Mapping each app type (Invoicing, Payment, Tax) using apphttpdriver.MapEventAppToAPI
  3. Properly handling errors at each step

The implementation is thorough and follows a consistent pattern of error handling.

openmeter/app/stripe/service/factory.go (1)

216-219: Refactored app structure with Meta field implementation

This change aligns with the broader refactoring pattern across app packages to unify metadata handling via the Meta struct. The Meta struct likely implements the app.EventAppParser interface to support event data serialization.

openmeter/app/httpdriver/customer.go (3)

348-349: Updated to use Meta field for consistency

The code now correctly uses the Stripe app's Meta field when mapping to API types, ensuring consistency with the overall design pattern.


364-365: Updated to use Meta field for consistency

The code now correctly uses the Sandbox app's Meta field when mapping to API types, maintaining consistency with the refactoring pattern.


377-395: Added support for Custom Invoicing app type

The implementation for Custom Invoicing app type follows the same pattern as other app types, using the Meta field for mapping and providing proper error handling. This is a good addition that completes the app type support.

openmeter/app/service/app.go (4)

77-82: Updated error handling for event creation in UpdateApp

The code now correctly handles errors returned from app.NewAppUpdateEvent(), which is necessary based on the recent changes to this function's signature to return an error. This ensures that event creation failures are properly propagated to the caller.


105-110: Added necessary retrieval of existing app before uninstall

Good addition to retrieve the existing app before uninstalling it. This is required to extract event-related app data that will be needed when creating the delete event.


118-123: Enhanced event data extraction with proper error handling

The code now correctly extracts event app data using existingApp.GetEventAppData() and passes it to app.NewAppDeleteEvent(), with appropriate error handling. This is necessary to support the richer event payloads in the new event system.


149-153: Added error handling for UpdateAppStatus event creation

Similar to the change in UpdateApp, this adds proper error handling for the app.NewAppUpdateEvent() call, maintaining consistency throughout the codebase.

openmeter/app/stripe/entity/input.go (1)

416-421: Added JSON struct tags to control serialization of AppData

Good addition of JSON struct tags to the AppData struct fields. Sensitive fields like APIKey and WebhookSecret are correctly excluded from JSON serialization with json:"-", while other fields have explicit JSON field names which helps maintain a stable API.

test/billing/event_test.go (3)

33-35: Updated to use new draft invoice creation helper

Changed from using the private s.createDraftInvoice to the exported s.CreateDraftInvoice which uses the new DraftInvoiceInput struct. This aligns with the test helper refactoring that centralizes draft invoice creation.


38-39: Added error handling for invoice event creation

The code now correctly checks for errors returned from billing.NewInvoiceCreatedEvent(), which is necessary based on the recent changes to the event creation function signature.


52-54: Updated field access to match new event structure

Updated how app base fields are accessed in the event, changing from event.AppBases.Tax to event.Apps.Tax.AppBase (and similarly for Payment and Invoicing). This matches the structural changes in the billing event model where InvoiceAppBases was renamed to InvoiceApps.

test/billing/invoice_test.go (3)

906-909: Migrated to new draft invoice creation method

Changed to use the exported s.CreateDraftInvoice method with the new DraftInvoiceInput struct. This is part of refactoring that centralizes draft invoice creation in test/billing/suite.go.


968-971: Migrated to new draft invoice creation method

Updated this test case to use the standardized s.CreateDraftInvoice method with the new DraftInvoiceInput struct, consistent with the refactoring pattern applied throughout the tests.


1198-1201: Migrated to new draft invoice creation method

Changed to use the exported s.CreateDraftInvoice method with the new DraftInvoiceInput struct, maintaining consistency with other test cases after the draft invoice creation refactoring.

openmeter/app/custominvoicing/app.go (4)

39-42: Looks good: Introduced new Meta struct for event data handling

The Meta struct effectively encapsulates the app base and configuration data, following a good design pattern for embedding related fields.


56-58: App structure update looks good

The refactored App struct properly embeds the new Meta struct, aligning with the broader changes to enhance app data handling.


83-85: Implementation of GetEventAppData is correct

The GetEventAppData method correctly serializes the app configuration into event app data, fulfilling the interface requirement.


44-54: ⚠️ Potential issue

Fix the implementation of FromEventAppData method

There's a bug in the implementation of the FromEventAppData method. It's attempting to parse app data into itself rather than into the Meta struct's Configuration field.

func (m *Meta) FromEventAppData(event app.EventApp) error {
	m.AppBase = event.AppBase

-	if err := event.AppData.ParseInto(&m.Configuration); err != nil {
+	if err := event.AppData.ParseInto(&m.Configuration); err != nil {
		return fmt.Errorf("error parsing app data: %w", err)
	}

	return nil
}

Likely an incorrect or invalid review comment.

openmeter/billing/service/invoicestate.go (3)

17-17: Import looks good

The addition of the eventbus import correctly supports the new event publishing functionality.


26-26: New Publisher field added to InvoiceStateMachine

The addition of a Publisher field to InvoiceStateMachine is appropriate for the event publishing capability.


381-389:

Details

✅ Verification successful

Event publishing for invoice state transitions

This implementation properly publishes an event after each successful state transition, enhancing the system's observability.

Verify that it's intentional for the method to return early if event creation or publishing fails, potentially leaving the state transition incomplete:


🏁 Script executed:

#!/bin/bash
# Find event error handling patterns in similar contexts
rg -A 3 'error creating .* event' --glob '*.go'
rg -A 3 'error .* event' --glob '*.go' | rg 'return'

Length of output: 982


I didn’t find any other patterns of swallowing or deferring state persistence around the event emit. It looks like the invoice is saved (and its transaction committed) before we build and publish the event, so a failure during event creation/publish will return early after the DB change has been applied.

If you intend for the entire transition—including state persistence and the outbound event—to be atomic (i.e. rolled back on publish failures), you’ll need to wrap persistence and publish in the same transactional boundary or add compensation/cleanup logic here.

– If this behavior is intentional (persist then best-effort publish), no changes are needed.
– Otherwise, consider moving the publish into the DB transaction callback or adding a rollback on publish errors.

openmeter/app/stripe/entity/app/app.go (2)

16-19: Meta struct looks good

The Meta struct effectively encapsulates the app base and app data, following the same pattern used in other app implementations.


90-92: Implementation of GetEventAppData is correct

The GetEventAppData method correctly serializes the app data into event app data, fulfilling the interface requirement.

test/app/custominvoicing/event_test.go (4)

15-21: Test suite structure looks good

The test suite structure is well organized, properly embedding the existing CustomInvoicingTestSuite and registering with the testing framework.


23-40: Test setup and event creation looks good

The test properly sets up a billing profile with specific configuration flags and creates an invoice event, checking for errors during creation.


41-50: JSON serialization testing looks good

The test properly verifies that the event can be marshaled to and unmarshaled from JSON, which is an important aspect for events that will be transmitted.


51-62:

Details

✅ Verification successful

Event data validation is comprehensive

The test effectively validates that the event contains the correct app bases and that the app data can be unmarshaled into the Meta struct, verifying that configuration flags are preserved.

Given the identified bugs in the FromEventAppData methods, verify that this test is actually testing the correct behavior:


🏁 Script executed:

#!/bin/bash
# Check the implementation of the FromEventAppData method in both files
rg -A 10 'func \(.+\) FromEventAppData' --glob 'openmeter/app/*/app.go'

Length of output: 1211


In order to confirm that the test is exercising the custominvoicing.Meta.FromEventAppData logic correctly (and that there are no typos in the app field names), let’s dump the contents of the test file:


🏁 Script executed:

#!/bin/bash
# Show the first 200 lines of the custominvoicing event test
sed -n '1,200p' test/app/custominvoicing/event_test.go

Length of output: 2059


To confirm how the InvoiceCreatedEvent struct defines its Apps fields (and whether Invocing is a typo), let’s dump its definition:


🏁 Script executed:

#!/bin/bash
# Show InvoiceCreatedEvent and its Apps sub-struct
rg -A20 'type InvoiceCreatedEvent' openmeter/billing

Length of output: 1141


To confirm whether Apps.Invocing is a typo in the test or also present in the event struct, let’s dump the EventInvoice definition and search for “Invocing”:


🏁 Script executed:

#!/bin/bash
# Dump the EventInvoice struct (which is inlined into InvoiceCreatedEvent)
rg -A50 'type EventInvoice' openmeter/billing/events.go

# Search the repo for any occurrence of the misspelled 'Invocing'
rg -n 'Invocing' -n .

Length of output: 2136


Event data validation is correct and comprehensive
The TestCreateInvoiceEvent suite accurately exercises the custom-invoicing path:

  • It uses the code’s (consistently spelled) Apps.Invocing field as defined on InvoiceCreatedEvent.
  • It verifies both the AppBase and that Meta.FromEventAppData correctly parses and preserves EnableDraftSyncHook and EnableIssuingSyncHook.

No changes are needed.

openmeter/billing/events.go (1)

58-90: InvoiceApps.Validate can emit false positives when any app is optional

NewEventInvoice leaves InvoiceApps fields zero-valued if the corresponding workflow app is nil, but Validate blindly calls .Validate() on them, which will fail for a zero EventApp.

If the business rule requires all three apps, enforce it up-front (e.g., return an error from NewEventInvoice).
Otherwise, guard each call:

if a.Tax.IsZero() { /* skip */ } else if err := a.Tax.Validate(); err != nil { … }

Consider adding an explicit IsZero() helper to EventApp.

@turip turip force-pushed the fix/allow-marshaling-event-invoice-into-api-types branch from fb374bd to ed3228b Compare April 30, 2025 09:38
@turip turip enabled auto-merge (squash) April 30, 2025 11:35
@tothandras tothandras disabled auto-merge April 30, 2025 11:39
@tothandras tothandras merged commit 3f23df5 into main Apr 30, 2025
27 of 28 checks passed
@tothandras tothandras deleted the fix/allow-marshaling-event-invoice-into-api-types branch April 30, 2025 11:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/misc Miscellaneous changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants