feat: smart config

This commit is contained in:
2025-05-25 16:18:21 +02:00
parent 4df7561dd3
commit e8bad71f21
4 changed files with 120 additions and 0 deletions

7
internal/config/admin.go Normal file
View File

@ -0,0 +1,7 @@
package config
type AdminConfig struct {
Name string `env:"GUARD_ADMIN_NAME" default:"Admin"`
Email string `env:"GUARD_ADMIN_EMAIL" required:"true"`
Password string `env:"GUARD_ADMIN_PASSWORD" required:"true"`
}

8
internal/config/jwt.go Normal file
View File

@ -0,0 +1,8 @@
package config
type JwtConfig struct {
PrivateKey string `env:"GUARD_JWT_PRIVATE" required:"true"`
PublicKey string `env:"GUARD_JWT_PUBLIC" required:"true"`
KID string `env:"GUARD_JWT_KID" default:"guard-rsa"`
Issuer string `env:"GUARD_JWT_ISSUER" required:"true"`
}

7
internal/config/minio.go Normal file
View File

@ -0,0 +1,7 @@
package config
type MinioConfig struct {
Endpoint string `env:"GUARD_MINIO_ENDPOINT" default:"localhost:9000"`
AccessKey string `env:"GUARD_MINIO_ACCESS_KEY" required:"true"`
SecretKey string `env:"GUARD_MINIO_SECRET_KEY" required:"true"`
}

98
internal/config/mod.go Normal file
View File

@ -0,0 +1,98 @@
package config
import (
"errors"
"fmt"
"os"
"reflect"
"strconv"
"strings"
)
type AppConfig struct {
Port string `env:"GUARD_PORT" default:"3001"`
Host string `env:"GUARD_HOST" default:"127.0.0.1"`
DatabaseURL string `env:"GUARD_DB_URL" required:"true"`
Admin AdminConfig
Jwt JwtConfig
Minio MinioConfig
}
func LoadEnv(target any) error {
v := reflect.ValueOf(target)
if v.Kind() != reflect.Pointer || v.Elem().Kind() != reflect.Struct {
return &InvalidTargetError{}
}
return loadStruct(v.Elem(), "")
}
type InvalidTargetError struct{}
func (e *InvalidTargetError) Error() string {
return "target must be a pointer to a struct"
}
var ErrMissingRequiredEnv = errors.New("missing required environment variable")
func loadStruct(v reflect.Value, prefix string) error {
t := v.Type()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
valueField := v.Field(i)
if !valueField.CanSet() {
continue
}
envKey := field.Tag.Get("env")
if envKey == "" {
envKey = strings.ToUpper(prefix + "_" + field.Name)
envKey = strings.TrimPrefix(envKey, "_")
}
required := field.Tag.Get("required") == "true"
defaultVal := field.Tag.Get("default")
if field.Type.Kind() == reflect.Struct {
err := loadStruct(valueField, envKey)
if err != nil {
return err
}
continue
}
envVal := os.Getenv(envKey)
if envVal == "" {
if defaultVal != "" {
envVal = defaultVal
} else if required {
return fmt.Errorf("%w: %s", ErrMissingRequiredEnv, envKey)
} else {
continue
}
}
switch field.Type.Kind() {
case reflect.String:
valueField.SetString(envVal)
case reflect.Int, reflect.Int64:
i, err := strconv.ParseInt(envVal, 10, 64)
if err != nil {
return fmt.Errorf("invalid int for %s: %w", envKey, err)
}
valueField.SetInt(i)
case reflect.Bool:
b, err := strconv.ParseBool(envVal)
if err != nil {
return fmt.Errorf("invalid bool for %s: %w", envKey, err)
}
valueField.SetBool(b)
default:
return fmt.Errorf("unsupported type for field %s", field.Name)
}
}
return nil
}