├── go.mod ├── source.go ├── source_env.go ├── source_dotenv.go ├── enver.go ├── enver_test.go └── README.md /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/msrexe/enver 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /source.go: -------------------------------------------------------------------------------- 1 | package enver 2 | 3 | type Source interface { 4 | // Type returns the type of the source. 5 | Type() string 6 | // Get returns the key-value pairs of the source. 7 | Get() (map[string]any, error) 8 | } 9 | -------------------------------------------------------------------------------- /source_env.go: -------------------------------------------------------------------------------- 1 | package enver 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | const ( 10 | Type_Env = "env" 11 | ) 12 | 13 | type Source_Env struct { 14 | } 15 | 16 | func (s *Source_Env) Type() string { 17 | return Type_Env 18 | } 19 | 20 | func (s *Source_Env) Get() (map[string]any, error) { 21 | return s.readEnv() 22 | } 23 | 24 | func (s *Source_Env) readEnv() (map[string]any, error) { 25 | pairs := make(map[string]any) 26 | 27 | for _, pair := range os.Environ() { 28 | parts := strings.Split(pair, "=") 29 | if len(parts) != 2 { 30 | return pairs, fmt.Errorf("invalid environment variable format. Expected key=value format, got: %s", pair) 31 | } 32 | 33 | pairs[parts[0]] = parts[1] 34 | } 35 | 36 | return pairs, nil 37 | } 38 | -------------------------------------------------------------------------------- /source_dotenv.go: -------------------------------------------------------------------------------- 1 | package enver 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | const ( 10 | Type_DotEnv = "dotenv" 11 | ) 12 | 13 | type Source_DotEnv struct { 14 | Path string 15 | } 16 | 17 | func (s *Source_DotEnv) Type() string { 18 | return Type_DotEnv 19 | } 20 | 21 | func (s *Source_DotEnv) Get() (map[string]any, error) { 22 | return s.readDotEnvFile(s.Path) 23 | } 24 | 25 | func (s *Source_DotEnv) readDotEnvFile(path string) (map[string]any, error) { 26 | if path == "" { 27 | path = ".env" 28 | } 29 | 30 | pairs := make(map[string]any) 31 | 32 | data, err := os.ReadFile(path) 33 | if err != nil { 34 | return pairs, fmt.Errorf("failed to read .env file: %w", err) 35 | } 36 | 37 | lines := strings.Split(string(data), "\n") 38 | for _, line := range lines { 39 | if line == "" { 40 | continue 41 | } 42 | 43 | parts := strings.Split(line, "=") 44 | if len(parts) != 2 { 45 | return pairs, fmt.Errorf("invalid .env file format. Expected key=value format, got: %s", line) 46 | } 47 | 48 | pairs[parts[0]] = parts[1] 49 | } 50 | 51 | return pairs, nil 52 | } 53 | -------------------------------------------------------------------------------- /enver.go: -------------------------------------------------------------------------------- 1 | package enver 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "strings" 7 | ) 8 | 9 | var ( 10 | DefaultSources = []Source{ 11 | &Source_Env{}, 12 | &Source_DotEnv{}, 13 | } 14 | ) 15 | 16 | // Fill fills the given struct with environment variables from the sources. 17 | // The given struct has struct tags to specify the environment variable key and default value. 18 | // The struct tags have the following format: 19 | // `env:"KEY,DEFAULT"` 20 | // The KEY is the environment variable key. 21 | // The DEFAULT is the default value to use if the environment variable is not set. 22 | // The sources are used in the order they are provided. Precedence is given to sources that are provided first. 23 | func Fill(envs any, sources ...Source) error { 24 | if reflect.TypeOf(envs).Kind() != reflect.Ptr { 25 | return fmt.Errorf("envs parameter must be a pointer to a struct") 26 | } 27 | 28 | for i := len(sources) - 1; i >= 0; i-- { 29 | envMap, err := sources[i].Get() 30 | if err != nil { 31 | return err 32 | } 33 | 34 | err = fill(envs, envMap) 35 | if err != nil { 36 | return err 37 | } 38 | } 39 | 40 | return nil 41 | } 42 | 43 | func fill(obj interface{}, envMap map[string]any) error { 44 | objType := reflect.TypeOf(obj) 45 | 46 | if objType.Kind() != reflect.Ptr { 47 | return fmt.Errorf("setField: obj parameter must be a pointer to a struct") 48 | } 49 | 50 | objValue := reflect.ValueOf(obj).Elem() 51 | 52 | for i := 0; i < objType.Elem().NumField(); i++ { 53 | field := objType.Elem().Field(i) 54 | 55 | envTag := field.Tag.Get("env") 56 | if envTag != "" { 57 | parts := strings.Split(envTag, ",") 58 | envKey := parts[0] 59 | defaultValue := parts[1] 60 | 61 | envValue, ok := envMap[envKey] 62 | if !ok { 63 | envValue = defaultValue 64 | } 65 | 66 | fieldValue := objValue.FieldByName(field.Name) 67 | convertedValue := reflect.ValueOf(envValue).Convert(fieldValue.Type()) 68 | fieldValue.Set(convertedValue) 69 | } 70 | } 71 | 72 | return nil 73 | } 74 | -------------------------------------------------------------------------------- /enver_test.go: -------------------------------------------------------------------------------- 1 | package enver 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestFill_ProcessEnv(t *testing.T) { 9 | type envs struct { 10 | Env string `env:"ENV,default"` 11 | } 12 | 13 | if err := os.Setenv("ENV", "test"); err != nil { 14 | t.Errorf("Setenv failed: %v", err) 15 | } 16 | 17 | testEnvs := envs{} 18 | err := Fill(&testEnvs, &Source_Env{}) 19 | if err != nil { 20 | t.Errorf("Fill failed: %v", err) 21 | } 22 | 23 | if testEnvs.Env != "test" { 24 | t.Errorf("Expected test, got %s", testEnvs.Env) 25 | } 26 | } 27 | 28 | func TestFill_ProcessDotEnv(t *testing.T) { 29 | type envs struct { 30 | Env string `env:"ENV,default"` 31 | } 32 | 33 | if err := os.WriteFile(".env", []byte("ENV=test"), 0644); err != nil { 34 | t.Errorf("WriteFile failed: %v", err) 35 | } 36 | 37 | testEnvs := envs{} 38 | err := Fill(&testEnvs, &Source_DotEnv{}) 39 | if err != nil { 40 | t.Errorf("Fill failed: %v", err) 41 | } 42 | 43 | if testEnvs.Env != "test" { 44 | t.Errorf("Expected test, got %s", testEnvs.Env) 45 | } 46 | 47 | if err := os.Remove(".env"); err != nil { 48 | t.Errorf("Remove failed: %v", err) 49 | } 50 | } 51 | 52 | func TestFill_ProcessEnvAndDotEnv(t *testing.T) { 53 | type envs struct { 54 | Env string `env:"ENV,default"` 55 | } 56 | 57 | if err := os.Setenv("ENV", "test"); err != nil { 58 | t.Errorf("Setenv failed: %v", err) 59 | } 60 | 61 | if err := os.WriteFile(".env", []byte("ENV=env"), 0644); err != nil { 62 | t.Errorf("WriteFile failed: %v", err) 63 | } 64 | 65 | testEnvs := envs{} 66 | err := Fill(&testEnvs, &Source_Env{}, &Source_DotEnv{}) 67 | if err != nil { 68 | t.Errorf("Fill failed: %v", err) 69 | } 70 | 71 | if testEnvs.Env != "test" { 72 | t.Errorf("Expected test, got %s", testEnvs.Env) 73 | } 74 | 75 | if err := os.Remove(".env"); err != nil { 76 | t.Errorf("Remove failed: %v", err) 77 | } 78 | } 79 | 80 | func TestFill_DefaultValue(t *testing.T) { 81 | type envs struct { 82 | Env string `env:"ENV,default"` 83 | } 84 | 85 | testEnvs := envs{} 86 | err := Fill(&testEnvs, &Source_Env{}) 87 | if err != nil { 88 | t.Errorf("Fill failed: %v", err) 89 | } 90 | 91 | if testEnvs.Env != "default" { 92 | t.Errorf("Expected default, got %s", testEnvs.Env) 93 | } 94 | } 95 | 96 | func TestFill_InvalidStruct(t *testing.T) { 97 | type envs struct { 98 | Env string `env:"ENVTEST,default"` 99 | } 100 | 101 | testEnvs := envs{} 102 | err := Fill(testEnvs, &Source_Env{}) 103 | if err == nil { 104 | t.Errorf("Fill should have failed") 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Enver 2 | 3 | Enver is a Go package designed to simplify the process of reading environment variables with default values from various sources. It provides a straightforward API that allows developers to easily integrate environment variable management into their applications. Enver supports reading from two primary sources: 4 | 5 | 1. **Environment Variables**: Directly from the system's environment variables. 6 | 2. **.env Files**: From `.env` files located within your project directory. 7 | 8 | Additionally, Enver enables overriding values by specifying multiple sources, with precedence given to the sources listed first. 9 | 10 | ## Features 11 | 12 | - Read environment variables directly from the system or `.env` files. 13 | - Specify default values for environment variables. 14 | - Override environment variable values by specifying multiple sources with precedence. 15 | 16 | ## Installation 17 | 18 | To use Enver in your Go project, you need to install it by running: 19 | 20 | ```bash 21 | go get github.com/msrexe/enver 22 | ``` 23 | 24 | Ensure you have Go 1.18 or higher, as specified in the `go.mod` file. 25 | 26 | ## Usage 27 | 28 | ### Defining Your Configuration Struct 29 | 30 | Define a struct with fields representing your configuration parameters. Use struct tags to specify the environment variable keys and default values: 31 | 32 | ```go 33 | type Config struct { 34 | Port string `env:"PORT,8080"` 35 | Host string `env:"HOST,localhost"` 36 | } 37 | ``` 38 | 39 | ### Reading Environment Variables 40 | 41 | To read environment variables into your struct, use the `Fill` function provided by Enver. You can specify one or more sources (`Source_Env` for system environment variables and `Source_DotEnv` for `.env` file variables): 42 | 43 | ```go 44 | config := Config{} 45 | err := enver.Fill(&config, &enver.Source_Env{}, &enver.Source_DotEnv{}) 46 | if err != nil { 47 | log.Fatalf("Error filling config: %v", err) 48 | } 49 | ``` 50 | 51 | ### Specifying Custom `.env` File Path 52 | 53 | If you need to specify a custom path for your `.env` file, you can do so by setting the `Path` field of `Source_DotEnv`: 54 | 55 | ```go 56 | dotenvSource := enver.Source_DotEnv{Path: "path/to/your/.env"} 57 | err := enver.Fill(&config, &dotenvSource) 58 | ``` 59 | 60 | ## Testing 61 | 62 | Enver includes a set of unit tests demonstrating how to test reading from environment variables and `.env` files. Refer to the `enver_test.go` file for examples. 63 | 64 | ## Contributing 65 | 66 | Contributions to Enver are welcome! Please feel free to submit issues, pull requests, or enhancements to improve the library. 67 | 68 | ## License 69 | 70 | Enver is open-source software licensed under the MIT license. 71 | --------------------------------------------------------------------------------