Files
any2anexoj/internal/trading212/serializer.go
T
natercioandnatercio 9bd4230ff1
Badges / coveralls (push) Successful in 1m4s
Internal state persistence (#27)
Allow us to generate year over year reports without having to rerun everything from the beginning.

Co-authored-by: Natercio Moniz <[email protected]>
2026-08-03 00:33:06 +01:00

89 lines
2.6 KiB
Go

package trading212
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/nmoniz/any2anexoj/internal"
"github.com/nmoniz/any2anexoj/internal/ofigi"
"github.com/shopspring/decimal"
)
// recordState is the on-disk representation of a trading212.Record.
// Nature is intentionally omitted because it is resolved lazily via the
// OpenFIGI client on demand.
type recordState struct {
Symbol string `json:"symbol"`
Timestamp time.Time `json:"timestamp"`
Kind internal.Kind `json:"kind"`
Quantity decimal.Decimal `json:"quantity"`
Price decimal.Decimal `json:"price"`
Fees decimal.Decimal `json:"fees"`
Taxes decimal.Decimal `json:"taxes"`
}
// RecordSerializer encodes and decodes trading212.Record values for the
// internal persistence layer. On decode it re-wires the lazy natureGetter to
// the supplied OpenFIGI client so a restored Record resolves Nature() via a
// fresh API call when needed.
type RecordSerializer struct {
figi *ofigi.Client
}
// NewRecordSerializer returns a RecordSerializer that uses figi to resolve
// Record.Nature() on load.
func NewRecordSerializer(figi *ofigi.Client) *RecordSerializer {
return &RecordSerializer{figi: figi}
}
// MarshalRecord encodes the given internal.Record as JSON. It returns an
// error if the record is not a trading212.Record (or a *trading212.Record).
func (s *RecordSerializer) MarshalRecord(_ context.Context, r internal.Record) ([]byte, error) {
var rec Record
switch v := r.(type) {
case Record:
rec = v
case *Record:
if v == nil {
return nil, fmt.Errorf("trading212: cannot marshal nil *trading212.Record")
}
rec = *v
default:
return nil, fmt.Errorf("trading212: cannot marshal %T as trading212.Record", r)
}
state := recordState{
Symbol: rec.symbol,
Timestamp: rec.timestamp,
Kind: rec.kind,
Quantity: rec.quantity,
Price: rec.price,
Fees: rec.fees,
Taxes: rec.taxes,
}
return json.Marshal(state)
}
// UnmarshalRecord decodes a JSON-encoded trading212.Record and re-wires its
// natureGetter to the serializer's OpenFIGI client.
func (s *RecordSerializer) UnmarshalRecord(ctx context.Context, data []byte) (internal.Record, error) {
var state recordState
if err := json.Unmarshal(data, &state); err != nil {
return nil, fmt.Errorf("unmarshal trading212 record: %w", err)
}
return Record{
symbol: state.Symbol,
timestamp: state.Timestamp,
kind: state.Kind,
quantity: state.Quantity,
price: state.Price,
fees: state.Fees,
taxes: state.Taxes,
natureGetter: figiNatureGetter(ctx, s.figi, state.Symbol),
}, nil
}