mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2025-12-13 17:10:36 +00:00
Config Package
Some checks are pending
Build , Vet Test, and Lint / Run Vet Tests (1.23.x) (push) Waiting to run
Build , Vet Test, and Lint / Run Vet Tests (1.24.x) (push) Waiting to run
Build , Vet Test, and Lint / Lint Code (push) Waiting to run
Build , Vet Test, and Lint / Build (push) Waiting to run
Tests / Unit Tests (push) Waiting to run
Tests / Integration Tests (push) Waiting to run
Some checks are pending
Build , Vet Test, and Lint / Run Vet Tests (1.23.x) (push) Waiting to run
Build , Vet Test, and Lint / Run Vet Tests (1.24.x) (push) Waiting to run
Build , Vet Test, and Lint / Lint Code (push) Waiting to run
Build , Vet Test, and Lint / Build (push) Waiting to run
Tests / Unit Tests (push) Waiting to run
Tests / Integration Tests (push) Waiting to run
This commit is contained in:
parent
659b2925e4
commit
1baa0af0ac
52
.env.example
Normal file
52
.env.example
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# ResolveSpec Environment Variables Example
|
||||||
|
# Environment variables override config file settings
|
||||||
|
# All variables are prefixed with RESOLVESPEC_
|
||||||
|
# Nested config uses underscores (e.g., server.addr -> RESOLVESPEC_SERVER_ADDR)
|
||||||
|
|
||||||
|
# Server Configuration
|
||||||
|
RESOLVESPEC_SERVER_ADDR=:8080
|
||||||
|
RESOLVESPEC_SERVER_SHUTDOWN_TIMEOUT=30s
|
||||||
|
RESOLVESPEC_SERVER_DRAIN_TIMEOUT=25s
|
||||||
|
RESOLVESPEC_SERVER_READ_TIMEOUT=10s
|
||||||
|
RESOLVESPEC_SERVER_WRITE_TIMEOUT=10s
|
||||||
|
RESOLVESPEC_SERVER_IDLE_TIMEOUT=120s
|
||||||
|
|
||||||
|
# Tracing Configuration
|
||||||
|
RESOLVESPEC_TRACING_ENABLED=false
|
||||||
|
RESOLVESPEC_TRACING_SERVICE_NAME=resolvespec
|
||||||
|
RESOLVESPEC_TRACING_SERVICE_VERSION=1.0.0
|
||||||
|
RESOLVESPEC_TRACING_ENDPOINT=http://localhost:4318/v1/traces
|
||||||
|
|
||||||
|
# Cache Configuration
|
||||||
|
RESOLVESPEC_CACHE_PROVIDER=memory
|
||||||
|
|
||||||
|
# Redis Cache (when provider=redis)
|
||||||
|
RESOLVESPEC_CACHE_REDIS_HOST=localhost
|
||||||
|
RESOLVESPEC_CACHE_REDIS_PORT=6379
|
||||||
|
RESOLVESPEC_CACHE_REDIS_PASSWORD=
|
||||||
|
RESOLVESPEC_CACHE_REDIS_DB=0
|
||||||
|
|
||||||
|
# Memcache (when provider=memcache)
|
||||||
|
# Note: For arrays, separate values with commas
|
||||||
|
RESOLVESPEC_CACHE_MEMCACHE_SERVERS=localhost:11211
|
||||||
|
RESOLVESPEC_CACHE_MEMCACHE_MAX_IDLE_CONNS=10
|
||||||
|
RESOLVESPEC_CACHE_MEMCACHE_TIMEOUT=100ms
|
||||||
|
|
||||||
|
# Logger Configuration
|
||||||
|
RESOLVESPEC_LOGGER_DEV=false
|
||||||
|
RESOLVESPEC_LOGGER_PATH=
|
||||||
|
|
||||||
|
# Middleware Configuration
|
||||||
|
RESOLVESPEC_MIDDLEWARE_RATE_LIMIT_RPS=100.0
|
||||||
|
RESOLVESPEC_MIDDLEWARE_RATE_LIMIT_BURST=200
|
||||||
|
RESOLVESPEC_MIDDLEWARE_MAX_REQUEST_SIZE=10485760
|
||||||
|
|
||||||
|
# CORS Configuration
|
||||||
|
# Note: For arrays in env vars, separate with commas
|
||||||
|
RESOLVESPEC_CORS_ALLOWED_ORIGINS=*
|
||||||
|
RESOLVESPEC_CORS_ALLOWED_METHODS=GET,POST,PUT,DELETE,OPTIONS
|
||||||
|
RESOLVESPEC_CORS_ALLOWED_HEADERS=*
|
||||||
|
RESOLVESPEC_CORS_MAX_AGE=3600
|
||||||
|
|
||||||
|
# Database Configuration
|
||||||
|
RESOLVESPEC_DATABASE_URL=host=localhost user=postgres password=postgres dbname=resolvespec_test port=5434 sslmode=disable
|
||||||
@ -6,8 +6,10 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/bitechdev/ResolveSpec/pkg/config"
|
||||||
"github.com/bitechdev/ResolveSpec/pkg/logger"
|
"github.com/bitechdev/ResolveSpec/pkg/logger"
|
||||||
"github.com/bitechdev/ResolveSpec/pkg/modelregistry"
|
"github.com/bitechdev/ResolveSpec/pkg/modelregistry"
|
||||||
|
"github.com/bitechdev/ResolveSpec/pkg/server"
|
||||||
"github.com/bitechdev/ResolveSpec/pkg/testmodels"
|
"github.com/bitechdev/ResolveSpec/pkg/testmodels"
|
||||||
|
|
||||||
"github.com/bitechdev/ResolveSpec/pkg/resolvespec"
|
"github.com/bitechdev/ResolveSpec/pkg/resolvespec"
|
||||||
@ -19,12 +21,27 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Initialize logger
|
// Load configuration
|
||||||
logger.Init(true)
|
cfgMgr := config.NewManager()
|
||||||
|
if err := cfgMgr.Load(); err != nil {
|
||||||
|
log.Fatalf("Failed to load configuration: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := cfgMgr.GetConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to get configuration: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize logger with configuration
|
||||||
|
logger.Init(cfg.Logger.Dev)
|
||||||
|
if cfg.Logger.Path != "" {
|
||||||
|
logger.UpdateLoggerPath(cfg.Logger.Path, cfg.Logger.Dev)
|
||||||
|
}
|
||||||
logger.Info("ResolveSpec test server starting")
|
logger.Info("ResolveSpec test server starting")
|
||||||
|
logger.Info("Configuration loaded - Server will listen on: %s", cfg.Server.Addr)
|
||||||
|
|
||||||
// Initialize database
|
// Initialize database
|
||||||
db, err := initDB()
|
db, err := initDB(cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("Failed to initialize database: %+v", err)
|
logger.Error("Failed to initialize database: %+v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@ -50,29 +67,51 @@ func main() {
|
|||||||
// Setup routes using new SetupMuxRoutes function (without authentication)
|
// Setup routes using new SetupMuxRoutes function (without authentication)
|
||||||
resolvespec.SetupMuxRoutes(r, handler, nil)
|
resolvespec.SetupMuxRoutes(r, handler, nil)
|
||||||
|
|
||||||
// Start server
|
// Create graceful server with configuration
|
||||||
logger.Info("Starting server on :8080")
|
srv := server.NewGracefulServer(server.Config{
|
||||||
if err := http.ListenAndServe(":8080", r); err != nil {
|
Addr: cfg.Server.Addr,
|
||||||
|
Handler: r,
|
||||||
|
ShutdownTimeout: cfg.Server.ShutdownTimeout,
|
||||||
|
DrainTimeout: cfg.Server.DrainTimeout,
|
||||||
|
ReadTimeout: cfg.Server.ReadTimeout,
|
||||||
|
WriteTimeout: cfg.Server.WriteTimeout,
|
||||||
|
IdleTimeout: cfg.Server.IdleTimeout,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Start server with graceful shutdown
|
||||||
|
logger.Info("Starting server on %s", cfg.Server.Addr)
|
||||||
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
||||||
logger.Error("Server failed to start: %v", err)
|
logger.Error("Server failed to start: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initDB() (*gorm.DB, error) {
|
func initDB(cfg *config.Config) (*gorm.DB, error) {
|
||||||
|
// Configure GORM logger based on config
|
||||||
|
logLevel := gormlog.Info
|
||||||
|
if !cfg.Logger.Dev {
|
||||||
|
logLevel = gormlog.Warn
|
||||||
|
}
|
||||||
|
|
||||||
newLogger := gormlog.New(
|
newLogger := gormlog.New(
|
||||||
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
|
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
|
||||||
gormlog.Config{
|
gormlog.Config{
|
||||||
SlowThreshold: time.Second, // Slow SQL threshold
|
SlowThreshold: time.Second, // Slow SQL threshold
|
||||||
LogLevel: gormlog.Info, // Log level
|
LogLevel: logLevel, // Log level
|
||||||
IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger
|
IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger
|
||||||
ParameterizedQueries: true, // Don't include params in the SQL log
|
ParameterizedQueries: true, // Don't include params in the SQL log
|
||||||
Colorful: true, // Disable color
|
Colorful: cfg.Logger.Dev,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Use database URL from config if available, otherwise use default SQLite
|
||||||
|
dbURL := cfg.Database.URL
|
||||||
|
if dbURL == "" {
|
||||||
|
dbURL = "test.db"
|
||||||
|
}
|
||||||
|
|
||||||
// Create SQLite database
|
// Create SQLite database
|
||||||
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{Logger: newLogger, FullSaveAssociations: false})
|
db, err := gorm.Open(sqlite.Open(dbURL), &gorm.Config{Logger: newLogger, FullSaveAssociations: false})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
41
config.yaml
Normal file
41
config.yaml
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# ResolveSpec Test Server Configuration
|
||||||
|
# This is a minimal configuration for the test server
|
||||||
|
|
||||||
|
server:
|
||||||
|
addr: ":8080"
|
||||||
|
shutdown_timeout: 30s
|
||||||
|
drain_timeout: 25s
|
||||||
|
read_timeout: 10s
|
||||||
|
write_timeout: 10s
|
||||||
|
idle_timeout: 120s
|
||||||
|
|
||||||
|
logger:
|
||||||
|
dev: true # Enable development mode for readable logs
|
||||||
|
path: "" # Empty means log to stdout
|
||||||
|
|
||||||
|
cache:
|
||||||
|
provider: "memory"
|
||||||
|
|
||||||
|
middleware:
|
||||||
|
rate_limit_rps: 100.0
|
||||||
|
rate_limit_burst: 200
|
||||||
|
max_request_size: 10485760 # 10MB
|
||||||
|
|
||||||
|
cors:
|
||||||
|
allowed_origins:
|
||||||
|
- "*"
|
||||||
|
allowed_methods:
|
||||||
|
- "GET"
|
||||||
|
- "POST"
|
||||||
|
- "PUT"
|
||||||
|
- "DELETE"
|
||||||
|
- "OPTIONS"
|
||||||
|
allowed_headers:
|
||||||
|
- "*"
|
||||||
|
max_age: 3600
|
||||||
|
|
||||||
|
tracing:
|
||||||
|
enabled: false
|
||||||
|
|
||||||
|
database:
|
||||||
|
url: "" # Empty means use default SQLite (test.db)
|
||||||
57
config.yaml.example
Normal file
57
config.yaml.example
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# ResolveSpec Configuration Example
|
||||||
|
# This file demonstrates all available configuration options
|
||||||
|
# Copy this file to config.yaml and customize as needed
|
||||||
|
|
||||||
|
server:
|
||||||
|
addr: ":8080"
|
||||||
|
shutdown_timeout: 30s
|
||||||
|
drain_timeout: 25s
|
||||||
|
read_timeout: 10s
|
||||||
|
write_timeout: 10s
|
||||||
|
idle_timeout: 120s
|
||||||
|
|
||||||
|
tracing:
|
||||||
|
enabled: false
|
||||||
|
service_name: "resolvespec"
|
||||||
|
service_version: "1.0.0"
|
||||||
|
endpoint: "http://localhost:4318/v1/traces" # OTLP endpoint
|
||||||
|
|
||||||
|
cache:
|
||||||
|
provider: "memory" # Options: memory, redis, memcache
|
||||||
|
|
||||||
|
redis:
|
||||||
|
host: "localhost"
|
||||||
|
port: 6379
|
||||||
|
password: ""
|
||||||
|
db: 0
|
||||||
|
|
||||||
|
memcache:
|
||||||
|
servers:
|
||||||
|
- "localhost:11211"
|
||||||
|
max_idle_conns: 10
|
||||||
|
timeout: 100ms
|
||||||
|
|
||||||
|
logger:
|
||||||
|
dev: false
|
||||||
|
path: "" # Empty for stdout, or specify file path
|
||||||
|
|
||||||
|
middleware:
|
||||||
|
rate_limit_rps: 100.0
|
||||||
|
rate_limit_burst: 200
|
||||||
|
max_request_size: 10485760 # 10MB in bytes
|
||||||
|
|
||||||
|
cors:
|
||||||
|
allowed_origins:
|
||||||
|
- "*"
|
||||||
|
allowed_methods:
|
||||||
|
- "GET"
|
||||||
|
- "POST"
|
||||||
|
- "PUT"
|
||||||
|
- "DELETE"
|
||||||
|
- "OPTIONS"
|
||||||
|
allowed_headers:
|
||||||
|
- "*"
|
||||||
|
max_age: 3600
|
||||||
|
|
||||||
|
database:
|
||||||
|
url: "host=localhost user=postgres password=postgres dbname=resolvespec_test port=5434 sslmode=disable"
|
||||||
11
go.mod
11
go.mod
@ -36,9 +36,11 @@ require (
|
|||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||||
github.com/go-logr/logr v1.4.3 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
@ -50,12 +52,20 @@ require (
|
|||||||
github.com/mattn/go-sqlite3 v1.14.28 // indirect
|
github.com/mattn/go-sqlite3 v1.14.28 // indirect
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_model v0.6.2 // indirect
|
github.com/prometheus/client_model v0.6.2 // indirect
|
||||||
github.com/prometheus/common v0.66.1 // indirect
|
github.com/prometheus/common v0.66.1 // indirect
|
||||||
github.com/prometheus/procfs v0.16.1 // indirect
|
github.com/prometheus/procfs v0.16.1 // indirect
|
||||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
|
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||||
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
|
github.com/spf13/viper v1.21.0 // indirect
|
||||||
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/tidwall/match v1.1.1 // indirect
|
github.com/tidwall/match v1.1.1 // indirect
|
||||||
github.com/tidwall/pretty v1.2.0 // indirect
|
github.com/tidwall/pretty v1.2.0 // indirect
|
||||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||||
@ -66,6 +76,7 @@ require (
|
|||||||
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
|
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
|
||||||
go.uber.org/multierr v1.10.0 // indirect
|
go.uber.org/multierr v1.10.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
golang.org/x/crypto v0.41.0 // indirect
|
golang.org/x/crypto v0.41.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect
|
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc // indirect
|
||||||
golang.org/x/net v0.43.0 // indirect
|
golang.org/x/net v0.43.0 // indirect
|
||||||
|
|||||||
22
go.sum
22
go.sum
@ -17,6 +17,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
|
|||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||||
@ -26,6 +28,8 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
|||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
@ -66,6 +70,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq
|
|||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
||||||
@ -84,11 +90,25 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94
|
|||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||||
|
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
|
||||||
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
|
||||||
|
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
|
||||||
|
github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg=
|
||||||
|
github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
|
||||||
|
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||||
|
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||||
|
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU=
|
||||||
|
github.com/spf13/viper v1.21.0/go.mod h1:P0lhsswPGWD/1lZJ9ny3fYnVqxiegrlNrEmgLjbTCAY=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||||
@ -138,6 +158,8 @@ go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
|||||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||||
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
|
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
||||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
||||||
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc=
|
golang.org/x/exp v0.0.0-20250711185948-6ae5c78190dc h1:TS73t7x3KarrNd5qAipmspBDS1rkMcgVG/fS1aRb4Rc=
|
||||||
|
|||||||
291
pkg/config/README.md
Normal file
291
pkg/config/README.md
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
# ResolveSpec Configuration System
|
||||||
|
|
||||||
|
A centralized configuration system with support for multiple configuration sources: config files (YAML, TOML, JSON), environment variables, and programmatic configuration.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Multiple Config Sources**: Config files, environment variables, and code
|
||||||
|
- **Priority Order**: Environment variables > Config file > Defaults
|
||||||
|
- **Multiple Formats**: YAML, TOML, JSON supported
|
||||||
|
- **Type Safety**: Strongly-typed configuration structs
|
||||||
|
- **Sensible Defaults**: Works out of the box with reasonable defaults
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/heinhel/ResolveSpec/pkg/config"
|
||||||
|
|
||||||
|
// Create a new config manager
|
||||||
|
mgr := config.NewManager()
|
||||||
|
|
||||||
|
// Load configuration from file and environment
|
||||||
|
if err := mgr.Load(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the complete configuration
|
||||||
|
cfg, err := mgr.GetConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the configuration
|
||||||
|
fmt.Println("Server address:", cfg.Server.Addr)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Configuration Paths
|
||||||
|
|
||||||
|
```go
|
||||||
|
mgr := config.NewManagerWithOptions(
|
||||||
|
config.WithConfigFile("/path/to/config.yaml"),
|
||||||
|
config.WithEnvPrefix("MYAPP"),
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Sources
|
||||||
|
|
||||||
|
### 1. Config Files
|
||||||
|
|
||||||
|
Place a `config.yaml` file in one of these locations:
|
||||||
|
- Current directory (`.`)
|
||||||
|
- `./config/`
|
||||||
|
- `/etc/resolvespec/`
|
||||||
|
- `$HOME/.resolvespec/`
|
||||||
|
|
||||||
|
Example `config.yaml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
server:
|
||||||
|
addr: ":8080"
|
||||||
|
shutdown_timeout: 30s
|
||||||
|
|
||||||
|
tracing:
|
||||||
|
enabled: true
|
||||||
|
service_name: "my-service"
|
||||||
|
|
||||||
|
cache:
|
||||||
|
provider: "redis"
|
||||||
|
redis:
|
||||||
|
host: "localhost"
|
||||||
|
port: 6379
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Environment Variables
|
||||||
|
|
||||||
|
All configuration can be set via environment variables with the `RESOLVESPEC_` prefix:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export RESOLVESPEC_SERVER_ADDR=":9090"
|
||||||
|
export RESOLVESPEC_TRACING_ENABLED=true
|
||||||
|
export RESOLVESPEC_CACHE_PROVIDER=redis
|
||||||
|
export RESOLVESPEC_CACHE_REDIS_HOST=localhost
|
||||||
|
```
|
||||||
|
|
||||||
|
Nested configuration uses underscores:
|
||||||
|
- `server.addr` → `RESOLVESPEC_SERVER_ADDR`
|
||||||
|
- `cache.redis.host` → `RESOLVESPEC_CACHE_REDIS_HOST`
|
||||||
|
|
||||||
|
### 3. Programmatic Configuration
|
||||||
|
|
||||||
|
```go
|
||||||
|
mgr := config.NewManager()
|
||||||
|
mgr.Set("server.addr", ":9090")
|
||||||
|
mgr.Set("tracing.enabled", true)
|
||||||
|
|
||||||
|
cfg, _ := mgr.GetConfig()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration Options
|
||||||
|
|
||||||
|
### Server Configuration
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
server:
|
||||||
|
addr: ":8080" # Server address
|
||||||
|
shutdown_timeout: 30s # Graceful shutdown timeout
|
||||||
|
drain_timeout: 25s # Connection drain timeout
|
||||||
|
read_timeout: 10s # HTTP read timeout
|
||||||
|
write_timeout: 10s # HTTP write timeout
|
||||||
|
idle_timeout: 120s # HTTP idle timeout
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tracing Configuration
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
tracing:
|
||||||
|
enabled: false # Enable/disable tracing
|
||||||
|
service_name: "resolvespec" # Service name
|
||||||
|
service_version: "1.0.0" # Service version
|
||||||
|
endpoint: "http://localhost:4318/v1/traces" # OTLP endpoint
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cache Configuration
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
cache:
|
||||||
|
provider: "memory" # Options: memory, redis, memcache
|
||||||
|
|
||||||
|
redis:
|
||||||
|
host: "localhost"
|
||||||
|
port: 6379
|
||||||
|
password: ""
|
||||||
|
db: 0
|
||||||
|
|
||||||
|
memcache:
|
||||||
|
servers:
|
||||||
|
- "localhost:11211"
|
||||||
|
max_idle_conns: 10
|
||||||
|
timeout: 100ms
|
||||||
|
```
|
||||||
|
|
||||||
|
### Logger Configuration
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
logger:
|
||||||
|
dev: false # Development mode (human-readable output)
|
||||||
|
path: "" # Log file path (empty = stdout)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Middleware Configuration
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
middleware:
|
||||||
|
rate_limit_rps: 100.0 # Requests per second
|
||||||
|
rate_limit_burst: 200 # Burst size
|
||||||
|
max_request_size: 10485760 # Max request size in bytes (10MB)
|
||||||
|
```
|
||||||
|
|
||||||
|
### CORS Configuration
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
cors:
|
||||||
|
allowed_origins:
|
||||||
|
- "*"
|
||||||
|
allowed_methods:
|
||||||
|
- "GET"
|
||||||
|
- "POST"
|
||||||
|
- "PUT"
|
||||||
|
- "DELETE"
|
||||||
|
- "OPTIONS"
|
||||||
|
allowed_headers:
|
||||||
|
- "*"
|
||||||
|
max_age: 3600
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Configuration
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
database:
|
||||||
|
url: "host=localhost user=postgres password=postgres dbname=mydb port=5432 sslmode=disable"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Priority and Overrides
|
||||||
|
|
||||||
|
Configuration sources are applied in this order (highest priority first):
|
||||||
|
|
||||||
|
1. **Environment Variables** (highest priority)
|
||||||
|
2. **Config File**
|
||||||
|
3. **Defaults** (lowest priority)
|
||||||
|
|
||||||
|
This allows you to:
|
||||||
|
- Set defaults in code
|
||||||
|
- Override with a config file
|
||||||
|
- Override specific values with environment variables
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
### Production Setup
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# config.yaml
|
||||||
|
server:
|
||||||
|
addr: ":8080"
|
||||||
|
|
||||||
|
tracing:
|
||||||
|
enabled: true
|
||||||
|
service_name: "myapi"
|
||||||
|
endpoint: "http://jaeger:4318/v1/traces"
|
||||||
|
|
||||||
|
cache:
|
||||||
|
provider: "redis"
|
||||||
|
redis:
|
||||||
|
host: "redis"
|
||||||
|
port: 6379
|
||||||
|
password: "${REDIS_PASSWORD}"
|
||||||
|
|
||||||
|
logger:
|
||||||
|
dev: false
|
||||||
|
path: "/var/log/myapi/app.log"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Development Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Use environment variables for development
|
||||||
|
export RESOLVESPEC_LOGGER_DEV=true
|
||||||
|
export RESOLVESPEC_TRACING_ENABLED=false
|
||||||
|
export RESOLVESPEC_CACHE_PROVIDER=memory
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing Setup
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Override config for tests
|
||||||
|
mgr := config.NewManager()
|
||||||
|
mgr.Set("cache.provider", "memory")
|
||||||
|
mgr.Set("database.url", testDBURL)
|
||||||
|
|
||||||
|
cfg, _ := mgr.GetConfig()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Use config files for base configuration** - Define your standard settings
|
||||||
|
2. **Use environment variables for secrets** - Never commit passwords/tokens
|
||||||
|
3. **Use environment variables for deployment-specific values** - Different per environment
|
||||||
|
4. **Keep defaults sensible** - Application should work with minimal configuration
|
||||||
|
5. **Document your configuration** - Comment your config.yaml files
|
||||||
|
|
||||||
|
## Integration with ResolveSpec Components
|
||||||
|
|
||||||
|
The configuration system integrates seamlessly with ResolveSpec components:
|
||||||
|
|
||||||
|
```go
|
||||||
|
cfg, _ := config.NewManager().Load().GetConfig()
|
||||||
|
|
||||||
|
// Server
|
||||||
|
srv := server.NewGracefulServer(server.Config{
|
||||||
|
Addr: cfg.Server.Addr,
|
||||||
|
ShutdownTimeout: cfg.Server.ShutdownTimeout,
|
||||||
|
// ... other fields
|
||||||
|
})
|
||||||
|
|
||||||
|
// Tracing
|
||||||
|
if cfg.Tracing.Enabled {
|
||||||
|
tracer := tracing.Init(tracing.Config{
|
||||||
|
ServiceName: cfg.Tracing.ServiceName,
|
||||||
|
ServiceVersion: cfg.Tracing.ServiceVersion,
|
||||||
|
Endpoint: cfg.Tracing.Endpoint,
|
||||||
|
})
|
||||||
|
defer tracer.Shutdown(context.Background())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache
|
||||||
|
var cacheProvider cache.Provider
|
||||||
|
switch cfg.Cache.Provider {
|
||||||
|
case "redis":
|
||||||
|
cacheProvider = cache.NewRedisProvider(cfg.Cache.Redis.Host, cfg.Cache.Redis.Port, ...)
|
||||||
|
case "memcache":
|
||||||
|
cacheProvider = cache.NewMemcacheProvider(cfg.Cache.Memcache.Servers, ...)
|
||||||
|
default:
|
||||||
|
cacheProvider = cache.NewMemoryProvider()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger
|
||||||
|
logger.Init(cfg.Logger.Dev)
|
||||||
|
if cfg.Logger.Path != "" {
|
||||||
|
logger.UpdateLoggerPath(cfg.Logger.Path, cfg.Logger.Dev)
|
||||||
|
}
|
||||||
|
```
|
||||||
80
pkg/config/config.go
Normal file
80
pkg/config/config.go
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// Config represents the complete application configuration
|
||||||
|
type Config struct {
|
||||||
|
Server ServerConfig `mapstructure:"server"`
|
||||||
|
Tracing TracingConfig `mapstructure:"tracing"`
|
||||||
|
Cache CacheConfig `mapstructure:"cache"`
|
||||||
|
Logger LoggerConfig `mapstructure:"logger"`
|
||||||
|
Middleware MiddlewareConfig `mapstructure:"middleware"`
|
||||||
|
CORS CORSConfig `mapstructure:"cors"`
|
||||||
|
Database DatabaseConfig `mapstructure:"database"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerConfig holds server-related configuration
|
||||||
|
type ServerConfig struct {
|
||||||
|
Addr string `mapstructure:"addr"`
|
||||||
|
ShutdownTimeout time.Duration `mapstructure:"shutdown_timeout"`
|
||||||
|
DrainTimeout time.Duration `mapstructure:"drain_timeout"`
|
||||||
|
ReadTimeout time.Duration `mapstructure:"read_timeout"`
|
||||||
|
WriteTimeout time.Duration `mapstructure:"write_timeout"`
|
||||||
|
IdleTimeout time.Duration `mapstructure:"idle_timeout"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TracingConfig holds OpenTelemetry tracing configuration
|
||||||
|
type TracingConfig struct {
|
||||||
|
Enabled bool `mapstructure:"enabled"`
|
||||||
|
ServiceName string `mapstructure:"service_name"`
|
||||||
|
ServiceVersion string `mapstructure:"service_version"`
|
||||||
|
Endpoint string `mapstructure:"endpoint"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CacheConfig holds cache provider configuration
|
||||||
|
type CacheConfig struct {
|
||||||
|
Provider string `mapstructure:"provider"` // memory, redis, memcache
|
||||||
|
Redis RedisConfig `mapstructure:"redis"`
|
||||||
|
Memcache MemcacheConfig `mapstructure:"memcache"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// RedisConfig holds Redis-specific configuration
|
||||||
|
type RedisConfig struct {
|
||||||
|
Host string `mapstructure:"host"`
|
||||||
|
Port int `mapstructure:"port"`
|
||||||
|
Password string `mapstructure:"password"`
|
||||||
|
DB int `mapstructure:"db"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MemcacheConfig holds Memcache-specific configuration
|
||||||
|
type MemcacheConfig struct {
|
||||||
|
Servers []string `mapstructure:"servers"`
|
||||||
|
MaxIdleConns int `mapstructure:"max_idle_conns"`
|
||||||
|
Timeout time.Duration `mapstructure:"timeout"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoggerConfig holds logger configuration
|
||||||
|
type LoggerConfig struct {
|
||||||
|
Dev bool `mapstructure:"dev"`
|
||||||
|
Path string `mapstructure:"path"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MiddlewareConfig holds middleware configuration
|
||||||
|
type MiddlewareConfig struct {
|
||||||
|
RateLimitRPS float64 `mapstructure:"rate_limit_rps"`
|
||||||
|
RateLimitBurst int `mapstructure:"rate_limit_burst"`
|
||||||
|
MaxRequestSize int64 `mapstructure:"max_request_size"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORSConfig holds CORS configuration
|
||||||
|
type CORSConfig struct {
|
||||||
|
AllowedOrigins []string `mapstructure:"allowed_origins"`
|
||||||
|
AllowedMethods []string `mapstructure:"allowed_methods"`
|
||||||
|
AllowedHeaders []string `mapstructure:"allowed_headers"`
|
||||||
|
MaxAge int `mapstructure:"max_age"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DatabaseConfig holds database configuration (primarily for testing)
|
||||||
|
type DatabaseConfig struct {
|
||||||
|
URL string `mapstructure:"url"`
|
||||||
|
}
|
||||||
168
pkg/config/manager.go
Normal file
168
pkg/config/manager.go
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Manager handles configuration loading from multiple sources
|
||||||
|
type Manager struct {
|
||||||
|
v *viper.Viper
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewManager creates a new configuration manager with defaults
|
||||||
|
func NewManager() *Manager {
|
||||||
|
v := viper.New()
|
||||||
|
|
||||||
|
// Set configuration file settings
|
||||||
|
v.SetConfigName("config")
|
||||||
|
v.SetConfigType("yaml")
|
||||||
|
v.AddConfigPath(".")
|
||||||
|
v.AddConfigPath("./config")
|
||||||
|
v.AddConfigPath("/etc/resolvespec")
|
||||||
|
v.AddConfigPath("$HOME/.resolvespec")
|
||||||
|
|
||||||
|
// Enable environment variable support
|
||||||
|
v.SetEnvPrefix("RESOLVESPEC")
|
||||||
|
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||||
|
v.AutomaticEnv()
|
||||||
|
|
||||||
|
// Set default values
|
||||||
|
setDefaults(v)
|
||||||
|
|
||||||
|
return &Manager{v: v}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewManagerWithOptions creates a new configuration manager with custom options
|
||||||
|
func NewManagerWithOptions(opts ...Option) *Manager {
|
||||||
|
m := NewManager()
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(m)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// Option is a functional option for configuring the Manager
|
||||||
|
type Option func(*Manager)
|
||||||
|
|
||||||
|
// WithConfigFile sets a specific config file path
|
||||||
|
func WithConfigFile(path string) Option {
|
||||||
|
return func(m *Manager) {
|
||||||
|
m.v.SetConfigFile(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConfigName sets the config file name (without extension)
|
||||||
|
func WithConfigName(name string) Option {
|
||||||
|
return func(m *Manager) {
|
||||||
|
m.v.SetConfigName(name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithConfigPath adds a path to search for config files
|
||||||
|
func WithConfigPath(path string) Option {
|
||||||
|
return func(m *Manager) {
|
||||||
|
m.v.AddConfigPath(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithEnvPrefix sets the environment variable prefix
|
||||||
|
func WithEnvPrefix(prefix string) Option {
|
||||||
|
return func(m *Manager) {
|
||||||
|
m.v.SetEnvPrefix(prefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load attempts to load configuration from file and environment
|
||||||
|
func (m *Manager) Load() error {
|
||||||
|
// Try to read config file (not an error if it doesn't exist)
|
||||||
|
if err := m.v.ReadInConfig(); err != nil {
|
||||||
|
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
|
||||||
|
return fmt.Errorf("error reading config file: %w", err)
|
||||||
|
}
|
||||||
|
// Config file not found; will rely on defaults and env vars
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfig returns the complete configuration
|
||||||
|
func (m *Manager) GetConfig() (*Config, error) {
|
||||||
|
var cfg Config
|
||||||
|
if err := m.v.Unmarshal(&cfg); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal config: %w", err)
|
||||||
|
}
|
||||||
|
return &cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a configuration value by key
|
||||||
|
func (m *Manager) Get(key string) interface{} {
|
||||||
|
return m.v.Get(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetString returns a string configuration value
|
||||||
|
func (m *Manager) GetString(key string) string {
|
||||||
|
return m.v.GetString(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetInt returns an int configuration value
|
||||||
|
func (m *Manager) GetInt(key string) int {
|
||||||
|
return m.v.GetInt(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBool returns a bool configuration value
|
||||||
|
func (m *Manager) GetBool(key string) bool {
|
||||||
|
return m.v.GetBool(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets a configuration value
|
||||||
|
func (m *Manager) Set(key string, value interface{}) {
|
||||||
|
m.v.Set(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setDefaults sets default configuration values
|
||||||
|
func setDefaults(v *viper.Viper) {
|
||||||
|
// Server defaults
|
||||||
|
v.SetDefault("server.addr", ":8080")
|
||||||
|
v.SetDefault("server.shutdown_timeout", "30s")
|
||||||
|
v.SetDefault("server.drain_timeout", "25s")
|
||||||
|
v.SetDefault("server.read_timeout", "10s")
|
||||||
|
v.SetDefault("server.write_timeout", "10s")
|
||||||
|
v.SetDefault("server.idle_timeout", "120s")
|
||||||
|
|
||||||
|
// Tracing defaults
|
||||||
|
v.SetDefault("tracing.enabled", false)
|
||||||
|
v.SetDefault("tracing.service_name", "resolvespec")
|
||||||
|
v.SetDefault("tracing.service_version", "1.0.0")
|
||||||
|
v.SetDefault("tracing.endpoint", "")
|
||||||
|
|
||||||
|
// Cache defaults
|
||||||
|
v.SetDefault("cache.provider", "memory")
|
||||||
|
v.SetDefault("cache.redis.host", "localhost")
|
||||||
|
v.SetDefault("cache.redis.port", 6379)
|
||||||
|
v.SetDefault("cache.redis.password", "")
|
||||||
|
v.SetDefault("cache.redis.db", 0)
|
||||||
|
v.SetDefault("cache.memcache.servers", []string{"localhost:11211"})
|
||||||
|
v.SetDefault("cache.memcache.max_idle_conns", 10)
|
||||||
|
v.SetDefault("cache.memcache.timeout", "100ms")
|
||||||
|
|
||||||
|
// Logger defaults
|
||||||
|
v.SetDefault("logger.dev", false)
|
||||||
|
v.SetDefault("logger.path", "")
|
||||||
|
|
||||||
|
// Middleware defaults
|
||||||
|
v.SetDefault("middleware.rate_limit_rps", 100.0)
|
||||||
|
v.SetDefault("middleware.rate_limit_burst", 200)
|
||||||
|
v.SetDefault("middleware.max_request_size", 10485760) // 10MB
|
||||||
|
|
||||||
|
// CORS defaults
|
||||||
|
v.SetDefault("cors.allowed_origins", []string{"*"})
|
||||||
|
v.SetDefault("cors.allowed_methods", []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"})
|
||||||
|
v.SetDefault("cors.allowed_headers", []string{"*"})
|
||||||
|
v.SetDefault("cors.max_age", 3600)
|
||||||
|
|
||||||
|
// Database defaults
|
||||||
|
v.SetDefault("database.url", "")
|
||||||
|
}
|
||||||
166
pkg/config/manager_test.go
Normal file
166
pkg/config/manager_test.go
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewManager(t *testing.T) {
|
||||||
|
mgr := NewManager()
|
||||||
|
if mgr == nil {
|
||||||
|
t.Fatal("Expected manager to be non-nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if mgr.v == nil {
|
||||||
|
t.Fatal("Expected viper instance to be non-nil")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultValues(t *testing.T) {
|
||||||
|
mgr := NewManager()
|
||||||
|
if err := mgr.Load(); err != nil {
|
||||||
|
t.Fatalf("Failed to load config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := mgr.GetConfig()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test default values
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
got interface{}
|
||||||
|
expected interface{}
|
||||||
|
}{
|
||||||
|
{"server.addr", cfg.Server.Addr, ":8080"},
|
||||||
|
{"server.shutdown_timeout", cfg.Server.ShutdownTimeout, 30 * time.Second},
|
||||||
|
{"tracing.enabled", cfg.Tracing.Enabled, false},
|
||||||
|
{"tracing.service_name", cfg.Tracing.ServiceName, "resolvespec"},
|
||||||
|
{"cache.provider", cfg.Cache.Provider, "memory"},
|
||||||
|
{"cache.redis.host", cfg.Cache.Redis.Host, "localhost"},
|
||||||
|
{"cache.redis.port", cfg.Cache.Redis.Port, 6379},
|
||||||
|
{"logger.dev", cfg.Logger.Dev, false},
|
||||||
|
{"middleware.rate_limit_rps", cfg.Middleware.RateLimitRPS, 100.0},
|
||||||
|
{"middleware.rate_limit_burst", cfg.Middleware.RateLimitBurst, 200},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.got != tt.expected {
|
||||||
|
t.Errorf("%s: got %v, want %v", tt.name, tt.got, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnvironmentVariableOverrides(t *testing.T) {
|
||||||
|
// Set environment variables
|
||||||
|
os.Setenv("RESOLVESPEC_SERVER_ADDR", ":9090")
|
||||||
|
os.Setenv("RESOLVESPEC_TRACING_ENABLED", "true")
|
||||||
|
os.Setenv("RESOLVESPEC_CACHE_PROVIDER", "redis")
|
||||||
|
os.Setenv("RESOLVESPEC_LOGGER_DEV", "true")
|
||||||
|
defer func() {
|
||||||
|
os.Unsetenv("RESOLVESPEC_SERVER_ADDR")
|
||||||
|
os.Unsetenv("RESOLVESPEC_TRACING_ENABLED")
|
||||||
|
os.Unsetenv("RESOLVESPEC_CACHE_PROVIDER")
|
||||||
|
os.Unsetenv("RESOLVESPEC_LOGGER_DEV")
|
||||||
|
}()
|
||||||
|
|
||||||
|
mgr := NewManager()
|
||||||
|
if err := mgr.Load(); err != nil {
|
||||||
|
t.Fatalf("Failed to load config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := mgr.GetConfig()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test environment variable overrides
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
got interface{}
|
||||||
|
expected interface{}
|
||||||
|
}{
|
||||||
|
{"server.addr", cfg.Server.Addr, ":9090"},
|
||||||
|
{"tracing.enabled", cfg.Tracing.Enabled, true},
|
||||||
|
{"cache.provider", cfg.Cache.Provider, "redis"},
|
||||||
|
{"logger.dev", cfg.Logger.Dev, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if tt.got != tt.expected {
|
||||||
|
t.Errorf("%s: got %v, want %v", tt.name, tt.got, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProgrammaticConfiguration(t *testing.T) {
|
||||||
|
mgr := NewManager()
|
||||||
|
mgr.Set("server.addr", ":7070")
|
||||||
|
mgr.Set("tracing.service_name", "test-service")
|
||||||
|
|
||||||
|
cfg, err := mgr.GetConfig()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Server.Addr != ":7070" {
|
||||||
|
t.Errorf("server.addr: got %s, want :7070", cfg.Server.Addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Tracing.ServiceName != "test-service" {
|
||||||
|
t.Errorf("tracing.service_name: got %s, want test-service", cfg.Tracing.ServiceName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetterMethods(t *testing.T) {
|
||||||
|
mgr := NewManager()
|
||||||
|
mgr.Set("test.string", "value")
|
||||||
|
mgr.Set("test.int", 42)
|
||||||
|
mgr.Set("test.bool", true)
|
||||||
|
|
||||||
|
if got := mgr.GetString("test.string"); got != "value" {
|
||||||
|
t.Errorf("GetString: got %s, want value", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := mgr.GetInt("test.int"); got != 42 {
|
||||||
|
t.Errorf("GetInt: got %d, want 42", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := mgr.GetBool("test.bool"); !got {
|
||||||
|
t.Errorf("GetBool: got %v, want true", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithOptions(t *testing.T) {
|
||||||
|
mgr := NewManagerWithOptions(
|
||||||
|
WithEnvPrefix("MYAPP"),
|
||||||
|
WithConfigName("myconfig"),
|
||||||
|
)
|
||||||
|
|
||||||
|
if mgr == nil {
|
||||||
|
t.Fatal("Expected manager to be non-nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set environment variable with custom prefix
|
||||||
|
os.Setenv("MYAPP_SERVER_ADDR", ":5000")
|
||||||
|
defer os.Unsetenv("MYAPP_SERVER_ADDR")
|
||||||
|
|
||||||
|
if err := mgr.Load(); err != nil {
|
||||||
|
t.Fatalf("Failed to load config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := mgr.GetConfig()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Server.Addr != ":5000" {
|
||||||
|
t.Errorf("server.addr: got %s, want :5000", cfg.Server.Addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -321,9 +321,9 @@ func (h *Handler) handleRead(ctx context.Context, w common.ResponseWriter, id st
|
|||||||
tableName,
|
tableName,
|
||||||
options.Filters,
|
options.Filters,
|
||||||
options.Sort,
|
options.Sort,
|
||||||
"", // No custom SQL WHERE in resolvespec
|
"", // No custom SQL WHERE in resolvespec
|
||||||
"", // No custom SQL OR in resolvespec
|
"", // No custom SQL OR in resolvespec
|
||||||
nil, // No expand options in resolvespec
|
nil, // No expand options in resolvespec
|
||||||
false, // distinct not used here
|
false, // distinct not used here
|
||||||
options.CursorForward,
|
options.CursorForward,
|
||||||
options.CursorBackward,
|
options.CursorBackward,
|
||||||
|
|||||||
148
todo.md
148
todo.md
@ -2,143 +2,11 @@
|
|||||||
|
|
||||||
This document tracks incomplete features and improvements for the ResolveSpec project.
|
This document tracks incomplete features and improvements for the ResolveSpec project.
|
||||||
|
|
||||||
## Core Features to Implement
|
|
||||||
|
|
||||||
### 1. Column Selection and Filtering for Preloads
|
|
||||||
**Location:** `pkg/resolvespec/handler.go:730`
|
|
||||||
**Status:** Not Implemented
|
|
||||||
**Description:** Currently, preloads are applied without any column selection or filtering. This feature would allow clients to:
|
|
||||||
- Select specific columns for preloaded relationships
|
|
||||||
- Apply filters to preloaded data
|
|
||||||
- Reduce payload size and improve performance
|
|
||||||
|
|
||||||
**Current Limitation:**
|
|
||||||
```go
|
|
||||||
// For now, we'll preload without conditions
|
|
||||||
// TODO: Implement column selection and filtering for preloads
|
|
||||||
// This requires a more sophisticated approach with callbacks or query builders
|
|
||||||
query = query.Preload(relationFieldName)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Required Implementation:**
|
|
||||||
- Add support for column selection in preloaded relationships
|
|
||||||
- Implement filtering conditions for preloaded data
|
|
||||||
- Design a callback or query builder approach that works across different ORMs
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. Recursive JSON Cleaning
|
|
||||||
**Location:** `pkg/restheadspec/handler.go:796`
|
|
||||||
**Status:** Partially Implemented (Simplified)
|
|
||||||
**Description:** The current `cleanJSON` function returns data as-is without recursively removing null and empty fields from nested structures.
|
|
||||||
|
|
||||||
**Current Limitation:**
|
|
||||||
```go
|
|
||||||
// This is a simplified implementation
|
|
||||||
// A full implementation would recursively clean nested structures
|
|
||||||
// For now, we'll return the data as-is
|
|
||||||
// TODO: Implement recursive cleaning
|
|
||||||
return data
|
|
||||||
```
|
|
||||||
|
|
||||||
**Required Implementation:**
|
|
||||||
- Recursively traverse nested structures (maps, slices, structs)
|
|
||||||
- Remove null values
|
|
||||||
- Remove empty objects and arrays
|
|
||||||
- Handle edge cases (circular references, pointers, etc.)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. Custom SQL Join Support
|
|
||||||
**Location:** `pkg/restheadspec/headers.go:159`
|
|
||||||
**Status:** Not Implemented
|
|
||||||
**Description:** Support for custom SQL joins via the `X-Custom-SQL-Join` header is currently logged but not executed.
|
|
||||||
|
|
||||||
**Current Limitation:**
|
|
||||||
```go
|
|
||||||
case strings.HasPrefix(normalizedKey, "x-custom-sql-join"):
|
|
||||||
// TODO: Implement custom SQL join
|
|
||||||
logger.Debug("Custom SQL join not yet implemented: %s", decodedValue)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Required Implementation:**
|
|
||||||
- Parse custom SQL join expressions from headers
|
|
||||||
- Apply joins to the query builder
|
|
||||||
- Ensure security (SQL injection prevention)
|
|
||||||
- Support for different join types (INNER, LEFT, RIGHT, FULL)
|
|
||||||
- Works across different database adapters (GORM, Bun)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. Proper Condition Handling for Bun Preloads
|
|
||||||
**Location:** `pkg/common/adapters/database/bun.go:202`
|
|
||||||
**Status:** Partially Implemented
|
|
||||||
**Description:** The Bun adapter's `Preload` method currently ignores conditions passed to it.
|
|
||||||
|
|
||||||
**Current Limitation:**
|
|
||||||
```go
|
|
||||||
func (b *BunSelectQuery) Preload(relation string, conditions ...interface{}) common.SelectQuery {
|
|
||||||
// Bun uses Relation() method for preloading
|
|
||||||
// For now, we'll just pass the relation name without conditions
|
|
||||||
// TODO: Implement proper condition handling for Bun
|
|
||||||
b.query = b.query.Relation(relation)
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Required Implementation:**
|
|
||||||
- Properly handle condition parameters in Bun's Relation() method
|
|
||||||
- Support filtering on preloaded relationships
|
|
||||||
- Ensure compatibility with GORM's condition syntax where possible
|
|
||||||
- Test with various condition types
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Code Quality Improvements
|
|
||||||
|
|
||||||
### 5. Modernize Go Type Declarations
|
|
||||||
**Location:** `pkg/common/types.go:5, 42, 64, 79`
|
|
||||||
**Status:** Pending
|
|
||||||
**Priority:** Low
|
|
||||||
**Description:** Replace legacy `interface{}` with modern `any` type alias (Go 1.18+).
|
|
||||||
|
|
||||||
**Affected Lines:**
|
|
||||||
- Line 5: Function parameter or return type
|
|
||||||
- Line 42: Function parameter or return type
|
|
||||||
- Line 64: Function parameter or return type
|
|
||||||
- Line 79: Function parameter or return type
|
|
||||||
|
|
||||||
**Benefits:**
|
|
||||||
- More modern and idiomatic Go code
|
|
||||||
- Better readability
|
|
||||||
- Aligns with current Go best practices
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 6. Pre / Post select/update/delete query in transaction.
|
|
||||||
- This will allow us to set a user before doing a select
|
|
||||||
- When making changes, we can have the trigger fire with the correct user.
|
|
||||||
- Maybe wrap the handleRead,Update,Create,Delete handlers in a transaction with context that can abort when the request is cancelled or a configurable timeout is reached.
|
|
||||||
|
|
||||||
### 7.
|
|
||||||
|
|
||||||
## Additional Considerations
|
|
||||||
|
|
||||||
### Documentation
|
### Documentation
|
||||||
- Ensure all new features are documented in README.md
|
- Ensure all new features are documented in README.md
|
||||||
- Update examples to showcase new functionality
|
- Update examples to showcase new functionality
|
||||||
- Add migration notes if any breaking changes are introduced
|
- Add migration notes if any breaking changes are introduced
|
||||||
|
|
||||||
### Testing
|
|
||||||
- Add unit tests for each new feature
|
|
||||||
- Add integration tests for database adapter compatibility
|
|
||||||
- Ensure backward compatibility is maintained
|
|
||||||
|
|
||||||
### Performance
|
|
||||||
- Profile preload performance with column selection and filtering
|
|
||||||
- Optimize recursive JSON cleaning for large payloads
|
|
||||||
- Benchmark custom SQL join performance
|
|
||||||
|
|
||||||
|
|
||||||
### 8.
|
### 8.
|
||||||
|
|
||||||
@ -147,20 +15,6 @@ func (b *BunSelectQuery) Preload(relation string, conditions ...interface{}) com
|
|||||||
- Add unit tests for security providers
|
- Add unit tests for security providers
|
||||||
- Add concurrency tests for model registry
|
- Add concurrency tests for model registry
|
||||||
|
|
||||||
2. **Security Enhancements**:
|
|
||||||
- Add request size limits
|
|
||||||
- Configure CORS properly
|
|
||||||
- Implement input sanitization beyond SQL
|
|
||||||
|
|
||||||
3. **Configuration Management**:
|
|
||||||
- Centralized config system
|
|
||||||
- Environment-based configuration
|
|
||||||
|
|
||||||
4. **Graceful Shutdown**:
|
|
||||||
- Implement shutdown coordination
|
|
||||||
- Drain in-flight requests
|
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Priority Ranking
|
## Priority Ranking
|
||||||
@ -180,4 +34,4 @@ func (b *BunSelectQuery) Preload(relation string, conditions ...interface{}) com
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
**Last Updated:** 2025-11-07
|
**Last Updated:** 2025-12-09
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user