Hone logo
Hone
Problems

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:

  1. Variable Definition: Create a ConfigVar struct (or similar) to hold Name (string), Type (e.g., string, int, bool), and DefaultValue (interface{}) for each environment variable.
  2. Loading: A LoadEnv function that takes a slice of ConfigVar definitions and loads the corresponding environment variables from os.Getenv.
  3. Retrieval: A Get function that takes the Name of a ConfigVar and returns its value as the defined Type and an error if it cannot be retrieved or converted.
  4. Type Conversion: The Get function should attempt to convert the environment variable's string value to the specified type (e.g., "123" to int, "true" to bool).
  5. Default Values: If an environment variable is not set and a DefaultValue is provided, use the DefaultValue.
  6. Error Handling: Provide clear error messages for missing required variables (those without defaults) and type conversion failures.

Expected Behavior:

  • When LoadEnv is called with a list of ConfigVars, the package should populate its internal store.
  • When Get is 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 DefaultValue exists, the DefaultValue should be returned after being cast to the target type.
    • If the environment variable is not set and no DefaultValue exists, 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 ConfigVar type should support at least string, int, and bool as valid Type values. You can represent these types using constants or an enum-like pattern.
  • The Get function should return the value with the correct underlying type.
  • Parsing for int should use strconv.Atoi.
  • Parsing for bool should be lenient, accepting "true", "false", "1", "0" (case-insensitive) and converting them to bool.
  • 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 Type in ConfigVar. Using constants defined within your package is a common Go idiom.
  • Think about the timing of error reporting. Should LoadEnv validate all variables at once, or should Get perform validation lazily? The problem description implies that LoadEnv should initiate the loading and internal parsing/validation.
  • The DefaultValue should be of type interface{} to accommodate different Go types, but it needs to be reliably cast to the target Type when used.
  • The os package in Go provides os.Getenv for reading environment variables and os.LookupEnv which also returns a boolean indicating if the variable was set. LookupEnv can be helpful for differentiating between an unset variable and a variable explicitly set to an empty string.
Loading editor...
go