From fa955529714367de135ad90b8a1d887cb0f6539b Mon Sep 17 00:00:00 2001 From: Natercio Moniz Date: Sun, 2 Aug 2026 10:55:31 +0100 Subject: [PATCH] moved open-figi implementation into own package --- cmd/any2anexoj-cli/main.go | 3 ++- internal/{open_figi.go => ofigi/client.go} | 15 ++++++------ .../client_test.go} | 24 +++++++++---------- 3 files changed, 22 insertions(+), 20 deletions(-) rename internal/{open_figi.go => ofigi/client.go} (93%) rename internal/{open_figi_test.go => ofigi/client_test.go} (89%) diff --git a/cmd/any2anexoj-cli/main.go b/cmd/any2anexoj-cli/main.go index 08a4380..522b26f 100644 --- a/cmd/any2anexoj-cli/main.go +++ b/cmd/any2anexoj-cli/main.go @@ -10,6 +10,7 @@ import ( "time" "github.com/nmoniz/any2anexoj/internal" + "github.com/nmoniz/any2anexoj/internal/ofigi" "github.com/nmoniz/any2anexoj/internal/trading212" "github.com/spf13/pflag" "golang.org/x/sync/errgroup" @@ -97,7 +98,7 @@ func run(ctx context.Context) error { func getReader(platform string, ofAPIKey string) (internal.RecordReader, error) { switch platform { case "trading212": - return trading212.NewRecordReader(os.Stdin, internal.NewOpenFIGI(&http.Client{Timeout: 5 * time.Second}, ofAPIKey)), nil + return trading212.NewRecordReader(os.Stdin, ofigi.NewClient(&http.Client{Timeout: 5 * time.Second}, ofAPIKey)), nil default: return nil, fmt.Errorf("unsupported platform: %s", platform) } diff --git a/internal/open_figi.go b/internal/ofigi/client.go similarity index 93% rename from internal/open_figi.go rename to internal/ofigi/client.go index d4bce71..b969cde 100644 --- a/internal/open_figi.go +++ b/internal/ofigi/client.go @@ -1,4 +1,4 @@ -package internal +package ofigi import ( "bytes" @@ -16,8 +16,8 @@ import ( var OpenFIGIAPIKeyHeader = http.CanonicalHeaderKey("X-OPENFIGI-APIKEY") -// OpenFIGI is a small adapter for the openfigi.com api. -type OpenFIGI struct { +// Client is a thin adapter for the openfigi.com api. +type Client struct { client *http.Client apiKey string mappingLimiter *rate.Limiter @@ -25,12 +25,13 @@ type OpenFIGI struct { mu sync.RWMutex // TODO: there's no eviction policy at the moment as this is only used by short-lived application // which processes a relatively small amount of records. We need to consider using an external - // cache lib (like golang-lru or go-cache) if this becomes a problem or implement this ourselves. + // cache lib (like golang-lru or go-cache) if this becomes a problem or implement eviction + // ourselves. securityTypeCache map[string]string } // NewOpenFIGI creates an OpenFIGI client that uses the API key if provided -func NewOpenFIGI(c *http.Client, apiKey string) *OpenFIGI { +func NewOpenFIGI(c *http.Client, apiKey string) *Client { // Rate limits as per https://www.openfigi.com/api/documentation#rate-limits limiter := rate.NewLimiter(rate.Every(time.Minute), 25) if len(apiKey) > 0 { @@ -40,7 +41,7 @@ func NewOpenFIGI(c *http.Client, apiKey string) *OpenFIGI { slog.Debug("OpenFIGI client: created with puplic rate limits") } - return &OpenFIGI{ + return &Client{ client: c, apiKey: apiKey, mappingLimiter: limiter, @@ -48,7 +49,7 @@ func NewOpenFIGI(c *http.Client, apiKey string) *OpenFIGI { } } -func (of *OpenFIGI) SecurityTypeByISIN(ctx context.Context, isin string) (string, error) { +func (of *Client) SecurityTypeByISIN(ctx context.Context, isin string) (string, error) { of.mu.RLock() if secType, ok := of.securityTypeCache[isin]; ok { of.mu.RUnlock() diff --git a/internal/open_figi_test.go b/internal/ofigi/client_test.go similarity index 89% rename from internal/open_figi_test.go rename to internal/ofigi/client_test.go index 3b31107..052a23c 100644 --- a/internal/open_figi_test.go +++ b/internal/ofigi/client_test.go @@ -1,4 +1,4 @@ -package internal_test +package ofigi_test import ( "bytes" @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/nmoniz/any2anexoj/internal" + "github.com/nmoniz/any2anexoj/internal/ofigi" ) func TestOpenFIGI_SecurityTypeByISIN(t *testing.T) { @@ -110,7 +110,7 @@ func TestOpenFIGI_SecurityTypeByISIN(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - of := internal.NewOpenFIGI(tt.client, "") + of := ofigi.NewOpenFIGI(tt.client, "") got, gotErr := of.SecurityTypeByISIN(context.Background(), tt.isin) if gotErr != nil { @@ -145,7 +145,7 @@ func TestOpenFIGI_SecurityTypeByISIN_Cache(t *testing.T) { }, nil }) - of := internal.NewOpenFIGI(c, "") + of := ofigi.NewOpenFIGI(c, "") got, gotErr := of.SecurityTypeByISIN(t.Context(), "NL0000235190") if gotErr != nil { @@ -171,15 +171,15 @@ func TestOpenFIGI_SecurityTypeByISIN_APIKey(t *testing.T) { wantAPIKey := "123abc-456xyz" c := NewTestClient(t, func(req *http.Request) (*http.Response, error) { - value, ok := req.Header[internal.OpenFIGIAPIKeyHeader] + value, ok := req.Header[ofigi.OpenFIGIAPIKeyHeader] if !ok { - t.Fatalf("want %q header but got none: %v", internal.OpenFIGIAPIKeyHeader, req.Header) + t.Fatalf("want %q header but got none: %v", ofigi.OpenFIGIAPIKeyHeader, req.Header) } if len(value) != 1 { - t.Fatalf("want exactly one %q header value but got %d", internal.OpenFIGIAPIKeyHeader, len(value)) + t.Fatalf("want exactly one %q header value but got %d", ofigi.OpenFIGIAPIKeyHeader, len(value)) } if value[0] != wantAPIKey { - t.Fatalf("want %q header value %q but got %q", internal.OpenFIGIAPIKeyHeader, wantAPIKey, value[0]) + t.Fatalf("want %q header value %q but got %q", ofigi.OpenFIGIAPIKeyHeader, wantAPIKey, value[0]) } return &http.Response{ Status: http.StatusText(http.StatusOK), @@ -187,7 +187,7 @@ func TestOpenFIGI_SecurityTypeByISIN_APIKey(t *testing.T) { Body: io.NopCloser(bytes.NewBufferString(`[{"data":[{"securityType":"Common Stock"}]}]`)), }, nil }) - of := internal.NewOpenFIGI(c, wantAPIKey) + of := ofigi.NewOpenFIGI(c, wantAPIKey) _, err := of.SecurityTypeByISIN(t.Context(), "US1234567890") if err != nil { @@ -197,9 +197,9 @@ func TestOpenFIGI_SecurityTypeByISIN_APIKey(t *testing.T) { t.Run("without API key", func(t *testing.T) { c := NewTestClient(t, func(req *http.Request) (*http.Response, error) { - _, ok := req.Header[internal.OpenFIGIAPIKeyHeader] + _, ok := req.Header[ofigi.OpenFIGIAPIKeyHeader] if ok { - t.Fatalf("want no %s header but got one", internal.OpenFIGIAPIKeyHeader) + t.Fatalf("want no %s header but got one", ofigi.OpenFIGIAPIKeyHeader) } return &http.Response{ Status: http.StatusText(http.StatusOK), @@ -207,7 +207,7 @@ func TestOpenFIGI_SecurityTypeByISIN_APIKey(t *testing.T) { Body: io.NopCloser(bytes.NewBufferString(`[{"data":[{"securityType":"Common Stock"}]}]`)), }, nil }) - of := internal.NewOpenFIGI(c, "") + of := ofigi.NewOpenFIGI(c, "") _, err := of.SecurityTypeByISIN(t.Context(), "US1234567890") if err != nil { t.Fatalf("want success but got an error: %s", err)