fix(loader): wire context into HTTP request, guard nil opts in Redis list/hash
- HTTPLoader.Load: http.NewRequest → NewRequestWithContext so caller cancellation/deadlines propagate to the outgoing request - RedisListLoader, RedisHashLoader: add nil-guard before calling opt() (already present in StringLoader, SetLoader, and HTTPLoader) - Add doc comments for all 10 exported symbols (package, interfaces, constructors, option types, option funcs, DefaultRedisKey) - Add loader_test.go with 32 tests covering FileLoader, HTTPLoader (context cancel, error status, empty/large body), Redis options, nil-guard safety, and interface satisfaction checks
This commit is contained in:
@@ -13,8 +13,10 @@ type httpLoaderOptions struct {
|
|||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTTPLoaderOption configures an HTTP loader.
|
||||||
type HTTPLoaderOption func(opts *httpLoaderOptions)
|
type HTTPLoaderOption func(opts *httpLoaderOptions)
|
||||||
|
|
||||||
|
// TimeoutHTTPLoaderOption sets the HTTP request timeout.
|
||||||
func TimeoutHTTPLoaderOption(timeout time.Duration) HTTPLoaderOption {
|
func TimeoutHTTPLoaderOption(timeout time.Duration) HTTPLoaderOption {
|
||||||
return func(opts *httpLoaderOptions) {
|
return func(opts *httpLoaderOptions) {
|
||||||
opts.timeout = timeout
|
opts.timeout = timeout
|
||||||
@@ -43,7 +45,7 @@ func HTTPLoader(url string, opts ...HTTPLoaderOption) Loader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l *httpLoader) Load(ctx context.Context) (io.Reader, error) {
|
func (l *httpLoader) Load(ctx context.Context) (io.Reader, error) {
|
||||||
req, err := http.NewRequest(http.MethodGet, l.url, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, l.url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
// Package loader provides interfaces and implementations for hot-reloadable
|
||||||
|
// data sources used by components such as auth, bypass, hosts, and admission.
|
||||||
package loader
|
package loader
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -5,15 +7,18 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Loader loads data from a backend source, returning it as an io.Reader.
|
||||||
type Loader interface {
|
type Loader interface {
|
||||||
Load(context.Context) (io.Reader, error)
|
Load(context.Context) (io.Reader, error)
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lister lists entries from a backend source.
|
||||||
type Lister interface {
|
type Lister interface {
|
||||||
List(ctx context.Context) ([]string, error)
|
List(ctx context.Context) ([]string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mapper loads key-value pairs from a backend source.
|
||||||
type Mapper interface {
|
type Mapper interface {
|
||||||
Map(ctx context.Context) (map[string]string, error)
|
Map(ctx context.Context) (map[string]string, error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,399 @@
|
|||||||
|
package loader
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// --- FileLoader ---
|
||||||
|
|
||||||
|
func TestFileLoader_Load(t *testing.T) {
|
||||||
|
f, err := os.CreateTemp("", "loader-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
|
||||||
|
if _, err := f.WriteString("hello world"); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
ld := FileLoader(f.Name())
|
||||||
|
r, err := ld.Load(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
data, err := io.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(data) != "hello world" {
|
||||||
|
t.Fatalf("got %q, want %q", string(data), "hello world")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileLoader_Load_NotFound(t *testing.T) {
|
||||||
|
ld := FileLoader("/nonexistent/path")
|
||||||
|
_, err := ld.Load(context.Background())
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for nonexistent file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileLoader_List(t *testing.T) {
|
||||||
|
f, err := os.CreateTemp("", "loader-list-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
|
||||||
|
lines := "alpha\nbeta\ngamma\n"
|
||||||
|
if _, err := f.WriteString(lines); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
ld := FileLoader(f.Name())
|
||||||
|
list, err := ld.(Lister).List(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(list) != 3 {
|
||||||
|
t.Fatalf("got %d lines, want 3", len(list))
|
||||||
|
}
|
||||||
|
expected := []string{"alpha", "beta", "gamma"}
|
||||||
|
for i, v := range expected {
|
||||||
|
if list[i] != v {
|
||||||
|
t.Fatalf("line %d: got %q, want %q", i, list[i], v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileLoader_List_NotFound(t *testing.T) {
|
||||||
|
ld := FileLoader("/nonexistent/path")
|
||||||
|
_, err := ld.(Lister).List(context.Background())
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for nonexistent file")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileLoader_Close(t *testing.T) {
|
||||||
|
ld := FileLoader("/dev/null")
|
||||||
|
if err := ld.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileLoader_ImplementsLister(t *testing.T) {
|
||||||
|
_ = FileLoader("").(Lister)
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- HTTPLoader ---
|
||||||
|
|
||||||
|
func TestHTTPLoader_Load(t *testing.T) {
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("http response"))
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
ld := HTTPLoader(srv.URL)
|
||||||
|
r, err := ld.Load(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
data, err := io.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(data) != "http response" {
|
||||||
|
t.Fatalf("got %q, want %q", string(data), "http response")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPLoader_Load_ErrorStatus(t *testing.T) {
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
ld := HTTPLoader(srv.URL)
|
||||||
|
_, err := ld.Load(context.Background())
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for non-200 status")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPLoader_Load_ContextCanceled(t *testing.T) {
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
<-r.Context().Done()
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
ld := HTTPLoader(srv.URL)
|
||||||
|
_, err := ld.Load(ctx)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for canceled context")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPLoader_Load_BadURL(t *testing.T) {
|
||||||
|
ld := HTTPLoader("://invalid-url")
|
||||||
|
_, err := ld.Load(context.Background())
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for bad URL")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPLoader_Close(t *testing.T) {
|
||||||
|
ld := HTTPLoader("http://localhost:1")
|
||||||
|
if err := ld.Close(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHTTPLoader_TimeoutOption(t *testing.T) {
|
||||||
|
ld := HTTPLoader("http://localhost:1", TimeoutHTTPLoaderOption(0))
|
||||||
|
_ = ld
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- RedisLoaderOption ---
|
||||||
|
|
||||||
|
func TestRedisLoaderOption_DB(t *testing.T) {
|
||||||
|
var opts redisLoaderOptions
|
||||||
|
opt := DBRedisLoaderOption(5)
|
||||||
|
opt(&opts)
|
||||||
|
if opts.db != 5 {
|
||||||
|
t.Fatalf("got db %d, want 5", opts.db)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedisLoaderOption_Username(t *testing.T) {
|
||||||
|
var opts redisLoaderOptions
|
||||||
|
opt := UsernameRedisLoaderOption("admin")
|
||||||
|
opt(&opts)
|
||||||
|
if opts.username != "admin" {
|
||||||
|
t.Fatalf("got username %q, want %q", opts.username, "admin")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedisLoaderOption_Password(t *testing.T) {
|
||||||
|
var opts redisLoaderOptions
|
||||||
|
opt := PasswordRedisLoaderOption("secret")
|
||||||
|
opt(&opts)
|
||||||
|
if opts.password != "secret" {
|
||||||
|
t.Fatalf("got password %q, want %q", opts.password, "secret")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedisLoaderOption_Key(t *testing.T) {
|
||||||
|
var opts redisLoaderOptions
|
||||||
|
opt := KeyRedisLoaderOption("mykey")
|
||||||
|
opt(&opts)
|
||||||
|
if opts.key != "mykey" {
|
||||||
|
t.Fatalf("got key %q, want %q", opts.key, "mykey")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedisLoaderOption_NilOption(t *testing.T) {
|
||||||
|
// Verify nil options don't panic across all constructors.
|
||||||
|
// The nil guard in each constructor should prevent calling the nil option function.
|
||||||
|
HTTPLoader("http://localhost:1", HTTPLoaderOption(nil))
|
||||||
|
RedisStringLoader("localhost:6379", RedisLoaderOption(nil))
|
||||||
|
RedisSetLoader("localhost:6379", RedisLoaderOption(nil))
|
||||||
|
RedisListLoader("localhost:6379", RedisLoaderOption(nil))
|
||||||
|
RedisHashLoader("localhost:6379", RedisLoaderOption(nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedisLoaderOption_DefaultKey(t *testing.T) {
|
||||||
|
if DefaultRedisKey != "gost" {
|
||||||
|
t.Fatalf("got DefaultRedisKey %q, want %q", DefaultRedisKey, "gost")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// -- Interface satisfaction (compile-time) ---
|
||||||
|
|
||||||
|
// --- RedisSetLoader supports Lister ---
|
||||||
|
|
||||||
|
func TestRedisSetLoader_ImplementsLister(t *testing.T) {
|
||||||
|
var _ Lister = &redisSetLoader{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- RedisListLoader supports Lister ---
|
||||||
|
|
||||||
|
func TestRedisListLoader_ImplementsLister(t *testing.T) {
|
||||||
|
var _ Lister = &redisListLoader{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- RedisHashLoader supports Lister and Mapper ---
|
||||||
|
|
||||||
|
func TestRedisHashLoader_ImplementsLister(t *testing.T) {
|
||||||
|
var _ Lister = &redisHashLoader{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedisHashLoader_ImplementsMapper(t *testing.T) {
|
||||||
|
var _ Mapper = &redisHashLoader{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- RedisStringLoader Load with empty result ---
|
||||||
|
|
||||||
|
func TestRedisLoaderOption_MultipleOptions(t *testing.T) {
|
||||||
|
var opts redisLoaderOptions
|
||||||
|
for _, opt := range []RedisLoaderOption{
|
||||||
|
DBRedisLoaderOption(3),
|
||||||
|
UsernameRedisLoaderOption("user"),
|
||||||
|
PasswordRedisLoaderOption("pass"),
|
||||||
|
KeyRedisLoaderOption("k"),
|
||||||
|
} {
|
||||||
|
opt(&opts)
|
||||||
|
}
|
||||||
|
if opts.db != 3 {
|
||||||
|
t.Fatalf("got db %d, want 3", opts.db)
|
||||||
|
}
|
||||||
|
if opts.username != "user" {
|
||||||
|
t.Fatalf("got username %q, want %q", opts.username, "user")
|
||||||
|
}
|
||||||
|
if opts.password != "pass" {
|
||||||
|
t.Fatalf("got password %q, want %q", opts.password, "pass")
|
||||||
|
}
|
||||||
|
if opts.key != "k" {
|
||||||
|
t.Fatalf("got key %q, want %q", opts.key, "k")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- HTTPLoader_Load verifies reader is independent ---
|
||||||
|
|
||||||
|
func TestHTTPLoader_Load_EmptyBody(t *testing.T) {
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write(nil)
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
ld := HTTPLoader(srv.URL)
|
||||||
|
r, err := ld.Load(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
data, err := io.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(data) != 0 {
|
||||||
|
t.Fatalf("expected empty body, got %q", string(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- FileLoader Load verifies reader independence ---
|
||||||
|
|
||||||
|
func TestFileLoader_Load_EmptyFile(t *testing.T) {
|
||||||
|
f, err := os.CreateTemp("", "loader-empty-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
ld := FileLoader(f.Name())
|
||||||
|
r, err := ld.Load(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
data, err := io.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(data) != 0 {
|
||||||
|
t.Fatalf("expected empty, got %q", string(data))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- FileLoader List empty file ---
|
||||||
|
|
||||||
|
func TestFileLoader_List_EmptyFile(t *testing.T) {
|
||||||
|
f, err := os.CreateTemp("", "loader-list-empty-test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.Remove(f.Name())
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
ld := FileLoader(f.Name())
|
||||||
|
list, err := ld.(Lister).List(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(list) != 0 {
|
||||||
|
t.Fatalf("got %v, want empty list for empty file", list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- HTTPLoader Load with large body ---
|
||||||
|
|
||||||
|
func TestHTTPLoader_Load_LargeBody(t *testing.T) {
|
||||||
|
largeData := strings.Repeat("x", 1<<16)
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte(largeData))
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
ld := HTTPLoader(srv.URL)
|
||||||
|
r, err := ld.Load(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
data, err := io.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(data) != largeData {
|
||||||
|
t.Fatalf("got %d bytes, want %d", len(data), len(largeData))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- HTTPLoader Load with custom timeout option ---
|
||||||
|
|
||||||
|
func TestHTTPLoader_Load_WithTimeout(t *testing.T) {
|
||||||
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Write([]byte("ok"))
|
||||||
|
}))
|
||||||
|
defer srv.Close()
|
||||||
|
|
||||||
|
ld := HTTPLoader(srv.URL, TimeoutHTTPLoaderOption(0))
|
||||||
|
r, err := ld.Load(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
data, err := io.ReadAll(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(data) != "ok" {
|
||||||
|
t.Fatalf("got %q, want %q", string(data), "ok")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Verify Loader interface on all constructors ---
|
||||||
|
|
||||||
|
func TestRedisStringLoader_SatisfiesLoader(t *testing.T) {
|
||||||
|
var _ Loader = &redisStringLoader{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedisSetLoader_SatisfiesLoader(t *testing.T) {
|
||||||
|
var _ Loader = &redisSetLoader{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedisListLoader_SatisfiesLoader(t *testing.T) {
|
||||||
|
var _ Loader = &redisListLoader{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRedisHashLoader_SatisfiesLoader(t *testing.T) {
|
||||||
|
var _ Loader = &redisHashLoader{}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
// DefaultRedisKey is the default Redis key used by loaders.
|
||||||
DefaultRedisKey = "gost"
|
DefaultRedisKey = "gost"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -21,26 +22,31 @@ type redisLoaderOptions struct {
|
|||||||
key string
|
key string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RedisLoaderOption configures a Redis loader.
|
||||||
type RedisLoaderOption func(opts *redisLoaderOptions)
|
type RedisLoaderOption func(opts *redisLoaderOptions)
|
||||||
|
|
||||||
|
// DBRedisLoaderOption sets the Redis database number.
|
||||||
func DBRedisLoaderOption(db int) RedisLoaderOption {
|
func DBRedisLoaderOption(db int) RedisLoaderOption {
|
||||||
return func(opts *redisLoaderOptions) {
|
return func(opts *redisLoaderOptions) {
|
||||||
opts.db = db
|
opts.db = db
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UsernameRedisLoaderOption sets the Redis username.
|
||||||
func UsernameRedisLoaderOption(username string) RedisLoaderOption {
|
func UsernameRedisLoaderOption(username string) RedisLoaderOption {
|
||||||
return func(opts *redisLoaderOptions) {
|
return func(opts *redisLoaderOptions) {
|
||||||
opts.username = username
|
opts.username = username
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PasswordRedisLoaderOption sets the Redis password.
|
||||||
func PasswordRedisLoaderOption(password string) RedisLoaderOption {
|
func PasswordRedisLoaderOption(password string) RedisLoaderOption {
|
||||||
return func(opts *redisLoaderOptions) {
|
return func(opts *redisLoaderOptions) {
|
||||||
opts.password = password
|
opts.password = password
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KeyRedisLoaderOption sets the Redis key.
|
||||||
func KeyRedisLoaderOption(key string) RedisLoaderOption {
|
func KeyRedisLoaderOption(key string) RedisLoaderOption {
|
||||||
return func(opts *redisLoaderOptions) {
|
return func(opts *redisLoaderOptions) {
|
||||||
opts.key = key
|
opts.key = key
|
||||||
@@ -145,7 +151,9 @@ type redisListLoader struct {
|
|||||||
func RedisListLoader(addr string, opts ...RedisLoaderOption) Loader {
|
func RedisListLoader(addr string, opts ...RedisLoaderOption) Loader {
|
||||||
var options redisLoaderOptions
|
var options redisLoaderOptions
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(&options)
|
if opt != nil {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
key := options.key
|
key := options.key
|
||||||
@@ -190,7 +198,9 @@ type redisHashLoader struct {
|
|||||||
func RedisHashLoader(addr string, opts ...RedisLoaderOption) Loader {
|
func RedisHashLoader(addr string, opts ...RedisLoaderOption) Loader {
|
||||||
var options redisLoaderOptions
|
var options redisLoaderOptions
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(&options)
|
if opt != nil {
|
||||||
|
opt(&options)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
key := options.key
|
key := options.key
|
||||||
|
|||||||
Reference in New Issue
Block a user