Cache calls to SecurityTypeByISIN #18

Merged
natercio merged 2 commits from cache-figi-responses into main 2025-11-25 13:52:05 +00:00
2 changed files with 29 additions and 3 deletions
Showing only changes of commit 64bbf8d129 - Show all commits

View File

@@ -18,7 +18,10 @@ type OpenFIGI struct {
client *http.Client
mappingLimiter *rate.Limiter
mu sync.RWMutex
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.
securityTypeCache map[string]string
}
@@ -39,6 +42,16 @@ func (of *OpenFIGI) SecurityTypeByISIN(ctx context.Context, isin string) (string
}
of.mu.RUnlock()
of.mu.Lock()
defer of.mu.Unlock()
// we check again because there could be more than one concurrent cache miss and we want only one
// of them to result in an actual request. When the first one releases the lock the following
// reads will hit the cache.
if secType, ok := of.securityTypeCache[isin]; ok {
return secType, nil
}
if len(isin) != 12 || countries.ByName(isin[:2]) == countries.Unknown {
return "", fmt.Errorf("invalid ISIN: %s", isin)
}
@@ -90,10 +103,11 @@ func (of *OpenFIGI) SecurityTypeByISIN(ctx context.Context, isin string) (string
// It is not possible that an isin is assign to diferent security types, therefore we can assume
// all entries have the same securityType value.
secType := resBody[0].Data[0].SecurityType
if secType == "" {
return "", fmt.Errorf("empty security type returned for ISIN: %s", isin)
}
of.mu.Lock()
of.securityTypeCache[isin] = secType
of.mu.Unlock()
return secType, nil
}

View File

@@ -79,6 +79,18 @@ func TestOpenFIGI_SecurityTypeByISIN(t *testing.T) {
isin: "NL0000235190",
wantErr: true,
},
{
name: "empty securityType",
client: NewTestClient(t, func(req *http.Request) (*http.Response, error) {
return &http.Response{
Status: http.StatusText(http.StatusOK),
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewBufferString(`[{"data":[{"securityType":""}]}]`)),
}, nil
}),
isin: "NL0000235190",
wantErr: true,
},
{
name: "client error",
client: NewTestClient(t, func(req *http.Request) (*http.Response, error) {