Skip to content

feat(ledger): validate currency rounding - #4342

Merged
GAlexIHU merged 1 commit into
mainfrom
codex/ledger-currency-rounding-validation
May 11, 2026
Merged

feat(ledger): validate currency rounding#4342
GAlexIHU merged 1 commit into
mainfrom
codex/ledger-currency-rounding-validation

Conversation

@GAlexIHU

@GAlexIHU GAlexIHU commented May 11, 2026

Copy link
Copy Markdown
Contributor

summary

Adds ledger validation to reject transaction entry amounts that are not rounded to the posting currency precision.

details

  • validates each ledger entry amount using currencyx.Calculator.IsRoundedToPrecision
  • derives currency from the entry posting address route
  • rejects invalid precision with ledger_transaction_amount_invalid
  • does not normalize/round inside ledger, since ledger is the audit boundary

tests

  • added USD/JPP precision coverage for ledger transaction validation
  • ran:
nix develop --impure .#ci -c env POSTGRES_HOST=127.0.0.1 go test -tags=dynamic ./openmeter/ledger/...


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

## Release Notes

* **New Features**
  * Entry amounts are now validated to match their posting currency's decimal precision. Invalid amounts will be rejected with detailed error messages.

* **Tests**
  * Added test coverage for amount precision validation.

[![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/openmeterio/openmeter/pull/4342)

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

@GAlexIHU
GAlexIHU requested a review from a team as a code owner May 11, 2026 17:17
@GAlexIHU GAlexIHU added the release-note/ignore Ignore this change when generating release notes label May 11, 2026
@coderabbitai

coderabbitai Bot commented May 11, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

The PR enforces that ledger entry amounts are rounded to their posting currency's configured precision. A new internal validator checks each entry's amount against its currency's decimal places, returning a detailed error with the original amount and properly-rounded value when validation fails. Tests cover USD and JPY to verify correct acceptance and rejection of amounts at precision boundaries.

Changes

Amount Precision Validation

Layer / File(s) Summary
Precision Validation Function
openmeter/ledger/validations.go
New validateEntryAmountPrecision extracts currency from the entry's posting address route, obtains a precision calculator, and returns ErrTransactionAmountInvalid with contextual attributes (currency, original amount, rounded amount) if the amount is not rounded to currency precision.
Validation Integration
openmeter/ledger/validations.go
ValidateEntryInput now calls the precision validator after address validation and before returning success.
Test Cases & Helpers
openmeter/ledger/validations_test.go
Test TestValidateTransactionInputEntryAmountPrecision verifies USD accepts two decimal places and JPY accepts whole numbers, rejecting fractional values with proper error details. Helper functions mustPostingAddress and mustDecimal construct test fixtures.

🎯 2 (Simple) | ⏱️ ~12 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change—adding currency rounding validation to the ledger module, which is exactly what the changeset implements.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/ledger-currency-rounding-validation

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
openmeter/ledger/validations.go (2)

69-74: 💤 Low value

Heads up: ErrCurrencyInvalid branch is uncovered and shaped differently from sibling errors.

Two small notes on this branch:

  1. The neighbouring failure modes inside ValidateEntryInput wrap inner errors as ErrEntryInvalid with a reason attr (see lines 43–47), but here you return ErrCurrencyInvalid directly. Not wrong, just a touch inconsistent — callers handling validation issues uniformly may be surprised.
  2. There's no test exercising this path (e.g., an unknown/empty currency). Easy to add and would lock in the contract.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openmeter/ledger/validations.go` around lines 69 - 74, The ErrCurrencyInvalid
return in ValidateEntryInput is inconsistent with sibling error handling:
instead of returning ErrCurrencyInvalid directly with Attributes, wrap the
underlying currency parse error similarly to the ErrEntryInvalid pattern
(include a "reason" attribute) or normalize the sibling errors to use
ErrCurrencyInvalid with matching attrs; update the return in ValidateEntryInput
(the branch that checks currency and currently returns
ErrCurrencyInvalid.WithAttrs(models.Attributes{...})) to include a "reason" attr
(or wrap via ErrEntryInvalid.WithAttrs({"reason": err, "currency": currency})),
and add a unit test covering an unknown/empty currency to assert the chosen
error shape and attributes.

66-87: ⚡ Quick win

The Route().Route() chain is safe but verbose — consider a helper method for clarity.

The nil-safety concern here is actually not an issue: both PostingAddress.Route() and SubAccountRoute.Route() return value types (not pointers), so they can't be nil. Plus, ValidateAddress already guards against a nil PostingAddress before this function runs.

That said, the chain is awkward to read and appears throughout the codebase. A small helper like PostingAddress.GetRoute() that returns the decoded Route directly would improve readability without changing behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@openmeter/ledger/validations.go` around lines 66 - 87, The Route().Route()
call chain in validateEntryAmountPrecision (and other places using
EntryInput.PostingAddress().Route().Route()) is verbose—add a helper method like
PostingAddress.GetRoute() that returns the decoded Route value (the existing
decoded type returned by SubAccountRoute.Route()) so callers can use
entry.PostingAddress().GetRoute().Currency; implement GetRoute() on the
PostingAddress type to perform the current Route() then Route() calls
internally, update validateEntryAmountPrecision to use
PostingAddress.GetRoute(), and replace other occurrences across the codebase
(keeping existing behavior and nil-safety ensured by ValidateAddress).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@openmeter/ledger/validations.go`:
- Around line 69-74: The ErrCurrencyInvalid return in ValidateEntryInput is
inconsistent with sibling error handling: instead of returning
ErrCurrencyInvalid directly with Attributes, wrap the underlying currency parse
error similarly to the ErrEntryInvalid pattern (include a "reason" attribute) or
normalize the sibling errors to use ErrCurrencyInvalid with matching attrs;
update the return in ValidateEntryInput (the branch that checks currency and
currently returns ErrCurrencyInvalid.WithAttrs(models.Attributes{...})) to
include a "reason" attr (or wrap via ErrEntryInvalid.WithAttrs({"reason": err,
"currency": currency})), and add a unit test covering an unknown/empty currency
to assert the chosen error shape and attributes.
- Around line 66-87: The Route().Route() call chain in
validateEntryAmountPrecision (and other places using
EntryInput.PostingAddress().Route().Route()) is verbose—add a helper method like
PostingAddress.GetRoute() that returns the decoded Route value (the existing
decoded type returned by SubAccountRoute.Route()) so callers can use
entry.PostingAddress().GetRoute().Currency; implement GetRoute() on the
PostingAddress type to perform the current Route() then Route() calls
internally, update validateEntryAmountPrecision to use
PostingAddress.GetRoute(), and replace other occurrences across the codebase
(keeping existing behavior and nil-safety ensured by ValidateAddress).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e20379b8-35be-435a-833a-3d884804f202

📥 Commits

Reviewing files that changed from the base of the PR and between f3ae09c and 7eb4934.

📒 Files selected for processing (2)
  • openmeter/ledger/validations.go
  • openmeter/ledger/validations_test.go

@GAlexIHU
GAlexIHU merged commit 826653b into main May 11, 2026
30 of 31 checks passed
@GAlexIHU
GAlexIHU deleted the codex/ledger-currency-rounding-validation branch May 11, 2026 19:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/ignore Ignore this change when generating release notes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants