Skip to content

Commit c9cd03c

Browse files
committed
fix: guard against nil InstrumentResolver in balance_mapper
Add nil-check at the start of ToDomainMoney and ToDomainMoneyFromInstrumentAmount so that a missing REFERENCE_DATA_SERVICE_URL configuration produces a controlled error rather than a request-time panic. Introduces ErrNilInstrumentResolver sentinel for callers to distinguish this configuration error from input validation errors.
1 parent 8c45fda commit c9cd03c

2 files changed

Lines changed: 39 additions & 2 deletions

File tree

services/position-keeping/adapters/balance_mapper.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@ var ErrNilGoogleMoney = errors.New("MoneyAmount.Amount (google.type.Money) is ni
3030
// ErrInvalidCurrency is returned when the currency code is empty or invalid.
3131
var ErrInvalidCurrency = errors.New("invalid or empty currency code")
3232

33+
// ErrNilInstrumentResolver is returned when a nil InstrumentResolver is provided.
34+
var ErrNilInstrumentResolver = errors.New("instrument resolver is required")
35+
3336
// ErrUnknownProtoBalanceType is returned when the proto BalanceType value is not recognized.
3437
var ErrUnknownProtoBalanceType = errors.New("unknown proto BalanceType value")
3538

@@ -118,6 +121,9 @@ func ToProtoMoneyAmount(domainMoney domain.Money) *commonv1.MoneyAmount {
118121
// Returns an error if the MoneyAmount or its inner google.type.Money is nil,
119122
// or if the instrument code cannot be resolved.
120123
func ToDomainMoney(ctx context.Context, resolver refdata.InstrumentResolver, protoMoney *commonv1.MoneyAmount) (domain.Money, error) {
124+
if resolver == nil {
125+
return domain.Money{}, ErrNilInstrumentResolver
126+
}
121127
if protoMoney == nil {
122128
return domain.Money{}, ErrNilMoneyAmount
123129
}
@@ -194,6 +200,9 @@ func ToProtoInstrumentAmountFromAsset(domainAsset domain.Asset) *quantityv1.Inst
194200
// registered instrument code (currencies, energy units, compute hours, carbon credits, etc.).
195201
// Returns an error if the amount is invalid or the instrument code cannot be resolved.
196202
func ToDomainMoneyFromInstrumentAmount(ctx context.Context, resolver refdata.InstrumentResolver, protoAmount *quantityv1.InstrumentAmount) (domain.Money, error) {
203+
if resolver == nil {
204+
return domain.Money{}, ErrNilInstrumentResolver
205+
}
197206
if protoAmount == nil {
198207
return domain.Money{}, ErrNilInstrumentAmount
199208
}

services/position-keeping/adapters/balance_mapper_test.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,7 @@ func TestToDomainMoney(t *testing.T) {
324324
expectedCur domain.Currency
325325
expectError bool
326326
errorIs error
327+
nilResolver bool
327328
}{
328329
{
329330
name: "GBP with cents",
@@ -403,6 +404,13 @@ func TestToDomainMoney(t *testing.T) {
403404
expectedCur: domain.CurrencyCHF,
404405
expectError: false,
405406
},
407+
{
408+
name: "nil resolver returns error",
409+
proto: &commonv1.MoneyAmount{Amount: &money.Money{CurrencyCode: "GBP"}},
410+
expectError: true,
411+
errorIs: adapters.ErrNilInstrumentResolver,
412+
nilResolver: true,
413+
},
406414
{
407415
name: "nil MoneyAmount returns error",
408416
proto: nil,
@@ -445,7 +453,11 @@ func TestToDomainMoney(t *testing.T) {
445453

446454
for _, tt := range tests {
447455
t.Run(tt.name, func(t *testing.T) {
448-
result, err := adapters.ToDomainMoney(ctx, resolver, tt.proto)
456+
r := refdata.InstrumentResolver(resolver)
457+
if tt.nilResolver {
458+
r = nil
459+
}
460+
result, err := adapters.ToDomainMoney(ctx, r, tt.proto)
449461

450462
if tt.expectError {
451463
require.Error(t, err)
@@ -717,7 +729,19 @@ func TestToDomainMoneyFromInstrumentAmount(t *testing.T) {
717729
errContains string
718730
expectAmount string
719731
expectCode string
732+
nilResolver bool
720733
}{
734+
{
735+
name: "nil resolver returns error",
736+
proto: &quantityv1.InstrumentAmount{
737+
Amount: "100",
738+
InstrumentCode: "GBP",
739+
Version: 1,
740+
},
741+
expectError: true,
742+
errContains: "resolver",
743+
nilResolver: true,
744+
},
721745
{
722746
name: "valid GBP amount",
723747
proto: &quantityv1.InstrumentAmount{
@@ -823,7 +847,11 @@ func TestToDomainMoneyFromInstrumentAmount(t *testing.T) {
823847

824848
for _, tt := range tests {
825849
t.Run(tt.name, func(t *testing.T) {
826-
result, err := adapters.ToDomainMoneyFromInstrumentAmount(ctx, resolver, tt.proto)
850+
r := refdata.InstrumentResolver(resolver)
851+
if tt.nilResolver {
852+
r = nil
853+
}
854+
result, err := adapters.ToDomainMoneyFromInstrumentAmount(ctx, r, tt.proto)
827855

828856
if tt.expectError {
829857
require.Error(t, err)

0 commit comments

Comments
 (0)