Environment Variable Manager with Type Conversion and Validation
Typed environment loader that reads environment variables with defaults, required checks, and per-type parsers for integers, booleans, floats, and durations.
main.go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
package main
import (
"errors"
"fmt"
"os"
"strconv"
"strings"
"time"
)
// EnvVar represents an environment variable configuration
type EnvVar struct {
Name string // Name of the environment variable
Description string // Human-readable description
Type string // Type: string, int, bool, float, duration
Default interface{} // Default value (nil for required)
Required bool // Whether the variable is required
Validation func(interface{}) error // Custom validation function
}
// EnvManager manages environment variable loading and validation
type EnvManager struct {
variables map[string]*EnvVar // Registered environment variables
values map[string]interface{} // Loaded values
errors []error // Collection of errors during loading
}
// NewEnvManager creates a new environment variable manager
func NewEnvManager() *EnvManager {
return &EnvManager{
variables: make(map[string]*EnvVar),
values: make(map[string]interface{}),
}
}
// Register adds an environment variable to the manager
func (e *EnvManager) Register(envVar EnvVar) {
// Validate basic configuration
if envVar.Name == "" {
e.errors = append(e.errors, errors.New("environment variable name cannot be empty"))
return
}
if envVar.Type == "" {
envVar.Type = "string" // Default to string type
}
// Check for duplicate registration
if _, exists := e.variables[envVar.Name]; exists {
e.errors = append(e.errors, fmt.Errorf("environment variable '%s' already registered", envVar.Name))
return
}
e.variables[envVar.Name] = &envVar
}
// Load reads and validates all registered environment variables
func (e *EnvManager) Load() error {
// Reset values and errors
e.values = make(map[string]interface{})
e.errors = nil
// Process each registered variable
for name, envVar := range e.variables {
e.processVariable(name, envVar)
}
// Return aggregated errors if any
if len(e.errors) > 0 {
errMsg := "failed to load environment variables:\n"
for _, err := range e.errors {
errMsg += fmt.Sprintf(" - %v\n", err)
}
return errors.New(errMsg)
}
return nil
}
// processVariable loads and validates a single environment variable
func (e *EnvManager) processVariable(name string, envVar *EnvVar) {
// Get value from environment
strValue := os.Getenv(name)
// Check if required
if strValue == "" {
if envVar.Required {
e.errors = append(e.errors, fmt.Errorf("required environment variable '%s' is not set (%s)", name, envVar.Description))
return
}
// Use default value if available
if envVar.Default != nil {
e.values[name] = envVar.Default
return
}
// Use zero value for optional variables with no default
e.values[name] = getZeroValue(envVar.Type)
return
}
// Convert to specified type
convertedValue, err := convertValue(strValue, envVar.Type)
if err != nil {
e.errors = append(e.errors, fmt.Errorf("failed to convert '%s' (value: '%s') to %s: %v", name, strValue, envVar.Type, err))
return
}
// Run custom validation if provided
if envVar.Validation != nil {
if err := envVar.Validation(convertedValue); err != nil {
e.errors = append(e.errors, fmt.Errorf("validation failed for '%s': %v", name, err))
return
}
}
// Store the converted value
e.values[name] = convertedValue
}
// convertValue converts a string to the specified type
func convertValue(value string, typ string) (interface{}, error) {
switch strings.ToLower(typ) {
case "string":
return value, nil
case "int":
return strconv.Atoi(value)
case "bool":
return strconv.ParseBool(value)
case "float":
return strconv.ParseFloat(value, 64)
case "duration":
return time.ParseDuration(value)
default:
return nil, fmt.Errorf("unsupported type '%s'", typ)
}
}
// getZeroValue returns the zero value for the specified type
func getZeroValue(typ string) interface{} {
switch strings.ToLower(typ) {
case "string":
return ""
case "int":
return 0
case "bool":
return false
case "float":
return 0.0
case "duration":
return 0 * time.Second
default:
return nil
}
}
// GetString retrieves a string value from the loaded environment variables
func (e *EnvManager) GetString(name string) (string, error) {
value, exists := e.values[name]
if !exists {
return "", fmt.Errorf("environment variable '%s' not found", name)
}
strVal, ok := value.(string)
if !ok {
return "", fmt.Errorf("environment variable '%s' is not a string", name)
}
return strVal, nil
}
// GetInt retrieves an int value from the loaded environment variables
func (e *EnvManager) GetInt(name string) (int, error) {
value, exists := e.values[name]
if !exists {
return 0, fmt.Errorf("environment variable '%s' not found", name)
}
intVal, ok := value.(int)
if !ok {
return 0, fmt.Errorf("environment variable '%s' is not an int", name)
}
return intVal, nil
}
// GetBool retrieves a bool value from the loaded environment variables
func (e *EnvManager) GetBool(name string) (bool, error) {
value, exists := e.values[name]
if !exists {
return false, fmt.Errorf("environment variable '%s' not found", name)
}
boolVal, ok := value.(bool)
if !ok {
return false, fmt.Errorf("environment variable '%s' is not a bool", name)
}
return boolVal, nil
}
// GetFloat retrieves a float64 value from the loaded environment variables
func (e *EnvManager) GetFloat(name string) (float64, error) {
value, exists := e.values[name]
if !exists {
return 0.0, fmt.Errorf("environment variable '%s' not found", name)
}
floatVal, ok := value.(float64)
if !ok {
return 0.0, fmt.Errorf("environment variable '%s' is not a float", name)
}
return floatVal, nil
}
// GetDuration retrieves a time.Duration value from the loaded environment variables
func (e *EnvManager) GetDuration(name string) (time.Duration, error) {
value, exists := e.values[name]
if !exists {
return 0, fmt.Errorf("environment variable '%s' not found", name)
}
durVal, ok := value.(time.Duration)
if !ok {
return 0, fmt.Errorf("environment variable '%s' is not a duration", name)
}
return durVal, nil
}
func main() {
// Initialize manager
manager := NewEnvManager()
// Register environment variables
manager.Register(EnvVar{
Name: "PORT",
Description: "HTTP server port",
Type: "int",
Default: 8080,
Required: false,
Validation: func(val interface{}) error {
port := val.(int)
if port < 1 || port > 65535 {
return fmt.Errorf("port must be between 1 and 65535")
}
return nil
},
})
manager.Register(EnvVar{
Name: "DEBUG",
Description: "Enable debug mode",
Type: "bool",
Default: false,
Required: false,
})
manager.Register(EnvVar{
Name: "DB_URL",
Description: "Database connection URL",
Type: "string",
Required: true,
})
// Load variables
if err := manager.Load(); err != nil {
fmt.Printf("Error loading env vars: %v\n", err)
return
}
// Retrieve values
port, _ := manager.GetInt("PORT")
debug, _ := manager.GetBool("DEBUG")
dbURL, _ := manager.GetString("DB_URL")
fmt.Printf("Loaded config: PORT=%d, DEBUG=%t, DB_URL=%s\n", port, debug, dbURL)
}How It Works
Typed environment loader that reads environment variables with defaults, required checks, and per-type parsers for integers, booleans, floats, and durations.
Helper functions fetch environment values, convert to desired types with strconv and time.ParseDuration, apply defaults, and return descriptive errors when validation fails; sample usage shows multiple variable types.
Key Concepts
- 1Per-type parsers keep conversions centralized and testable.
- 2Required and optional helpers reduce boilerplate checks.
- 3Error messages name the environment variable and the failed value.
When to Use This Pattern
- Configuring services via environment in containers.
- CLI tools needing typed flags from environment overrides.
- Bootstrap scripts that must fail fast on misconfiguration.
Best Practices
- Validate ranges such as ports and timeouts after parsing.
- Avoid silent fallbacks when a required variable is missing.
- Document all environment keys and defaults for operators.
Go Version1.18
Difficultyintermediate
Production ReadyYes
Lines of Code281