Go Environment Variable Management
This challenge focuses on building a robust system for managing environment variables within a Go application. Environment variables are crucial for configuring applications without hardcoding sensitive information or environment-specific settings, making your Go programs more flexible and secure. You will implement a mechanism to read, store, and validate these variables.
Problem Description
Your task is to create a Go package that provides functionality for reading, storing, and validating environment variables. This package should allow users to define expected environment variables, their types, and default values. It should also handle cases where variables are missing and provide informative error messages.
What needs to be achieved:
- Define a structure to represent an environment variable with its name, expected type, and an optional default value.
- Implement a function to load environment variables from the system and store them internally.
- Implement a function to retrieve the value of a specific environment variable.
- Implement validation logic to ensure that loaded environment variables conform to their expected types.
- Handle missing environment variables gracefully, either by using default values or returning an error.
Key Requirements:
- Variable Definition: Create a
ConfigVarstruct (or similar) to holdName(string),Type(e.g.,string,int,bool), andDefaultValue(interface{}) for each environment variable. - Loading: A
LoadEnvfunction that takes a slice ofConfigVardefinitions and loads the corresponding environment variables fromos.Getenv. - Retrieval: A
Getfunction that takes theNameof aConfigVarand returns its value as the definedTypeand an error if it cannot be retrieved or converted. - Type Conversion: The
Getfunction should attempt to convert the environment variable's string value to the specified type (e.g., "123" to int, "true" to bool). - Default Values: If an environment variable is not set and a
DefaultValueis provided, use theDefaultValue. - Error Handling: Provide clear error messages for missing required variables (those without defaults) and type conversion failures.
Expected Behavior:
- When
LoadEnvis called with a list ofConfigVars, the package should populate its internal store. - When
Getis called for a defined variable:- If the environment variable is set, its string value should be parsed into the target type.
- If the environment variable is not set, and a
DefaultValueexists, theDefaultValueshould be returned after being cast to the target type. - If the environment variable is not set and no
DefaultValueexists, an error should be returned. - If type conversion fails (e.g., trying to convert "abc" to an int), an error should be returned.
Edge Cases:
- Empty string values for environment variables.
- Boolean parsing (e.g., "true", "false", "1", "0").
- Integer parsing.
- Variables defined but not present in the environment and without defaults.
Examples
Example 1:
// Input (Setup in main.go or test file)
// Assume environment variables:
// APP_NAME="MyAwesomeApp"
// DEBUG_MODE="true"
// PORT="8080"
// Define configuration variables
vars := []ConfigVar{
{Name: "APP_NAME", Type: String}, // String type defined as a constant/enum
{Name: "DEBUG_MODE", Type: Bool, DefaultValue: false},
{Name: "PORT", Type: Int, DefaultValue: 3000},
{Name: "API_KEY", Type: String}, // No default, required
}
// Load the environment variables
err := LoadEnv(vars)
if err != nil {
// Handle error (e.g., missing API_KEY)
}
// Retrieve values
appName, err := Get("APP_NAME")
debugMode, err := Get("DEBUG_MODE")
port, err := Get("PORT")
apiKey, err := Get("API_KEY")
// Output (after successful LoadEnv and Get calls)
// appName: "MyAwesomeApp" (type string)
// debugMode: true (type bool)
// port: 8080 (type int)
// apiKey: "some_secret_key_from_env" (assuming it was set)
Explanation: The LoadEnv function reads the specified environment variables. Get retrieves them, converting "true" to true and "8080" to 8080. APP_NAME is found directly. DEBUG_MODE and PORT are found, overriding their defaults. API_KEY would need to be set in the environment for Get to succeed.
Example 2:
// Input (Setup in main.go or test file)
// Assume environment variables are NOT set.
// Define configuration variables
vars := []ConfigVar{
{Name: "LOG_LEVEL", Type: String, DefaultValue: "info"},
{Name: "WORKER_THREADS", Type: Int, DefaultValue: 4},
}
// Load the environment variables
err := LoadEnv(vars)
if err != nil {
// This error block should NOT be hit in this specific scenario.
}
// Retrieve values
logLevel, err := Get("LOG_LEVEL")
workerThreads, err := Get("WORKER_THREADS")
// Output
// logLevel: "info" (type string)
// workerThreads: 4 (type int)
Explanation: Since LOG_LEVEL and WORKER_THREADS are not set in the environment, their respective DefaultValues are returned.
Example 3:
// Input (Setup in main.go or test file)
// Assume environment variable:
// DATABASE_PORT="invalid_port"
// Define configuration variables
vars := []ConfigVar{
{Name: "DATABASE_PORT", Type: Int},
}
// Load the environment variables
err := LoadEnv(vars)
if err != nil {
// Handle error: "DATABASE_PORT" is missing or cannot be parsed as an int.
// The error message should be specific.
}
// Output (if LoadEnv encounters an error due to invalid type)
// Error: Failed to get or parse environment variable "DATABASE_PORT": expected int, got "invalid_port"
Explanation: LoadEnv attempts to read DATABASE_PORT. Since it's set but cannot be converted to an Int, an error is returned during the loading phase or when Get is first called for this variable if lazy loading is implemented.
Constraints
- The
ConfigVartype should support at leaststring,int, andboolas validTypevalues. You can represent these types using constants or an enum-like pattern. - The
Getfunction should return the value with the correct underlying type. - Parsing for
intshould usestrconv.Atoi. - Parsing for
boolshould be lenient, accepting "true", "false", "1", "0" (case-insensitive) and converting them tobool. - Performance is not a primary concern for this challenge; correctness and clarity are prioritized.
- The internal storage for loaded variables can be a simple map.
Notes
- Consider how to represent the
TypeinConfigVar. Using constants defined within your package is a common Go idiom. - Think about the timing of error reporting. Should
LoadEnvvalidate all variables at once, or shouldGetperform validation lazily? The problem description implies thatLoadEnvshould initiate the loading and internal parsing/validation. - The
DefaultValueshould be of typeinterface{}to accommodate different Go types, but it needs to be reliably cast to the targetTypewhen used. - The
ospackage in Go providesos.Getenvfor reading environment variables andos.LookupEnvwhich also returns a boolean indicating if the variable was set.LookupEnvcan be helpful for differentiating between an unset variable and a variable explicitly set to an empty string.