Files
x/internal/loader/redis.go
T
ginuerzh 51455da96f 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
2026-05-24 18:04:02 +08:00

257 lines
5.1 KiB
Go

package loader
import (
"bytes"
"context"
"fmt"
"io"
"strings"
"github.com/go-redis/redis/v8"
)
const (
// DefaultRedisKey is the default Redis key used by loaders.
DefaultRedisKey = "gost"
)
type redisLoaderOptions struct {
db int
username string
password string
key string
}
// RedisLoaderOption configures a Redis loader.
type RedisLoaderOption func(opts *redisLoaderOptions)
// DBRedisLoaderOption sets the Redis database number.
func DBRedisLoaderOption(db int) RedisLoaderOption {
return func(opts *redisLoaderOptions) {
opts.db = db
}
}
// UsernameRedisLoaderOption sets the Redis username.
func UsernameRedisLoaderOption(username string) RedisLoaderOption {
return func(opts *redisLoaderOptions) {
opts.username = username
}
}
// PasswordRedisLoaderOption sets the Redis password.
func PasswordRedisLoaderOption(password string) RedisLoaderOption {
return func(opts *redisLoaderOptions) {
opts.password = password
}
}
// KeyRedisLoaderOption sets the Redis key.
func KeyRedisLoaderOption(key string) RedisLoaderOption {
return func(opts *redisLoaderOptions) {
opts.key = key
}
}
type redisStringLoader struct {
client *redis.Client
key string
}
// RedisStringLoader loads data from redis string.
func RedisStringLoader(addr string, opts ...RedisLoaderOption) Loader {
var options redisLoaderOptions
for _, opt := range opts {
if opt != nil {
opt(&options)
}
}
key := options.key
if key == "" {
key = DefaultRedisKey
}
return &redisStringLoader{
client: redis.NewClient(&redis.Options{
Addr: addr,
Username: options.username,
Password: options.password,
DB: options.db,
}),
key: key,
}
}
func (p *redisStringLoader) Load(ctx context.Context) (io.Reader, error) {
v, err := p.client.Get(ctx, p.key).Bytes()
if err != nil {
return nil, err
}
return bytes.NewReader(v), nil
}
func (p *redisStringLoader) Close() error {
return p.client.Close()
}
type redisSetLoader struct {
client *redis.Client
key string
}
// RedisSetLoader loads data from redis set.
func RedisSetLoader(addr string, opts ...RedisLoaderOption) Loader {
var options redisLoaderOptions
for _, opt := range opts {
if opt != nil {
opt(&options)
}
}
key := options.key
if key == "" {
key = DefaultRedisKey
}
return &redisSetLoader{
client: redis.NewClient(&redis.Options{
Addr: addr,
Username: options.username,
Password: options.password,
DB: options.db,
}),
key: key,
}
}
func (p *redisSetLoader) Load(ctx context.Context) (io.Reader, error) {
v, err := p.List(ctx)
if err != nil {
return nil, err
}
return bytes.NewReader([]byte(strings.Join(v, "\n"))), nil
}
// List implements Lister interface{}
func (p *redisSetLoader) List(ctx context.Context) ([]string, error) {
return p.client.SMembers(ctx, p.key).Result()
}
func (p *redisSetLoader) Close() error {
return p.client.Close()
}
type redisListLoader struct {
client *redis.Client
key string
}
// RedisListLoader loads data from redis list.
func RedisListLoader(addr string, opts ...RedisLoaderOption) Loader {
var options redisLoaderOptions
for _, opt := range opts {
if opt != nil {
opt(&options)
}
}
key := options.key
if key == "" {
key = DefaultRedisKey
}
return &redisListLoader{
client: redis.NewClient(&redis.Options{
Addr: addr,
Username: options.username,
Password: options.password,
DB: options.db,
}),
key: key,
}
}
func (p *redisListLoader) Load(ctx context.Context) (io.Reader, error) {
v, err := p.List(ctx)
if err != nil {
return nil, err
}
return bytes.NewReader([]byte(strings.Join(v, "\n"))), nil
}
// List implements Lister interface{}
func (p *redisListLoader) List(ctx context.Context) ([]string, error) {
return p.client.LRange(ctx, p.key, 0, -1).Result()
}
func (p *redisListLoader) Close() error {
return p.client.Close()
}
type redisHashLoader struct {
client *redis.Client
key string
}
// RedisHashLoader loads data from redis hash.
func RedisHashLoader(addr string, opts ...RedisLoaderOption) Loader {
var options redisLoaderOptions
for _, opt := range opts {
if opt != nil {
opt(&options)
}
}
key := options.key
if key == "" {
key = DefaultRedisKey
}
return &redisHashLoader{
client: redis.NewClient(&redis.Options{
Addr: addr,
Username: options.username,
Password: options.password,
DB: options.db,
}),
key: key,
}
}
func (p *redisHashLoader) Load(ctx context.Context) (io.Reader, error) {
m, err := p.Map(ctx)
if err != nil {
return nil, err
}
var b strings.Builder
for k, v := range m {
fmt.Fprintf(&b, "%s %s\n", k, v)
}
return bytes.NewBufferString(b.String()), nil
}
// List implements Lister interface{}
func (p *redisHashLoader) List(ctx context.Context) (list []string, err error) {
m, err := p.Map(ctx)
if err != nil {
return
}
for k, v := range m {
list = append(list, fmt.Sprintf("%s %s", k, v))
}
return
}
// Map implements Mapper interface{}
func (p *redisHashLoader) Map(ctx context.Context) (map[string]string, error) {
return p.client.HGetAll(ctx, p.key).Result()
}
func (p *redisHashLoader) Close() error {
return p.client.Close()
}