Skip to content

chore: Docs, Naming + Tests - #2

Merged
gocanto merged 5 commits into
mainfrom
chore/docs
Dec 15, 2025
Merged

gocanto merged 5 commits into
mainfrom
chore/docs

Conversation

@gocanto

@gocanto gocanto commented Dec 15, 2025

Copy link
Copy Markdown
Owner

No description provided.

@coderabbitai

coderabbitai Bot commented Dec 15, 2025 •

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features

    • Added safe monetary arithmetic operations with overflow protection.
    • Introduced currency manager with enhanced lookup and resolution capabilities.
    • Added currency comparison and exchange rate conversion functionality.
    • Expanded Money API with comparison predicates and amount validation.
    • Added numeric formatting utilities for major unit conversion.
  • Documentation

    • Updated currency references and examples across documentation.
  • Tests

    • Added comprehensive test utilities and expanded test coverage for edge cases.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

This pull request introduces a comprehensive set of public APIs across multiple packages: a Calculator type with arithmetic and rounding methods, expanded currency management with Manager and Map enhancements, exchange rate conversion functionality, Money type inspection and comparison methods, and new test utilities. Data references throughout test files are updated from USD to SGD.

Changes

Cohort / File(s) Change Summary
Calculator API
calculator/calculator.go, calculator/ration.go, calculator/calculator_test.go
Introduced public Amount type alias (int64), Calculator struct, and NewCalculator constructor. Added public methods for arithmetic operations (Add, Subtract, Multiply, SafeMultiply, Divide, Modulus), transformations (Absolute, Negative, Round), and allocation (Allocate). SafeSubtract added to ration.go for overflow-safe subtraction. Expanded test coverage for overflow boundaries.
Currency Management
currency/currency.go, currency/iso.go, currency/manager.go, currency/map.go, currency/metal.go
Added NumericCode field and Equals method to Currency. Expanded Provider interface with GetCode and GetSymbols. Implemented NewISOCodePattern constructors and pattern retrieval methods. Introduced Manager type with constructors (NewManager, NewManagerWith, NewManagerFor) and query methods (FindByCode, Add, AddFrom, GetDefault, GetSymbols, Resolve). Extended Map with validation and lookup utilities (IsEmpty, IsNotEmpty, HasInvalidState, FindByCode, GetCodes).
Currency Dataset and Documentation
currency/codes.go, currency/dataset.go
Added ISO 4217 header comment to codes.go. Added SGD symbol entry to dataset symbols with ordering clarification. Minor documentation enhancements without functional changes.
Exchange Rate Conversion
exchange/exchange.go, exchange/converter.go
Added NewExchange constructor. Introduced ConvertAmount and ConvertAmountWithRate methods on Exchange for currency conversion. Added NewConverter constructor with nil validation and GetExchange method with defensive nil checks.
Money Type Extensions
money/money.go
Added comprehensive read-accessor and comparison methods: Currency, Amount, AssertSameCurrency, SameCurrency, CompareAmount, Equals, GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual, IsZero, IsPositive, IsNegative, and Display. Each includes nil and currency validation.
JSON and Formatting
money/json.go, format/format.go, exception/exception.go
Introduced JSONRawData struct with Amount (json.Number) and Currency fields for precision-preserving unmarshalling. Added ToMajorUnits method to Formatter. Added NewErrInvalidJSONUnmarshalFrom helper in exception package.
Parser Enhancements
parser/parser.go
Added NewParser and NewParserWith constructors for Parser initialisation with default or custom ISOCodePattern.
Test Infrastructure
tests/support.go, money/test_helpers_test.go
Created new tests package with generic test utilities (TestRequire, TestRequireNoErr, TestPow10i64Money, TestAbs64Money, TestMustParseFloatMoney, TestExpectedConvertAmountMoney, TestRoundRatHalfAwayFromZeroMoney). Removed inline test_helpers_test.go file (utilities now centralised).
Factory and Manager Setup
money/factory.go, money/manager.go
Introduced package-level defaultManager singleton in factory.go using NewManager(). Updated manager.go documentation to reference SGD in examples.
Test Updates – Currency Data
currency/iso_test.go, exchange/exchange_test.go, money/money_test.go, money/json_test.go, money/db_test.go, money/exchange_test.go, money/manager_test.go, money/aggregator_test.go, parser/parser_test.go, format/format_test.go, money/factory_test.go
Comprehensive test data migration from USD to SGD across all test files. Updated currency codes, numeric codes (840 → 702), symbols ($ → S$), and related assertions. Replaced test assertion helpers (require/requireNoErr) with testutil equivalents. Added new real-world test scenarios for exchange and conversion.
Configuration
Makefile
Updated TEST_PACKAGES computation to exclude test packages (added grep -v /tests).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • money/money.go: Dense set of new comparison and accessor methods with consistent nil/currency validation patterns—verify error handling is uniform across all predicates
  • exchange/exchange.go & exchange/converter.go: New conversion logic with rate lookups and fallback paths—ensure edge cases (zero rates, nil exchanges, precision loss) are properly handled
  • currency/manager.go: Multiple constructors with varying initialisation paths—confirm dataset validation and state consistency across all entry points
  • tests/support.go: New generic test utilities using big.Rat and rational arithmetic—validate rounding logic, especially TestRoundRatHalfAwayFromZeroMoney for corner cases (MinInt64, negative numbers)
  • Test data migrations: Large-scale USD→SGD updates across multiple files—spot-check several test cases to ensure assertions remain semantically correct

Possibly related PRs

  • chore: Docs, Naming + Tests #2: Introduces identical Calculator API and exported symbols in calculator/calculator.go, indicating direct feature parity
  • Alpha Version #1: Overlapping foundational changes including Calculator type, exchange conversion methods, and exception helpers, suggesting incremental builds on shared infrastructure

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning No pull request description was provided by the author, making it impossible to assess whether the objectives and rationale for this extensive set of changes are clearly communicated. Add a comprehensive pull request description explaining the objectives, key changes (e.g., new APIs, currency migration to SGD, test refactoring), and any breaking changes or migration guidance for reviewers.
Title check ❓ Inconclusive The title 'chore: Docs, Naming + Tests' is vague and generic, using non-descriptive terms that do not convey meaningful information about the substantial API additions and currency migration changes throughout the codebase. Provide a more specific title that captures the main changes, such as 'chore: Introduce calculator API, currency manager, and SGD migration' or similar to better reflect the scope of modifications.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5923c3d and a59ec31.

📒 Files selected for processing (14)
  • Makefile (1 hunks)
  • calculator/calculator.go (7 hunks)
  • calculator/calculator_test.go (1 hunks)
  • calculator/ration.go (1 hunks)
  • exception/exception.go (2 hunks)
  • exchange/exchange_test.go (15 hunks)
  • money/aggregator_test.go (2 hunks)
  • money/db_test.go (13 hunks)
  • money/exchange_test.go (14 hunks)
  • money/factory_test.go (2 hunks)
  • money/json_test.go (21 hunks)
  • money/manager_test.go (25 hunks)
  • money/money_test.go (37 hunks)
  • tests/support.go (1 hunks)

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

@gocanto gocanto changed the title chore: Docs + Naming chore: Docs, Naming + Tests Dec 15, 2025
@gocanto
gocanto marked this pull request as ready for review December 15, 2025 08:29

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (14)
calculator/calculator.go (7)

31-38: Silent failure on nil receiver may mask errors.

When the calculator is nil, SafeMultiply returns (0, nil) without an error. This silently fails and could mask bugs where callers expect an error to be returned when the result is 0. Consider returning an error to make the failure explicit.

Apply this diff:

 func (c *Calculator) SafeMultiply(initial int64, multipliers ...int64) (int64, error) {
 	if c == nil {
-		return 0, nil
+		return 0, errors.New("calculator: nil receiver")
 	}
 
 	return SafeMultiply(initial, multipliers...)
 }

Note: Add "errors" to imports if not already present.


40-47: Missing overflow check for MinInt64 / -1.

The Divide method doesn't check for the overflow case when amount == math.MinInt64 and seed == -1. In Go, this causes a runtime panic because the result would exceed math.MaxInt64.

Apply this diff:

 func (c *Calculator) Divide(amount Amount, seed int64) Amount {
 	if c == nil || seed == 0 {
 		return 0
 	}
+
+	if amount == math.MinInt64 && seed == -1 {
+		return 0 // Overflow protection
+	}
 
 	return amount / seed
 }

49-56: Missing overflow check for MinInt64 % -1.

The Modulus method doesn't check for the overflow case when amount == math.MinInt64 and seed == -1. In Go, this causes a runtime panic.

Apply this diff:

 func (c *Calculator) Modulus(amount Amount, seed int64) Amount {
 	if c == nil || seed == 0 {
 		return 0
 	}
+
+	if amount == math.MinInt64 && seed == -1 {
+		return 0 // Overflow protection
+	}
 
 	return amount % seed
 }

67-78: Incorrect overflow check in Absolute method.

Line 69 checks amount < math.MinInt64, which is impossible since no int64 value can be less than math.MinInt64. The intended check should be amount == math.MinInt64 to protect against overflow when negating the minimum value.

Apply this diff:

 func (c *Calculator) Absolute(amount Amount) Amount {
-	if c == nil || amount < math.MinInt64 {
+	if c == nil || amount == math.MinInt64 {
 		return 0
 	}
 
 	if amount < 0 {
 		return -amount
 	}
 
 	return amount
 }

80-91: Missing overflow protection in Negative method.

The method doesn't protect against negating math.MinInt64. When amount == math.MinInt64, attempting to negate it (line 87 implicitly or line 90's logic) would cause overflow since -math.MinInt64 exceeds math.MaxInt64.

Apply this diff:

 func (c *Calculator) Negative(amount Amount) Amount {
 	if c == nil {
 		return 0
 	}
+
+	if amount == math.MinInt64 {
+		return 0 // Overflow protection
+	}
 
 	if amount > 0 {
 		return -amount
 	}
 
 	return amount
 }

93-136: Float precision risk in rounding calculation.

Line 113 uses math.Pow(10, float64(exponent)) and converts back to int64. For monetary calculations requiring exact precision, using floating-point arithmetic can introduce rounding errors. Consider using integer exponentiation instead.

Apply this diff to use integer exponentiation:

-	// Calculate the rounding unit: 10^exponent determines the precision level
-	// e.g. exponent=2 means round to the nearest 100 (cents to dollars for precision 2)
-	reminder := int64(math.Pow(10, float64(exponent)))
+	// Calculate the rounding unit using integer exponentiation: 10^exponent determines the precision level
+	// e.g. exponent=2 means round to the nearest 100 (cents to dollars for precision 2)
+	remainder := int64(1)
+	for i := 0; i < exponent; i++ {
+		remainder *= 10
+	}

Note: Also fixes the typo from "reminder" to "remainder" in subsequent references.


116-116: Typo: "reminder" should be "remainder".

The variable name should be "remainder" to match standard mathematical terminology.

exchange/exchange.go (2)

89-117: Inconsistent error return values.

The method returns -1 for a nil receiver (line 92) but 0 for a rate lookup error (line 101). This inconsistency could confuse callers who check the returned amount to detect errors. Consider returning a consistent sentinel value (e.g., 0) for all error paths, or document the differing return values.

 func (e *Exchange) ConvertAmount(amount int64, fromCurrencyCode string, fromFraction int, toCurrencyCode string, toFraction int) (int64, error) {
 	if e == nil {
-		return -1, exception.ErrInvalidExchangeRate
+		return 0, exception.ErrInvalidExchangeRate
 	}

119-137: Consider extracting shared conversion logic.

The conversion calculation (lines 126-134) duplicates the logic in ConvertAmount (lines 106-114). Extracting this to a private helper would reduce duplication and centralise the rounding behaviour.

// convertWithRate performs the actual conversion calculation
func convertWithRate(amount int64, fromFraction, toFraction int, rate float64) int64 {
	fromFractionPow := math.Pow10(fromFraction)
	toFractionPow := math.Pow10(toFraction)
	majorUnits := float64(amount) / fromFractionPow
	convertedMajorUnits := majorUnits * rate
	return int64(math.Round(convertedMajorUnits * toFractionPow))
}
currency/dataset.go (1)

11-27: Fix symbol ordering to prevent partial matching issues.

The comment states symbols are "ordered to prevent partial matching issues," but the ordering is inconsistent. HK$ (3 characters, line 19) appears after S$ (2 characters, line 18), violating the longest-first principle needed for greedy matching.

Apply this diff to fix the ordering:

 func GetSymbols() []Symbol {
 	return []Symbol{
 		{"CFP", XPF}, // CFP Franc (XPF) - avoids ambiguity with "₣"
 		{"NT$", TWD}, // Taiwan Dollar
+		{"HK$", HKD},
 		{"R$", BRL},
 		{"C$", CAD},
 		{"A$", AUD},
 		{"S$", SGD},
-		{"HK$", HKD},
 		{"$", USD},
 		{"€", EUR},
 		{"£", GBP},
 		{"¥", JPY},
 		{"₹", INR},
 		{"₣", CHF},
 	}
 }
currency/map.go (1)

139-151: Fix incorrect sync.Once usage in GetCodes.

The sync.Once is declared as a local variable within the method, which defeats its purpose. Each call to GetCodes() creates a new once variable, so the initialization function runs every time instead of once. Additionally, this method returns a pointer to a local slice, which could lead to unexpected behaviour if callers modify it.

Apply this diff to fix the implementation:

-// GetCodes returns a list of all currency codes present in the map.
-func (cm Map) GetCodes() *[]string {
-	var once sync.Once
-	codes := make([]string, 0, len(*cm.dataset))
-
-	once.Do(func() {
-		for code := range *cm.dataset {
-			codes = append(codes, code)
-		}
-	})
-
-	return &codes
+// GetCodes returns a list of all currency codes present in the map.
+func (cm Map) GetCodes() []string {
+	if cm.dataset == nil {
+		return []string{}
+	}
+
+	codes := make([]string, 0, len(*cm.dataset))
+	for code := range *cm.dataset {
+		codes = append(codes, code)
+	}
+
+	return codes
 }
currency/iso.go (1)

38-61: Consider wrapping the error from NewCurrenciesMapFrom.

At line 50, when NewCurrenciesMapFrom returns an error, the code replaces it with exception.ErrNoCurrencyMapDataset. This may mask the underlying cause of the failure, making debugging more difficult.

Apply this diff to preserve the original error context:

 	currencies, err := NewCurrenciesMapFrom(dataset)
 	if err != nil {
-		return nil, exception.ErrNoCurrencyMapDataset
+		return nil, fmt.Errorf("failed to create currency map: %w", err)
 	}

Import fmt if not already present.

currency/manager.go (2)

78-91: Unnecessary ToUpper on numeric code.

At line 85, strings.ToUpper(code) is called on the numeric code parameter. Since ISO 4217 numeric codes consist only of digits (e.g., "702" for SGD), the ToUpper operation has no effect and can be removed for clarity.

Apply this diff:

 func (cm *Manager) FindByNumericCode(code string) *Currency {
 	if cm == nil || cm.currencies == nil {
 		return nil
 	}
 
 	for _, currency := range *cm.currencies.dataset {
-		if currency != nil && currency.NumericCode == strings.ToUpper(code) {
+		if currency != nil && currency.NumericCode == code {
 			return currency
 		}
 	}
 
 	return nil
 }

112-129: Unnecessary ToUpper on NumericCode.

At line 124, strings.ToUpper(NumericCode) is called when constructing the Currency. Since numeric codes are purely numeric, this operation has no effect. Consider removing it for clarity.

Apply this diff:

 	currency := &Currency{
 		Code:        strings.ToUpper(code),
 		Grapheme:    Grapheme,
 		Template:    Template,
 		Decimal:     Decimal,
 		Thousand:    Thousand,
-		NumericCode: strings.ToUpper(NumericCode),
+		NumericCode: NumericCode,
 		Fraction:    Fraction,
 	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 094acaf and a72b023.

📒 Files selected for processing (30)
  • calculator/calculator.go (7 hunks)
  • currency/codes.go (1 hunks)
  • currency/currency.go (2 hunks)
  • currency/dataset.go (2 hunks)
  • currency/iso.go (5 hunks)
  • currency/iso_test.go (5 hunks)
  • currency/manager.go (8 hunks)
  • currency/map.go (4 hunks)
  • currency/metal.go (2 hunks)
  • currency/uat_test.go (20 hunks)
  • exception/exception.go (2 hunks)
  • exchange/converter.go (2 hunks)
  • exchange/exchange.go (3 hunks)
  • exchange/exchange_test.go (13 hunks)
  • exchange/real_world_finance_test.go (1 hunks)
  • format/format.go (2 hunks)
  • format/format_test.go (1 hunks)
  • money/aggregator_test.go (1 hunks)
  • money/db_test.go (11 hunks)
  • money/exchange_test.go (1 hunks)
  • money/factory.go (1 hunks)
  • money/json.go (1 hunks)
  • money/json_test.go (13 hunks)
  • money/manager.go (2 hunks)
  • money/manager_test.go (31 hunks)
  • money/money.go (14 hunks)
  • money/money_test.go (40 hunks)
  • money/test_helpers_test.go (2 hunks)
  • parser/parser.go (3 hunks)
  • parser/parser_test.go (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (15)
money/exchange_test.go (7)
money/manager.go (1)
  • NewManager (21-27)
currency/manager.go (1)
  • NewManager (16-28)
exchange/exchange.go (1)
  • NewExchange (18-22)
exchange/real_world_finance_test.go (8)
  • USD (16-16)
  • EUR (17-17)
  • GBP (18-18)
  • JPY (19-19)
  • CNY (20-20)
  • CAD (21-21)
  • AUD (22-22)
  • CHF (23-23)
money/type.go (2)
  • Money (12-15)
  • Amount (9-9)
currency/currency.go (1)
  • Currency (12-27)
exception/exception.go (1)
  • ErrCurrencyConversionNotFound (12-12)
money/aggregator_test.go (4)
money/manager.go (1)
  • NewManager (21-27)
currency/manager.go (1)
  • NewManager (16-28)
exception/exception.go (1)
  • ErrInvalidAggregatorProvider (40-40)
money/aggregator.go (1)
  • Aggregator (8-10)
currency/iso_test.go (5)
currency/iso.go (1)
  • NewISOCodePatternWith (39-61)
currency/currency.go (1)
  • Currency (12-27)
exception/exception.go (2)
  • ErrNoConverterProvided (29-29)
  • ErrNoCurrencyMapDataset (15-15)
currency/codes.go (1)
  • SGD (131-131)
currency/dataset.go (1)
  • Symbol (4-7)
currency/dataset.go (1)
currency/codes.go (6)
  • XPF (174-174)
  • TWD (149-149)
  • BRL (25-25)
  • CAD (31-31)
  • AUD (12-12)
  • SGD (131-131)
currency/uat_test.go (4)
currency/currency.go (1)
  • Currency (12-27)
currency/codes.go (1)
  • SGD (131-131)
format/format.go (1)
  • Formatter (10-16)
currency/manager.go (1)
  • NewManager (16-28)
money/json_test.go (4)
money/manager.go (1)
  • NewManager (21-27)
currency/codes.go (1)
  • SGD (131-131)
currency/currency.go (1)
  • Currency (12-27)
money/type.go (2)
  • Amount (9-9)
  • Money (12-15)
parser/parser_test.go (3)
currency/codes.go (3)
  • SGD (131-131)
  • GBP (56-56)
  • EUR (53-53)
parser/parser.go (1)
  • Parser (11-13)
exception/exception.go (1)
  • ErrParserNotProvided (44-44)
exchange/real_world_finance_test.go (2)
exchange/exchange.go (1)
  • NewExchange (18-22)
exception/exception.go (1)
  • ErrCurrencyConversionNotFound (12-12)
money/manager_test.go (3)
currency/codes.go (1)
  • SGD (131-131)
exception/exception.go (6)
  • ErrEmptyAmountString (31-31)
  • ErrInvalidAmountMultiple (32-32)
  • ErrInvalidAmountFraction (33-33)
  • ErrInvalidAmount (34-34)
  • ErrParserNotProvided (44-44)
  • ErrNoMultipliersProvided (27-27)
money/manager.go (1)
  • NewManager (21-27)
money/db_test.go (4)
money/manager.go (1)
  • NewManager (21-27)
currency/manager.go (1)
  • NewManager (16-28)
currency/codes.go (1)
  • SGD (131-131)
currency/currency.go (1)
  • Currency (12-27)
exchange/exchange_test.go (2)
exception/exception.go (2)
  • ErrInvalidExchangeRate (18-18)
  • ErrCurrencyConversionNotFound (12-12)
exchange/exchange.go (1)
  • Exchange (12-15)
parser/parser.go (1)
currency/iso.go (1)
  • NewISOCodePattern (24-36)
currency/manager.go (3)
currency/map.go (1)
  • Map (13-16)
currency/dataset.go (1)
  • Symbol (4-7)
currency/currency.go (1)
  • Currency (12-27)
money/money_test.go (3)
currency/codes.go (3)
  • SGD (131-131)
  • JPY (76-76)
  • EUR (53-53)
currency/currency.go (1)
  • Currency (12-27)
currency/manager.go (1)
  • NewManager (16-28)
calculator/calculator.go (1)
calculator/ration.go (2)
  • SafeAdd (10-16)
  • Ration (37-87)
🔇 Additional comments (50)
exception/exception.go (1)

8-8: Sentinel errors doc comment is clear and consistent

The comment concisely documents the purpose of the following error block and matches the package’s current structure. No changes needed.

calculator/calculator.go (1)

58-65: Allocation logic is sound.

The method correctly guards against nil calculator and zero scale/amount. The division on line 64 could theoretically overflow if Ration(amount, ration) == math.MinInt64 and scale == -1, but this is an unlikely edge case in typical allocation scenarios.

exchange/exchange.go (1)

17-22: LGTM!

The constructor documentation and implementation are correct.

exchange/converter.go (2)

12-21: LGTM!

The constructor properly validates the input and returns an appropriate error when the exchange is nil.


23-34: LGTM!

Good defensive programming with nil-receiver and nil-exchange checks. The error types are appropriately differentiated.

format/format_test.go (1)

18-18: LGTM!

Comment correctly updated to reflect the SGD reference currency used throughout the test suite.

money/manager.go (2)

52-52: LGTM!

Documentation example correctly updated to use SGD.


71-72: LGTM!

Documentation examples correctly updated to use SGD for consistency.

currency/codes.go (1)

3-4: LGTM!

Good addition of a descriptive header comment for the ISO 4217 currency codes constant block.

money/aggregator_test.go (1)

38-54: LGTM! Test currency update is consistent.

The switch from USD to SGD in these error path tests is consistent with the broader codebase changes. Since both currencies have identical fraction values (2 decimal places), the test behaviour remains unchanged.

money/factory.go (1)

7-903: LGTM! Factory pattern is well-structured.

The singleton defaultManager and factory functions provide a clean, convenient API for creating Money instances. The pattern is consistent across all ~150 currency factory functions, and the Manager type appears designed for concurrent use based on TestManagerConcurrency in manager_test.go.

currency/iso_test.go (1)

36-146: LGTM! Test updates are consistent with SGD migration.

All test cases correctly updated to use SGD instead of USD. The tests verify pattern matching, constructor validation, and symbol sorting with SGD-specific data, maintaining full test coverage.

parser/parser.go (2)

15-27: LGTM! Constructors follow idiomatic Go patterns.

The NewParser() and NewParserWith() constructors provide both convenience (default) and flexibility (custom ISO pattern) for creating Parser instances. The implementation is clean and well-documented.


234-245: LGTM! Documentation updated for clarity.

The comment updates to reference SGD instead of USD improve consistency with the codebase migration whilst keeping the examples clear.

money/json.go (1)

16-21: LGTM! Precision-preserving JSON structure is well-designed.

The JSONRawData struct using json.Number for the amount field is a good pattern to avoid float64's 2^53 precision limit when unmarshalling large monetary values. The documentation clearly explains its purpose.

format/format.go (2)

9-27: LGTM! Documentation improvements enhance clarity.

The comment updates provide clearer context, and the NewFormatter comment accurately describes its purpose.


59-67: LGTM! ToMajorUnits implementation is correct.

The method correctly converts minor currency units to major units by dividing by 10^Fraction. The edge case where Fraction is 0 (e.g., JPY) is handled properly by returning the amount unchanged.

money/manager_test.go (1)

54-722: LGTM! Comprehensive test updates maintain full coverage.

All test cases correctly updated from USD to SGD. Since both currencies use 2 decimal places (Fraction: 2), the arithmetic assertions remain valid. Error path testing, concurrency testing, and edge case handling are all preserved with the currency switch.

currency/currency.go (1)

40-47: LGTM! Equals method is correctly implemented.

The nil-safe equality check correctly compares currencies by their Code field. The implementation properly handles nil receivers and nil arguments, returning true only when both are nil.

money/json_test.go (1)

33-33: LGTM! Test currency consistently updated to SGD.

All test cases have been systematically updated from USD to SGD whilst maintaining the same test coverage and logic. The changes are consistent across marshal, unmarshal, thread-safety, and parser tests.

Also applies to: 56-56, 75-77, 144-144, 156-156, 175-175, 294-294, 360-360, 377-377, 395-396, 456-456, 465-465, 493-493, 499-499, 508-508, 527-528

currency/map.go (3)

96-108: LGTM! IsEmpty and IsNotEmpty helpers are well-implemented.

These helper methods provide clear, expressive checks for map state with proper nil handling.


110-123: LGTM! HasInvalidState provides useful validation.

The method correctly checks for nil dataset and iterates through entries to detect nil currency pointers, returning appropriate errors.


125-137: LGTM! FindByCode implements case-insensitive lookup correctly.

The method safely handles nil dataset and performs case-insensitive lookup using strings.ToUpper, which is appropriate for ISO currency codes.

currency/uat_test.go (1)

93-93: LGTM! Test data systematically updated to SGD.

All test cases have been correctly updated to use SGD as the reference currency, including:

  • Currency code updates (USD → SGD)
  • Numeric code updates ("840" → "702")
  • Symbol updates ("$" → "S$")

The changes maintain test coverage whilst aligning with the new default currency throughout the codebase.

Also applies to: 133-175, 302-309, 349-374, 406-419, 437-453, 464-471, 486-520, 626-653, 697-697, 724-726

parser/parser_test.go (1)

25-29: LGTM! Parser tests consistently updated to SGD.

All parser test cases have been updated to use SGD as the test currency, including:

  • Symbol parsing tests (S$ prefix/suffix)
  • Comma-separated amount tests
  • Negative amount tests
  • Default currency tests

The test logic remains sound with the currency change.

Also applies to: 43-52, 225-226, 230-230, 545-575, 671-681, 734-739

exchange/exchange_test.go (1)

23-23: LGTM! Exchange tests consistently updated to SGD.

All exchange rate tests have been systematically updated from USD to SGD, including:

  • Rate addition tests
  • Rate retrieval tests (direct and inverse)
  • Amount conversion tests
  • Concurrent access tests
  • Test descriptions

The changes maintain comprehensive test coverage for exchange functionality.

Also applies to: 36-36, 80-95, 101-151, 155-209, 325-333, 376-376

money/test_helpers_test.go (5)

27-37: LGTM! Power of 10 helper is straightforward.

Simple and correct implementation for computing 10^exp as int64.


39-47: LGTM! Absolute value helper is correct.

Straightforward implementation of absolute value for int64.


49-60: LGTM! Float parsing helper uses big.Rat for precision.

Using big.Rat.SetString() for parsing provides accurate rational representation before converting to float64, which is appropriate for test expectations.


62-79: LGTM! Conversion calculation helper uses precise rational arithmetic.

The helper correctly computes expected conversion amounts using big.Rat for precise arithmetic:

  1. Multiplies amount by rate
  2. Scales by target fraction (toFraction)
  3. Divides by source fraction (fromFraction)
  4. Rounds using half-away-from-zero

This approach ensures accurate test expectations for monetary conversions.


81-110: LGTM! Rounding implementation follows half-away-from-zero correctly.

The rounding logic correctly implements half-away-from-zero rounding:

  1. Handles zero case
  2. Separates sign handling
  3. Uses quotient and remainder for rounding decision
  4. Compares 2×remainder against denominator for midpoint check
  5. Restores sign after rounding

This is appropriate for monetary calculations where consistent rounding behaviour is essential.

money/exchange_test.go (2)

635-734: LGTM! Comprehensive real-world conversion test with precise expectations.

This test provides excellent coverage of real-world conversion scenarios:

  • Various currency pairs (USD, EUR, GBP, JPY, CNY, CAD, AUD, CHF)
  • Different amount sizes (invoices, purchases, salaries)
  • Negative amounts (refunds)
  • Inverse rates (JPY to USD using "1/150")
  • Tolerance checks for rounding differences

The use of mustParseFloatMoney and expectedConvertAmountMoney ensures precise test expectations with proper handling of fractional differences across currencies.


736-751: LGTM! Test correctly verifies no implicit cross-rate calculation.

This test appropriately verifies that the exchange system doesn't automatically compute cross-rates. When only USD→EUR and USD→JPY rates are available, attempting EUR→JPY conversion correctly fails with ErrCurrencyConversionNotFound.

This behaviour is important for systems that require explicit rate configuration.

currency/iso.go (1)

63-83: LGTM!

The greedy matching strategy is correctly implemented using stable sort by descending length with alphabetical tie-breaking. This ensures longer symbols are matched first, preventing issues like "$" matching before "US$".

money/db_test.go (1)

9-408: LGTM!

All test cases have been systematically updated to use SGD as the reference currency. The updates are consistent throughout the file, maintain existing test logic, and include proper coverage of Value(), Scan(), separator handling, and thread safety scenarios.

currency/metal.go (2)

3-11: LGTM!

The Provider interface is well-designed with clear method responsibilities. It provides a clean abstraction for currency provisioning.


13-37: The SGD metadata in DefaultProvider is accurate. ISO 4217 specifies the numeric code as 702 and minor unit (fraction) as 2, both of which are correctly implemented. The decimal separator (.), thousands separator (,), grapheme (S$), and template ($1) are reasonable defaults for SGD, though ISO 4217 does not mandate specific formatting choices—these are locale/style dependent.

exchange/real_world_finance_test.go (4)

37-145: LGTM!

The test cases provide comprehensive coverage of real-world currency conversion scenarios, including:

  • Multiple currency pairs with different fractional digits
  • Negative amounts
  • Zero-decimal currencies (JPY)
  • Appropriate tolerance for floating-point precision

The acknowledgement of the JPY rounding boundary (lines 75-77) demonstrates careful consideration of numerical precision.


148-167: LGTM!

The round-trip test effectively validates inverse rate path functionality with appropriate tolerance for precision loss during bidirectional conversion.


169-180: LGTM!

This test correctly validates that the exchange system does not automatically chain conversion rates (EUR → USD → JPY), which is important for predictable behaviour and avoiding compounding conversion errors.


182-254: LGTM!

The helper functions are well-implemented:

  • Use of big.Rat for expected value calculations ensures precise arithmetic without floating-point errors
  • roundRatHalfAwayFromZero correctly implements the rounding strategy
  • All helpers are appropriately scoped and well-tested through their usage
money/money_test.go (1)

16-950: LGTM!

All test cases have been comprehensively and consistently updated to use SGD as the reference currency throughout the file. The updates include:

  • Constructor tests
  • Comparison and arithmetic operations
  • JSON marshalling/unmarshalling
  • Display formatting (correctly updated to "S$" prefix)
  • Edge case and error path coverage

The test logic remains sound and maintains thorough coverage of the Money API.

currency/manager.go (2)

15-67: LGTM!

The constructor family provides flexible initialization options:

  • NewManager() for default usage
  • NewManagerWith() for custom providers
  • NewManagerFor() for provider + dataset with proper validation

Error handling is appropriate, and the fallback to DefaultProvider when curr is nil (line 50) provides sensible default behaviour.


131-160: LGTM!

The getter methods and Resolve() are well-implemented with:

  • Consistent nil-safety checks
  • Clear fallback behaviour in Resolve()
  • Simple, focused responsibilities
money/money.go (6)

8-24: LGTM!

The getter methods Currency() and Amount() are cleanly implemented with proper nil-checking via ensureMoneyProvided().


26-59: LGTM!

The currency comparison methods are well-designed:

  • SameCurrency() provides boolean check
  • AssertSameCurrency() provides error-based validation
  • Proper reuse avoiding duplication
  • Comprehensive nil-checking

61-76: LGTM!

CompareAmount() correctly implements three-way comparison with proper currency validation.


78-131: LGTM!

The comparison predicate methods (Equals, GreaterThan, GreaterThanOrEqual, LessThan, LessThanOrEqual) are consistently implemented by delegating to CompareAmount(), ensuring correct behaviour and good code reuse.


133-172: LGTM!

The sign check methods (IsZero, IsPositive, IsNegative) and Display() are correctly implemented with proper validation. The Display() method appropriately delegates formatting to the currency's formatter.


174-237: LGTM!

The remaining methods are correctly implemented:

  • Compare() properly handles currency mismatch by returning the amount value (as documented)
  • AsMajorUnits() correctly converts to floating-point representation
  • JSON methods appropriately delegate to the JSON parser
  • ensureMoneyProvided() provides robust validation for nil receiver and nil currency

Comment thread calculator/calculator.go
Comment thread calculator/calculator.go
Comment thread exception/exception.go
Comment thread exchange/real_world_finance_test.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a72b023 and 973b49b.

📒 Files selected for processing (10)
  • exchange/exchange_test.go (15 hunks)
  • money/aggregator_test.go (2 hunks)
  • money/db_test.go (13 hunks)
  • money/exchange_test.go (14 hunks)
  • money/factory_test.go (2 hunks)
  • money/json_test.go (21 hunks)
  • money/manager_test.go (25 hunks)
  • money/money_test.go (37 hunks)
  • money/test_helpers_test.go (0 hunks)
  • support/tests.go (1 hunks)
💤 Files with no reviewable changes (1)
  • money/test_helpers_test.go
🧰 Additional context used
🧬 Code graph analysis (6)
money/db_test.go (6)
money/manager.go (1)
  • NewManager (21-27)
support/tests.go (1)
  • TestRequire (8-17)
calculator/calculator.go (1)
  • Amount (6-6)
money/type.go (1)
  • Amount (9-9)
currency/currency.go (1)
  • Currency (12-27)
currency/codes.go (1)
  • SGD (131-131)
money/manager_test.go (3)
currency/codes.go (1)
  • SGD (131-131)
support/tests.go (2)
  • TestRequire (8-17)
  • TestRequireNoErr (19-25)
exception/exception.go (5)
  • ErrEmptyAmountString (31-31)
  • ErrInvalidAmountMultiple (32-32)
  • ErrInvalidAmountFraction (33-33)
  • ErrInvalidAmount (34-34)
  • ErrParserNotProvided (44-44)
money/factory_test.go (3)
support/tests.go (1)
  • TestRequire (8-17)
money/type.go (1)
  • Amount (9-9)
currency/currency.go (1)
  • Currency (12-27)
money/exchange_test.go (4)
support/tests.go (5)
  • TestRequire (8-17)
  • TestRequireNoErr (19-25)
  • TestMustParseFloatMoney (49-60)
  • TestExpectedConvertAmountMoney (62-79)
  • TestAbs64Money (39-47)
calculator/calculator.go (1)
  • Amount (6-6)
exchange/exchange.go (1)
  • NewExchange (18-22)
exception/exception.go (1)
  • ErrCurrencyConversionNotFound (12-12)
money/money_test.go (3)
currency/codes.go (1)
  • SGD (131-131)
support/tests.go (2)
  • TestRequire (8-17)
  • TestRequireNoErr (19-25)
exception/exception.go (5)
  • ErrCurrencyMismatch (10-10)
  • ErrInvalidJSONUnmarshal (17-17)
  • ErrNoMoneyProvided (23-23)
  • ErrInvalidExchangeRate (18-18)
  • ErrNoJSONParserProvided (19-19)
exchange/exchange_test.go (3)
exception/exception.go (2)
  • ErrInvalidExchangeRate (18-18)
  • ErrCurrencyConversionNotFound (12-12)
exchange/exchange.go (2)
  • Exchange (12-15)
  • NewExchange (18-22)
support/tests.go (4)
  • TestRequireNoErr (19-25)
  • TestMustParseFloatMoney (49-60)
  • TestExpectedConvertAmountMoney (62-79)
  • TestAbs64Money (39-47)
🔇 Additional comments (22)
support/tests.go (2)

8-17: LGTM!

TestRequire and TestRequireNoErr are well-implemented generic test helpers with correct use of t.Helper() for proper stack trace reporting.

Also applies to: 19-25


62-79: LGTM!

TestExpectedConvertAmountMoney correctly performs currency conversion using arbitrary precision rational arithmetic, handling fractional digit scaling properly before rounding.

money/factory_test.go (1)

7-7: LGTM!

The migration to support.TestRequire is consistent with the new test helper utilities and correctly unwraps the (T, error) return types from m.Amount() and m.Currency().

Also applies to: 202-202, 207-207

money/aggregator_test.go (2)

10-10: LGTM!

The migration to support.TestRequireNoErr for validating AddRate calls is consistent with the new test helper utilities.

Also applies to: 15-15


39-55: LGTM!

The currency code change from USD to SGD aligns with the broader test data standardisation across the repository. The tests correctly verify nil aggregator behaviour regardless of which currency is used.

money/db_test.go (1)

7-7: LGTM!

The migration to SGD as the default test currency and adoption of support.TestRequire/support.TestRequireNoErr is consistent across all test cases. The changes maintain the same test coverage and assertions.

Also applies to: 11-11, 18-18, 34-36, 48-50, 62-62, 67-67, 82-82, 119-119, 126-127, 142-143, 154-155, 252-252, 258-258, 304-304

money/manager_test.go (6)

11-11: LGTM!

Import of the support package is correctly added for the new test helper utilities.


59-113: LGTM!

TestManagerCreate correctly migrated to use SGD as the primary test currency and support.TestRequire for assertions. The test maintains comprehensive coverage of currency creation scenarios.


116-149: LGTM!

TestManagerCreateFromFloat and TestManagerCreateFromString tests are well-structured with comprehensive edge cases including trailing decimals, negative values, and various decimal place configurations. The migration to SGD and support helpers is consistent.

Also applies to: 151-212


343-433: LGTM!

Arithmetic operation tests (TestManagerAdd, TestManagerSubtract) correctly validate same-currency operations with SGD and properly test the error path for different currencies (USD/EUR).


529-555: Verify rounding expectations for SGD.

The test comment on Line 532 says "rounds SGD amount" and expects 1234 → 1200. Ensure this matches the actual rounding behaviour of the Round method for a 2-decimal-place currency. The expected values suggest rounding to the nearest 100 minor units.


703-722: LGTM!

Error path tests for CreateFromString correctly verify specific error types using errors.Is(). The nil parser test properly validates the ErrParserNotProvided error.

money/json_test.go (5)

12-12: LGTM!

The import of the support package and migration to support.TestRequireNoErr for SetMarshal validation is consistent with the new test utilities.

Also applies to: 32-34


51-79: LGTM!

TestJSONSetUnmarshal correctly tests custom unmarshalling with SGD as the target currency. The assertions using support.TestRequire properly validate both amount and currency code.


145-161: LGTM!

Thread safety tests correctly updated to use SGD currency in both marshal and unmarshal operations. The concurrent goroutine tests maintain proper race condition coverage.


383-399: LGTM!

Good test coverage verifying that when all custom JSON parser functions are nil, the default currency resolves to SGD. This ensures backward compatibility with the default behaviour.


490-531: LGTM!

Comprehensive coverage of JSON unmarshal edge cases including decimal rounding (positive and negative), overflow detection, unknown currency handling, and default currency fallback to SGD.

money/money_test.go (1)

12-12: LGTM! Excellent refactoring to standardise test currency and error handling.

The migration from USD to SGD and the adoption of support package helpers (TestRequire, TestRequireNoErr) is consistent throughout. This refactoring improves test maintainability by centralising error-handling patterns and provides a uniform currency context across the test suite.

Also applies to: 18-952

money/exchange_test.go (2)

11-11: LGTM! Consistent adoption of support package helpers.

The migration to support.TestRequire and support.TestRequireNoErr is consistent throughout the existing tests.

Also applies to: 34-590


636-752: Excellent additions! Real-world conversion scenarios strengthen test coverage.

The new tests provide valuable coverage:

  • TestConverter_RealWorld_CommonCurrencies exercises multiple currency pairs with fixed fixture rates and tolerance handling for floating-point precision
  • TestConverter_RealWorld_NoImplicitCrossRate verifies that cross-rate conversions (EUR→JPY) correctly fail when only USD→EUR and USD→JPY rates exist, confirming the library doesn't automatically chain conversions

Both tests properly use parallel execution and the support helpers for deterministic decimal rate parsing.

exchange/exchange_test.go (2)

10-10: LGTM! Consistent refactoring with support helpers and SGD context.

The updates to use support package helpers and migrate some test fixtures to SGD are consistent with the broader test suite changes.

Also applies to: 24-540


542-711: Outstanding test additions! Comprehensive real-world scenarios at the exchange layer.

Three valuable new tests strengthen the exchange conversion logic:

  1. TestExchange_ConvertAmount_RealWorldCommonCurrencies (lines 542-677): Covers 8 real-world scenarios including invoices, salaries, card purchases, and negative refunds. Properly accounts for floating-point precision with tolerance bounds (e.g., wantTol: 1 for JPY conversions at x.5 boundaries).

  2. TestExchange_ConvertAmount_UsesInverseRatePath_RoundTripBounded (lines 679-698): Verifies that USD→EUR→USD round-trip conversions via inverse rates stay within 1 cent, confirming the inverse rate calculation maintains acceptable precision.

  3. TestExchange_ConvertAmount_DoesNotAssumeChainedCrossRates (lines 700-711): Confirms the exchange does not automatically chain rates (EUR→JPY fails when only USD→EUR and USD→JPY rates exist), preventing unexpected transitive conversions.

All tests properly leverage parallel execution and support.TestMustParseFloatMoney for deterministic fixture rates.

Comment thread money/db_test.go Outdated
Comment thread money/db_test.go Outdated
Comment thread tests/support.go
Comment thread tests/support.go
Comment thread tests/support.go
Comment thread tests/support.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 973b49b and 5923c3d.

📒 Files selected for processing (1)
  • Makefile (1 hunks)

Comment thread Makefile Outdated
@gocanto
gocanto merged commit 6a37e12 into main Dec 15, 2025
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant