├── .gitignore ├── .github ├── dependabot.yml └── workflows │ ├── generate-changelog.yml │ └── build.yml ├── var.go ├── go.mod ├── var_test.go ├── setup_test.go ├── helper_test.go ├── LICENSE.txt ├── cast.go ├── error.go ├── node.go ├── go.sum ├── struct_custom_validation_example_test.go ├── struct_custom_field_set_example_test.go ├── node_test.go ├── README.md ├── env_example_test.go ├── struct.go ├── env.go ├── env_test.go ├── struct_test.go ├── env_tree.go ├── env_tree_example_test.go └── env_tree_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | vendor/* 2 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | pull-request-branch-name: 8 | separator: "-" 9 | open-pull-requests-limit: 10 10 | -------------------------------------------------------------------------------- /.github/workflows/generate-changelog.yml: -------------------------------------------------------------------------------- 1 | name: Generate the changelog 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | call-workflow: 13 | uses: antham/go-workflow-github-action/.github/workflows/generate-changelog.yml@master 14 | -------------------------------------------------------------------------------- /var.go: -------------------------------------------------------------------------------- 1 | package envh 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | ) 7 | 8 | func parseVars() *map[string]string { 9 | results := map[string]string{} 10 | 11 | for _, v := range os.Environ() { 12 | e := strings.SplitN(v, "=", 2) 13 | 14 | results[e[0]] = e[1] 15 | } 16 | 17 | return &results 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | branches: 8 | - master 9 | - main 10 | pull_request: 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | call-workflow: 16 | uses: antham/go-workflow-github-action/.github/workflows/build-library.yml@master 17 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/antham/envh 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/sirupsen/logrus v1.9.3 7 | github.com/stretchr/testify v1.11.1 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/pmezard/go-difflib v1.0.0 // indirect 13 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect 14 | gopkg.in/yaml.v3 v3.0.1 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /var_test.go: -------------------------------------------------------------------------------- 1 | package envh 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestParseVars(t *testing.T) { 10 | setTestingEnvs() 11 | result := parseVars() 12 | 13 | assert.Equal(t, "test1", (*result)["TEST1"], "Must extract and parse environment variables") 14 | assert.Contains(t, "=test2=", (*result)["TEST2"], "Must extract and parse environment variables") 15 | } 16 | -------------------------------------------------------------------------------- /setup_test.go: -------------------------------------------------------------------------------- 1 | package envh 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/sirupsen/logrus" 9 | ) 10 | 11 | var envs []string 12 | 13 | func TestMain(m *testing.M) { 14 | saveExistingEnvs() 15 | code := m.Run() 16 | os.Exit(code) 17 | } 18 | 19 | func saveExistingEnvs() { 20 | envs = os.Environ() 21 | } 22 | 23 | func setEnv(key string, value string) { 24 | err := os.Setenv(key, value) 25 | 26 | if err != nil { 27 | logrus.Fatal(err) 28 | } 29 | } 30 | 31 | func restoreEnvs() { 32 | os.Clearenv() 33 | 34 | if len(envs) != 0 { 35 | for _, envCouple := range envs { 36 | parseEnv := strings.Split(envCouple, "=") 37 | 38 | setEnv(parseEnv[0], strings.Join(parseEnv[1:], "=")) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /helper_test.go: -------------------------------------------------------------------------------- 1 | package envh 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/sirupsen/logrus" 7 | ) 8 | 9 | func setTestingEnvs() { 10 | datas := map[string]string{ 11 | "TEST1": "test1", 12 | "TEST2": "=test2=", 13 | } 14 | 15 | for k, v := range datas { 16 | err := os.Setenv(k, v) 17 | 18 | if err != nil { 19 | logrus.Fatal(err) 20 | } 21 | } 22 | } 23 | 24 | func setTestingEnvsForTree() { 25 | datas := map[string]string{ 26 | "ENVH_TEST1_TEST2_TEST3": "test1", 27 | "ENVH_TEST1_TEST2_TEST4": "test2", 28 | "ENVH_TEST1_TEST5_TEST6": "test3", 29 | "ENVH_TEST1_TEST7_TEST2": "test4", 30 | "ENVH_TEST1": "test5", 31 | } 32 | 33 | for k, v := range datas { 34 | err := os.Setenv(k, v) 35 | 36 | if err != nil { 37 | logrus.Fatal(err) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Anthony HAMON 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /cast.go: -------------------------------------------------------------------------------- 1 | package envh 2 | 3 | import ( 4 | "strconv" 5 | ) 6 | 7 | func getString(fun func() (string, bool)) (string, error) { 8 | if v, ok := fun(); ok { 9 | return v, nil 10 | } 11 | 12 | return "", VariableNotFoundError{} 13 | } 14 | 15 | func getInt(fun func() (string, bool)) (int, error) { 16 | v, ok := fun() 17 | 18 | if !ok { 19 | return 0, VariableNotFoundError{} 20 | } 21 | 22 | i, err := strconv.Atoi(v) 23 | 24 | if err != nil { 25 | return 0, WrongTypeError{v, "int"} 26 | } 27 | 28 | return i, nil 29 | } 30 | 31 | func getFloat(fun func() (string, bool)) (float32, error) { 32 | v, ok := fun() 33 | 34 | if !ok { 35 | return 0, VariableNotFoundError{} 36 | } 37 | 38 | f, err := strconv.ParseFloat(v, 32) 39 | 40 | if err != nil { 41 | return 0, WrongTypeError{v, "float"} 42 | } 43 | 44 | return float32(f), nil 45 | } 46 | 47 | func getBool(fun func() (string, bool)) (bool, error) { 48 | v, ok := fun() 49 | 50 | if !ok { 51 | return false, VariableNotFoundError{} 52 | } 53 | 54 | b, err := strconv.ParseBool(v) 55 | 56 | if err != nil { 57 | return false, WrongTypeError{v, "bool"} 58 | } 59 | 60 | return b, nil 61 | } 62 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | package envh 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | // VariableNotFoundError is triggered when environment variable cannot be found 9 | type VariableNotFoundError struct { 10 | } 11 | 12 | // Error dump error 13 | func (e VariableNotFoundError) Error() string { 14 | return "Variable not found" 15 | } 16 | 17 | // NodeNotFoundError is triggered when tree node cannot be found 18 | type NodeNotFoundError struct { 19 | KeyChain []string 20 | } 21 | 22 | // Error dump error 23 | func (e NodeNotFoundError) Error() string { 24 | return fmt.Sprintf(`No node found at path "%s"`, strings.Join(e.KeyChain, " -> ")) 25 | } 26 | 27 | // WrongTypeError is triggered when we try to convert variable to a wrong type 28 | type WrongTypeError struct { 29 | Value interface{} 30 | Type string 31 | } 32 | 33 | // Error dump error 34 | func (e WrongTypeError) Error() string { 35 | return fmt.Sprintf(`Value "%s" can't be converted to type "%s"`, e.Value, e.Type) 36 | } 37 | 38 | // TypeUnsupported is triggered when a type isn't supported 39 | type TypeUnsupported struct { 40 | ActualType string 41 | RequiredType string 42 | } 43 | 44 | // Error dump error 45 | func (e TypeUnsupported) Error() string { 46 | return fmt.Sprintf(`Type "%s" is not supported : you must provide "%s"`, e.ActualType, e.RequiredType) 47 | } 48 | -------------------------------------------------------------------------------- /node.go: -------------------------------------------------------------------------------- 1 | package envh 2 | 3 | type node struct { 4 | children []*node 5 | key string 6 | value string 7 | hasValue bool 8 | } 9 | 10 | func newNode() *node { 11 | return &node{children: []*node{}} 12 | } 13 | 14 | func (n *node) findAllNodesByKey(key string, withValue bool) *[]*node { 15 | results := []*node{} 16 | nodes := n.children 17 | 18 | for { 19 | carry := []*node{} 20 | 21 | for _, node := range nodes { 22 | if node.key == key { 23 | if withValue && node.hasValue || !withValue { 24 | results = append(results, node) 25 | } 26 | } 27 | 28 | carry = append(carry, node.children...) 29 | } 30 | 31 | nodes = carry 32 | 33 | if len(carry) == 0 { 34 | return &results 35 | } 36 | } 37 | } 38 | 39 | func (n *node) findNodeByKeyChain(keyChain *[]string) (*node, bool) { 40 | if len(*keyChain) == 0 { 41 | return nil, false 42 | } 43 | 44 | current := n 45 | 46 | for _, key := range *keyChain { 47 | node, exists := current.findNodeByKey(key) 48 | 49 | if !exists { 50 | return nil, false 51 | } 52 | 53 | current = node 54 | } 55 | 56 | return current, true 57 | } 58 | 59 | func (n *node) findNodeByKey(key string) (*node, bool) { 60 | for _, child := range n.children { 61 | if child.key == key { 62 | return child, true 63 | } 64 | } 65 | 66 | return nil, false 67 | } 68 | 69 | func (n *node) appendNode(child *node) bool { 70 | if _, ok := n.findNodeByKey(child.key); ok { 71 | return false 72 | } 73 | 74 | n.children = append(n.children, child) 75 | 76 | return true 77 | } 78 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 5 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 6 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 7 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 8 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 9 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 10 | github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= 11 | github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= 12 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= 13 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 14 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 15 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 16 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 17 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 18 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 19 | -------------------------------------------------------------------------------- /struct_custom_validation_example_test.go: -------------------------------------------------------------------------------- 1 | package envh 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | type CONFIG3 struct { 11 | SERVER1 struct { 12 | IP string 13 | PORT string 14 | } 15 | SERVER2 struct { 16 | IP string 17 | PORT string 18 | } 19 | } 20 | 21 | func (c *CONFIG3) Walk(tree *EnvTree, keyChain []string) (bool, error) { 22 | if validator, ok := map[string]func(*EnvTree, []string) error{ 23 | "CONFIG3_SERVER1_IP": c.validateIP, 24 | "CONFIG3_SERVER2_IP": c.validateIP, 25 | "CONFIG3_SERVER1_PORT": c.validatePort, 26 | "CONFIG3_SERVER2_PORT": c.validatePort, 27 | }[strings.Join(keyChain, "_")]; ok { 28 | return false, validator(tree, keyChain) 29 | } 30 | 31 | return false, nil 32 | } 33 | 34 | func (c *CONFIG3) validateIP(tree *EnvTree, keyChain []string) error { 35 | ipStr, err := tree.FindString(keyChain...) 36 | 37 | if err != nil { 38 | return err 39 | } 40 | 41 | if ip := net.ParseIP(ipStr); ip == nil { 42 | return fmt.Errorf(`"%s" is not a valid IP change "%s"`, ipStr, strings.Join(keyChain, "_")) 43 | } 44 | 45 | return nil 46 | } 47 | 48 | func (c *CONFIG3) validatePort(tree *EnvTree, keyChain []string) error { 49 | port, err := tree.FindInt(keyChain...) 50 | 51 | if err != nil { 52 | return err 53 | } 54 | 55 | if port < 1 || port > 65535 { 56 | return fmt.Errorf(`"%d" is not a valid port, must be comprised between 1 and 65535 "%s"`, port, strings.Join(keyChain, "_")) 57 | } 58 | 59 | return nil 60 | } 61 | 62 | func ExampleStructWalker_customValidation() { 63 | os.Clearenv() 64 | setEnv("CONFIG3_SERVER1_IP", "127.0.0.1") 65 | setEnv("CONFIG3_SERVER1_PORT", "3000") 66 | setEnv("CONFIG3_SERVER2_IP", "localhost") 67 | setEnv("CONFIG3_SERVER2_PORT", "4000") 68 | 69 | env, err := NewEnvTree("^CONFIG3", "_") 70 | 71 | if err != nil { 72 | return 73 | } 74 | 75 | s := CONFIG3{} 76 | 77 | err = env.PopulateStruct(&s) 78 | 79 | fmt.Println(err) 80 | // Output: "localhost" is not a valid IP change "CONFIG3_SERVER2_IP" 81 | } 82 | -------------------------------------------------------------------------------- /struct_custom_field_set_example_test.go: -------------------------------------------------------------------------------- 1 | package envh 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "strings" 8 | ) 9 | 10 | type CONFIG2 struct { 11 | DB struct { 12 | USERNAME string 13 | PASSWORD string 14 | HOST string 15 | NAME string 16 | PORT int 17 | URL string 18 | USAGELIMIT float32 19 | } 20 | MAILER struct { 21 | HOST string 22 | USERNAME string 23 | PASSWORD string 24 | ENABLED bool 25 | } 26 | MAP map[string]string 27 | } 28 | 29 | func (c *CONFIG2) Walk(tree *EnvTree, keyChain []string) (bool, error) { 30 | if setter, ok := map[string]func(*EnvTree, []string) error{ 31 | "CONFIG2_DB_URL": c.setURL, 32 | "CONFIG2_MAP": c.setMap, 33 | }[strings.Join(keyChain, "_")]; ok { 34 | return true, setter(tree, keyChain) 35 | } 36 | 37 | return false, nil 38 | } 39 | 40 | func (c *CONFIG2) setMap(tree *EnvTree, keyChain []string) error { 41 | datas := map[string]string{} 42 | 43 | keys, err := tree.FindChildrenKeys(keyChain...) 44 | 45 | if err != nil { 46 | return err 47 | } 48 | 49 | for _, key := range keys { 50 | value, err := tree.FindString(append(keyChain, key)...) 51 | 52 | if err != nil { 53 | return err 54 | } 55 | 56 | datas[key] = value 57 | } 58 | 59 | c.MAP = datas 60 | 61 | return nil 62 | } 63 | 64 | func (c *CONFIG2) setURL(tree *EnvTree, keyChain []string) error { 65 | datas := map[string]string{} 66 | 67 | for _, key := range []string{"USERNAME", "PASSWORD", "HOST", "NAME"} { 68 | value, err := tree.FindString("CONFIG2", "DB", key) 69 | 70 | if err != nil { 71 | return err 72 | } 73 | 74 | datas[key] = value 75 | } 76 | 77 | port, err := tree.FindInt("CONFIG2", "DB", "PORT") 78 | 79 | if err != nil { 80 | return err 81 | } 82 | 83 | c.DB.URL = fmt.Sprintf("jdbc:mysql://%s:%d/%s?user=%s&password=%s", datas["HOST"], port, datas["NAME"], datas["USERNAME"], datas["PASSWORD"]) 84 | 85 | return nil 86 | } 87 | 88 | func ExampleStructWalker_customFieldSet() { 89 | os.Clearenv() 90 | setEnv("CONFIG2_DB_USERNAME", "foo") 91 | setEnv("CONFIG2_DB_PASSWORD", "bar") 92 | setEnv("CONFIG2_DB_HOST", "localhost") 93 | setEnv("CONFIG2_DB_NAME", "my-db") 94 | setEnv("CONFIG2_DB_PORT", "3306") 95 | setEnv("CONFIG2_DB_USAGELIMIT", "95.6") 96 | setEnv("CONFIG2_MAILER_HOST", "127.0.0.1") 97 | setEnv("CONFIG2_MAILER_USERNAME", "foo") 98 | setEnv("CONFIG2_MAILER_PASSWORD", "bar") 99 | setEnv("CONFIG2_MAILER_ENABLED", "true") 100 | setEnv("CONFIG2_MAP_KEY1", "value1") 101 | setEnv("CONFIG2_MAP_KEY2", "value2") 102 | setEnv("CONFIG2_MAP_KEY3", "value3") 103 | 104 | env, err := NewEnvTree("^CONFIG2", "_") 105 | 106 | if err != nil { 107 | return 108 | } 109 | 110 | s := CONFIG2{} 111 | 112 | err = env.PopulateStruct(&s) 113 | 114 | if err != nil { 115 | return 116 | } 117 | 118 | b, err := json.Marshal(s) 119 | 120 | if err != nil { 121 | return 122 | } 123 | 124 | fmt.Println(string(b)) 125 | // Output: 126 | // {"DB":{"USERNAME":"foo","PASSWORD":"bar","HOST":"localhost","NAME":"my-db","PORT":3306,"URL":"jdbc:mysql://localhost:3306/my-db?user=foo\u0026password=bar","USAGELIMIT":95.6},"MAILER":{"HOST":"127.0.0.1","USERNAME":"foo","PASSWORD":"bar","ENABLED":true},"MAP":{"KEY1":"value1","KEY2":"value2","KEY3":"value3"}} 127 | } 128 | -------------------------------------------------------------------------------- /node_test.go: -------------------------------------------------------------------------------- 1 | package envh 2 | 3 | import ( 4 | "regexp" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestCreateANode(t *testing.T) { 11 | n := newNode() 12 | 13 | assert.Equal(t, *n, node{children: []*node{}}, "Must creates a new node") 14 | } 15 | 16 | func TestFindNodeByKey(t *testing.T) { 17 | root := newNode() 18 | 19 | node := newNode() 20 | node.key = "test" 21 | node.value = "value" 22 | root.appendNode(node) 23 | 24 | result, exists := root.findNodeByKey("test") 25 | 26 | assert.True(t, exists, "Must return true cause element was found") 27 | assert.Equal(t, node, result, "Must return child node with key test") 28 | 29 | _, exists = root.findNodeByKey("test1") 30 | 31 | assert.False(t, exists, "Must return false cause element was not found") 32 | } 33 | 34 | func TestAppendNode(t *testing.T) { 35 | root := newNode() 36 | 37 | node := newNode() 38 | node.key = "test" 39 | node.value = "value" 40 | 41 | result := root.appendNode(node) 42 | 43 | assert.True(t, result, "Must return true cause element was successfully added") 44 | assert.Equal(t, node, root.children[0], "Must have node added as child") 45 | 46 | node2 := newNode() 47 | node2.key = "test" 48 | node2.value = "value2" 49 | 50 | result = root.appendNode(node2) 51 | 52 | assert.False(t, result, "Must return false cause an element with this key already exists") 53 | assert.Len(t, root.children, 1, "Must still have one node") 54 | assert.Equal(t, node, root.children[0], "Must have node added before") 55 | } 56 | 57 | func TestFindAllNodesByKey(t *testing.T) { 58 | nodes := map[string]*node{} 59 | 60 | root := newNode() 61 | n := root 62 | 63 | var accumulatedKey string 64 | 65 | for _, i := range []string{"1", "2", "3"} { 66 | t := newNode() 67 | t.key = "test" + i 68 | t.value = "value" + i 69 | n.appendNode(t) 70 | 71 | n = t 72 | 73 | if len(accumulatedKey) == 0 { 74 | accumulatedKey = i 75 | } else { 76 | accumulatedKey += "." + i 77 | } 78 | 79 | nodes[accumulatedKey] = t 80 | } 81 | 82 | accumulatedKey = "" 83 | n = root 84 | 85 | for _, i := range []string{"4", "5", "6", "3"} { 86 | t := newNode() 87 | t.key = "test" + i 88 | t.value = "value" + i 89 | n.appendNode(t) 90 | 91 | n = t 92 | 93 | if len(accumulatedKey) == 0 { 94 | accumulatedKey = i 95 | } else { 96 | accumulatedKey += "." + i 97 | } 98 | 99 | nodes[accumulatedKey] = t 100 | } 101 | 102 | results := root.findAllNodesByKey("test3", false) 103 | 104 | assert.Equal(t, []*node{nodes["4.5.6.3"], nodes["1.2.3"]}, *results, "Must recurse over tree to find keys") 105 | } 106 | 107 | func TestFindNodeByKeyChain(t *testing.T) { 108 | setTestingEnvsForTree() 109 | 110 | n := createTreeFromDelimiterFilteringByRegexp(regexp.MustCompile("ENVH"), "_") 111 | 112 | node, exists := n.findNodeByKeyChain(&[]string{"ENVH", "TEST1", "TEST5", "TEST6"}) 113 | 114 | assert.True(t, exists, "Must find a node from this key chain") 115 | assert.Equal(t, "test3", node.value, "Must return correct node") 116 | 117 | for _, keyChain := range [][]string{ 118 | {}, 119 | {"ENV"}, 120 | {"ENVH", "TEST1", "TEST7", "TEST8"}, 121 | {"ENVH", "TEST1", "TEST6", "TEST8"}, 122 | } { 123 | _, exists := n.findNodeByKeyChain(&keyChain) 124 | 125 | assert.False(t, exists, "Must not find a node from this key chain") 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Envh [![codecov](https://codecov.io/gh/antham/envh/branch/master/graph/badge.svg)](https://codecov.io/gh/antham/envh) [![Go Report Card](https://goreportcard.com/badge/github.com/antham/envh)](https://goreportcard.com/report/github.com/antham/envh) [![GoDoc](https://godoc.org/github.com/antham/envh?status.svg)](http://godoc.org/github.com/antham/envh) [![GitHub tag](https://img.shields.io/github/tag/antham/envh.svg)]() 2 | 3 | This library is made up of two parts : 4 | 5 | - Env object : it wraps your environments variables in an object and provides convenient helpers. 6 | - Env tree object : it manages environment variables through a tree structure to store a config the same way as in a yaml file or whatever format allows to store a config hierarchically 7 | 8 | ## Install 9 | 10 | go get github.com/antham/envh 11 | 12 | ## How it works 13 | 14 | Check [the godoc](http://godoc.org/github.com/antham/envh), there are many examples provided. 15 | 16 | ## Example with a tree dumped in a config struct 17 | 18 | ```go 19 | package envh 20 | 21 | import ( 22 | "encoding/json" 23 | "fmt" 24 | "os" 25 | "strings" 26 | ) 27 | 28 | type CONFIG2 struct { 29 | DB struct { 30 | USERNAME string 31 | PASSWORD string 32 | HOST string 33 | NAME string 34 | PORT int 35 | URL string 36 | USAGELIMIT float32 37 | } 38 | MAILER struct { 39 | HOST string 40 | USERNAME string 41 | PASSWORD string 42 | ENABLED bool 43 | } 44 | MAP map[string]string 45 | } 46 | 47 | func (c *CONFIG2) Walk(tree *EnvTree, keyChain []string) (bool, error) { 48 | if setter, ok := map[string]func(*EnvTree, []string) error{ 49 | "CONFIG2_DB_URL": c.setURL, 50 | "CONFIG2_MAP": c.setMap, 51 | }[strings.Join(keyChain, "_")]; ok { 52 | return true, setter(tree, keyChain) 53 | } 54 | 55 | return false, nil 56 | } 57 | 58 | func (c *CONFIG2) setMap(tree *EnvTree, keyChain []string) error { 59 | datas := map[string]string{} 60 | 61 | keys, err := tree.FindChildrenKeys(keyChain...) 62 | 63 | if err != nil { 64 | return err 65 | } 66 | 67 | for _, key := range keys { 68 | value, err := tree.FindString(append(keyChain, key)...) 69 | 70 | if err != nil { 71 | return err 72 | } 73 | 74 | datas[key] = value 75 | } 76 | 77 | c.MAP = datas 78 | 79 | return nil 80 | } 81 | 82 | func (c *CONFIG2) setURL(tree *EnvTree, keyChain []string) error { 83 | datas := map[string]string{} 84 | 85 | for _, key := range []string{"USERNAME", "PASSWORD", "HOST", "NAME"} { 86 | value, err := tree.FindString("CONFIG2", "DB", key) 87 | 88 | if err != nil { 89 | return err 90 | } 91 | 92 | datas[key] = value 93 | } 94 | 95 | port, err := tree.FindInt("CONFIG2", "DB", "PORT") 96 | 97 | if err != nil { 98 | return err 99 | } 100 | 101 | c.DB.URL = fmt.Sprintf("jdbc:mysql://%s:%d/%s?user=%s&password=%s", datas["HOST"], port, datas["NAME"], datas["USERNAME"], datas["PASSWORD"]) 102 | 103 | return nil 104 | } 105 | 106 | func ExampleStructWalker_customFieldSet() { 107 | os.Clearenv() 108 | setEnv("CONFIG2_DB_USERNAME", "foo") 109 | setEnv("CONFIG2_DB_PASSWORD", "bar") 110 | setEnv("CONFIG2_DB_HOST", "localhost") 111 | setEnv("CONFIG2_DB_NAME", "my-db") 112 | setEnv("CONFIG2_DB_PORT", "3306") 113 | setEnv("CONFIG2_DB_USAGELIMIT", "95.6") 114 | setEnv("CONFIG2_MAILER_HOST", "127.0.0.1") 115 | setEnv("CONFIG2_MAILER_USERNAME", "foo") 116 | setEnv("CONFIG2_MAILER_PASSWORD", "bar") 117 | setEnv("CONFIG2_MAILER_ENABLED", "true") 118 | setEnv("CONFIG2_MAP_KEY1", "value1") 119 | setEnv("CONFIG2_MAP_KEY2", "value2") 120 | setEnv("CONFIG2_MAP_KEY3", "value3") 121 | 122 | env, err := NewEnvTree("^CONFIG2", "_") 123 | 124 | if err != nil { 125 | return 126 | } 127 | 128 | s := CONFIG2{} 129 | 130 | err = env.PopulateStruct(&s) 131 | 132 | if err != nil { 133 | return 134 | } 135 | 136 | b, err := json.Marshal(s) 137 | 138 | if err != nil { 139 | return 140 | } 141 | 142 | fmt.Println(string(b)) 143 | // Output: 144 | // {"DB":{"USERNAME":"foo","PASSWORD":"bar","HOST":"localhost","NAME":"my-db","PORT":3306,"URL":"jdbc:mysql://localhost:3306/my-db?user=foo\u0026password=bar","USAGELIMIT":95.6},"MAILER":{"HOST":"127.0.0.1","USERNAME":"foo","PASSWORD":"bar","ENABLED":true},"MAP":{"KEY1":"value1","KEY2":"value2","KEY3":"value3"}} 145 | } 146 | ``` 147 | -------------------------------------------------------------------------------- /env_example_test.go: -------------------------------------------------------------------------------- 1 | package envh 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "sort" 7 | ) 8 | 9 | func ExampleEnv_GetAllKeys() { 10 | os.Clearenv() 11 | setEnv("HELLO", "world") 12 | setEnv("FOO", "bar") 13 | 14 | env := NewEnv() 15 | 16 | keys := env.GetAllKeys() 17 | 18 | sort.Strings(keys) 19 | 20 | fmt.Println(keys) 21 | // Output: [FOO HELLO] 22 | } 23 | 24 | func ExampleEnv_GetAllValues() { 25 | os.Clearenv() 26 | setEnv("HELLO", "world") 27 | setEnv("FOO", "bar") 28 | 29 | env := NewEnv() 30 | 31 | values := env.GetAllValues() 32 | 33 | sort.Strings(values) 34 | 35 | fmt.Println(values) 36 | // Output: [bar world] 37 | } 38 | 39 | func ExampleEnv_GetString() { 40 | os.Clearenv() 41 | setEnv("HELLO", "world") 42 | 43 | env := NewEnv() 44 | 45 | fmt.Println(env.GetString("HELLO")) 46 | // Output: world 47 | } 48 | 49 | func ExampleEnv_GetStringUnsecured() { 50 | os.Clearenv() 51 | setEnv("HELLO", "world") 52 | 53 | env := NewEnv() 54 | 55 | fmt.Println(env.GetStringUnsecured("HELLO")) 56 | // Output: world 57 | } 58 | 59 | func ExampleEnv_GetInt() { 60 | os.Clearenv() 61 | setEnv("INT", "1") 62 | setEnv("STRING", "TEST") 63 | 64 | env := NewEnv() 65 | 66 | fmt.Println(env.GetInt("INT")) 67 | fmt.Println(env.GetInt("STRING")) 68 | 69 | // Output: 70 | // 1 71 | // 0 Value "TEST" can't be converted to type "int" 72 | } 73 | 74 | func ExampleEnv_GetIntUnsecured() { 75 | os.Clearenv() 76 | setEnv("INT", "1") 77 | setEnv("STRING", "TEST") 78 | 79 | env := NewEnv() 80 | 81 | fmt.Println(env.GetIntUnsecured("INT")) 82 | fmt.Println(env.GetIntUnsecured("STRING")) 83 | 84 | // Output: 85 | // 1 86 | // 0 87 | } 88 | 89 | func ExampleEnv_GetFloat() { 90 | os.Clearenv() 91 | setEnv("FLOAT", "1.1") 92 | setEnv("STRING", "TEST") 93 | 94 | env := NewEnv() 95 | 96 | f, err := env.GetFloat("FLOAT") 97 | 98 | fmt.Printf("%0.1f ", f) 99 | fmt.Println(err) 100 | fmt.Println(env.GetFloat("STRING")) 101 | 102 | // Output: 103 | // 1.1 104 | // 0 Value "TEST" can't be converted to type "float" 105 | } 106 | 107 | func ExampleEnv_GetFloatUnsecured() { 108 | os.Clearenv() 109 | setEnv("FLOAT", "1.1") 110 | setEnv("STRING", "TEST") 111 | 112 | env := NewEnv() 113 | 114 | fmt.Printf("%0.1f\n", env.GetFloatUnsecured("FLOAT")) 115 | fmt.Println(env.GetFloatUnsecured("STRING")) 116 | 117 | // Output: 118 | // 1.1 119 | // 0 120 | } 121 | 122 | func ExampleEnv_GetBool() { 123 | os.Clearenv() 124 | setEnv("BOOL", "true") 125 | setEnv("STRING", "TEST") 126 | 127 | env := NewEnv() 128 | 129 | fmt.Println(env.GetBool("BOOL")) 130 | fmt.Println(env.GetBool("STRING")) 131 | 132 | // Output: 133 | // true 134 | // false Value "TEST" can't be converted to type "bool" 135 | } 136 | 137 | func ExampleEnv_GetBoolUnsecured() { 138 | os.Clearenv() 139 | setEnv("BOOL", "true") 140 | setEnv("STRING", "TEST") 141 | 142 | env := NewEnv() 143 | 144 | fmt.Println(env.GetBoolUnsecured("BOOL")) 145 | fmt.Println(env.GetBoolUnsecured("STRING")) 146 | 147 | // Output: 148 | // true 149 | // false 150 | } 151 | 152 | func ExampleEnv_FindEntries() { 153 | os.Clearenv() 154 | setEnv("API_USERNAME", "user") 155 | setEnv("API_PASSWORD", "password") 156 | setEnv("DB_USERNAME", "user") 157 | setEnv("DB_PASSWORD", "user") 158 | 159 | env := NewEnv() 160 | 161 | entries, err := env.FindEntries("API.*") 162 | 163 | fmt.Printf("API -> PASSWORD = %s, API -> USERNAME = %s ", entries["API_PASSWORD"], entries["API_PASSWORD"]) 164 | fmt.Println(err) 165 | fmt.Println(env.FindEntries("*")) 166 | 167 | // Output: 168 | // API -> PASSWORD = password, API -> USERNAME = password 169 | // map[] error parsing regexp: missing argument to repetition operator: `*` 170 | } 171 | 172 | func ExampleEnv_FindEntriesUnsecured() { 173 | os.Clearenv() 174 | setEnv("API_USERNAME", "user") 175 | setEnv("API_PASSWORD", "password") 176 | setEnv("DB_USERNAME", "user") 177 | setEnv("DB_PASSWORD", "user") 178 | 179 | env := NewEnv() 180 | 181 | entries := env.FindEntriesUnsecured("API.*") 182 | 183 | fmt.Printf("API -> PASSWORD = %s, API -> USERNAME = %s\n", entries["API_PASSWORD"], entries["API_PASSWORD"]) 184 | fmt.Println(env.FindEntriesUnsecured("*")) 185 | 186 | // Output: 187 | // API -> PASSWORD = password, API -> USERNAME = password 188 | // map[] 189 | } 190 | -------------------------------------------------------------------------------- /struct.go: -------------------------------------------------------------------------------- 1 | package envh 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // StructWalker must be implemented, when using PopulateStruct* functions, 8 | // to be able to set a value for a custom field with an unsupported field (a map for instance), 9 | // to add transformation before setting a field or for custom validation purpose. 10 | // Walk function is called when struct is populated for every struct field a matching is made with 11 | // an EnvTree node. Two parameters are given : tree represents whole parsed tree and keyChain is path leading to the node in tree. 12 | // Returning true as first parameter will bypass walking process and false not, so it's 13 | // possible to completely control how some part of a structure are defined and it's possible as well 14 | // only to add some checking and let regular process do its job. 15 | type StructWalker interface { 16 | Walk(tree *EnvTree, keyChain []string) (bypassWalkingProcess bool, err error) 17 | } 18 | 19 | type entry struct { 20 | typ reflect.Type 21 | value reflect.Value 22 | chain []string 23 | } 24 | 25 | func populateInt(forceDefinition bool, tree *EnvTree, val reflect.Value, keyChain []string) error { 26 | v, err := tree.FindInt(keyChain...) 27 | 28 | if forceDefinition && err != nil { 29 | return err 30 | } 31 | 32 | if _, ok := err.(WrongTypeError); ok { 33 | return err 34 | } 35 | 36 | val.SetInt(int64(v)) 37 | 38 | return nil 39 | } 40 | 41 | func populateFloat(forceDefinition bool, tree *EnvTree, val reflect.Value, keyChain []string) error { 42 | v, err := tree.FindFloat(keyChain...) 43 | 44 | if forceDefinition && err != nil { 45 | return err 46 | } 47 | 48 | if _, ok := err.(WrongTypeError); ok { 49 | return err 50 | } 51 | 52 | val.SetFloat(float64(v)) 53 | 54 | return nil 55 | } 56 | 57 | func populateString(forceDefinition bool, tree *EnvTree, val reflect.Value, keyChain []string) error { 58 | v, err := tree.FindString(keyChain...) 59 | 60 | if forceDefinition && err != nil { 61 | return err 62 | } 63 | 64 | val.SetString(v) 65 | 66 | return nil 67 | } 68 | 69 | func populateBool(forceDefinition bool, tree *EnvTree, val reflect.Value, keyChain []string) error { 70 | v, err := tree.FindBool(keyChain...) 71 | 72 | if forceDefinition && err != nil { 73 | return err 74 | } 75 | 76 | if _, ok := err.(WrongTypeError); ok { 77 | return err 78 | } 79 | 80 | val.SetBool(v) 81 | 82 | return nil 83 | } 84 | 85 | func populateRegularType(entries *[]entry, tree *EnvTree, val reflect.Value, valKeyChain []string, forceDefinition bool) error { 86 | switch val.Type().Kind() { 87 | case reflect.Struct: 88 | *entries = append(*entries, entry{val.Type(), val, valKeyChain}) 89 | 90 | return nil 91 | case reflect.Int: 92 | return populateInt(forceDefinition, tree, val, valKeyChain) 93 | case reflect.Float32: 94 | return populateFloat(forceDefinition, tree, val, valKeyChain) 95 | case reflect.String: 96 | return populateString(forceDefinition, tree, val, valKeyChain) 97 | case reflect.Bool: 98 | return populateBool(forceDefinition, tree, val, valKeyChain) 99 | default: 100 | return TypeUnsupported{val.Type().Kind().String(), "int32, float32, string, boolean or struct"} 101 | } 102 | } 103 | 104 | func callStructMethodWalk(origStruct interface{}, tree *EnvTree, keyChain []string) (bool, error) { 105 | if walker, ok := origStruct.(StructWalker); ok { 106 | return walker.Walk(tree, keyChain) 107 | } 108 | 109 | return false, nil 110 | } 111 | 112 | func populateStruct(entries *[]entry, origStruct interface{}, tree *EnvTree, forceDefinition bool) error { 113 | var err error 114 | var ok bool 115 | var val reflect.Value 116 | var valKeyChain []string 117 | 118 | typ := (*entries)[0].typ 119 | value := (*entries)[0].value 120 | chain := (*entries)[0].chain 121 | 122 | (*entries) = append([]entry{}, (*entries)[1:]...) 123 | 124 | for i := 0; i < typ.NumField(); i++ { 125 | val = value.Field(i) 126 | valKeyChain = append([]string{}, append(chain, typ.Field(i).Name)...) 127 | 128 | ok, err = callStructMethodWalk(origStruct, tree, valKeyChain) 129 | 130 | if err != nil { 131 | return err 132 | } 133 | 134 | if ok { 135 | continue 136 | } 137 | 138 | if err = populateRegularType(entries, tree, val, valKeyChain, forceDefinition); err != nil { 139 | return err 140 | } 141 | } 142 | 143 | return nil 144 | } 145 | 146 | func isPointerToStruct(data interface{}) bool { 147 | return !(reflect.TypeOf(data).Kind() != reflect.Ptr || reflect.TypeOf(data).Elem().Kind() != reflect.Struct) 148 | } 149 | 150 | func populateStructFromEnvTree(origStruct interface{}, tree *EnvTree, forceDefinition bool) error { 151 | if !isPointerToStruct(origStruct) { 152 | return TypeUnsupported{reflect.TypeOf(origStruct).Kind().String(), "pointer to struct"} 153 | } 154 | 155 | entries := []entry{{reflect.TypeOf(origStruct).Elem(), reflect.ValueOf(origStruct).Elem(), []string{reflect.TypeOf(origStruct).Elem().Name()}}} 156 | 157 | for { 158 | err := populateStruct(&entries, origStruct, tree, forceDefinition) 159 | 160 | if err != nil { 161 | return err 162 | } 163 | 164 | if len(entries) == 0 { 165 | return nil 166 | } 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /env.go: -------------------------------------------------------------------------------- 1 | // Package envh provides convenient helpers to manage easily your environment variables. 2 | package envh 3 | 4 | import ( 5 | "regexp" 6 | ) 7 | 8 | // Env manages environment variables 9 | // by giving a convenient helper 10 | // to interact with them 11 | type Env struct { 12 | envs *map[string]string 13 | } 14 | 15 | // NewEnv creates a new Env instance 16 | func NewEnv() Env { 17 | return Env{parseVars()} 18 | } 19 | 20 | // GetAllValues retrieves a slice of all environment variables values 21 | func (e Env) GetAllValues() []string { 22 | results := []string{} 23 | 24 | for _, v := range *e.envs { 25 | results = append(results, v) 26 | } 27 | 28 | return results 29 | } 30 | 31 | // GetAllKeys retrieves a slice of all environment variables keys 32 | func (e Env) GetAllKeys() []string { 33 | results := []string{} 34 | 35 | for k := range *e.envs { 36 | results = append(results, k) 37 | } 38 | 39 | return results 40 | } 41 | 42 | // GetString returns a string if variable exists 43 | // or an error otherwise 44 | func (e Env) GetString(key string) (string, error) { 45 | return getString(func() (string, bool) { 46 | v, ok := (*e.envs)[key] 47 | 48 | return v, ok 49 | }) 50 | } 51 | 52 | // GetStringUnsecured is insecured version of GetString to avoid the burden 53 | // of rechecking errors if it was done already. If any errors occurred cause 54 | // the variable is missing, it returns default zero string value. 55 | // This function has to be used carefully 56 | func (e Env) GetStringUnsecured(key string) string { 57 | if val, err := getString(func() (string, bool) { 58 | v, ok := (*e.envs)[key] 59 | 60 | return v, ok 61 | }); err == nil { 62 | return val 63 | } 64 | 65 | return "" 66 | } 67 | 68 | // GetInt returns an integer if variable exists 69 | // or an error if value is not an integer or doesn't exist 70 | func (e Env) GetInt(key string) (int, error) { 71 | return getInt(func() (string, bool) { 72 | v, ok := (*e.envs)[key] 73 | 74 | return v, ok 75 | }) 76 | } 77 | 78 | // GetIntUnsecured is insecured version of GetInt to avoid the burden 79 | // of rechecking errors if it was done already. If any errors occurred cause 80 | // the variable is missing or not an int value, it returns default zero int value. 81 | // This function has to be used carefully 82 | func (e Env) GetIntUnsecured(key string) int { 83 | if val, err := getInt(func() (string, bool) { 84 | v, ok := (*e.envs)[key] 85 | 86 | return v, ok 87 | }); err == nil { 88 | return val 89 | } 90 | 91 | return 0 92 | } 93 | 94 | // GetFloat returns a float if variable exists 95 | // or an error if value is not a float or doesn't exist 96 | func (e Env) GetFloat(key string) (float32, error) { 97 | return getFloat(func() (string, bool) { 98 | v, ok := (*e.envs)[key] 99 | 100 | return v, ok 101 | }) 102 | } 103 | 104 | // GetFloatUnsecured is insecured version of GetFloat to avoid the burden 105 | // of rechecking errors if it was done already. If any errors occurred cause 106 | // the variable is missing or not a floating value, it returns default zero floating value. 107 | // This function has to be used carefully 108 | func (e Env) GetFloatUnsecured(key string) float32 { 109 | if val, err := getFloat(func() (string, bool) { 110 | v, ok := (*e.envs)[key] 111 | 112 | return v, ok 113 | }); err == nil { 114 | return val 115 | } 116 | 117 | return 0 118 | } 119 | 120 | // GetBool returns a boolean if variable exists 121 | // or an error if value is not a boolean or doesn't exist 122 | func (e Env) GetBool(key string) (bool, error) { 123 | return getBool(func() (string, bool) { 124 | v, ok := (*e.envs)[key] 125 | 126 | return v, ok 127 | }) 128 | } 129 | 130 | // GetBoolUnsecured is insecured version of GetBool to avoid the burden 131 | // of rechecking errors if it was done already. If any errors occurred cause 132 | // the variable is missing or not a boolean value, it returns default zero boolean value. 133 | // This function has to be used carefully 134 | func (e Env) GetBoolUnsecured(key string) bool { 135 | if val, err := getBool(func() (string, bool) { 136 | v, ok := (*e.envs)[key] 137 | 138 | return v, ok 139 | }); err == nil { 140 | return val 141 | } 142 | 143 | return false 144 | } 145 | 146 | // FindEntries retrieves all keys matching a given regexp and their 147 | // corresponding values 148 | func (e Env) FindEntries(reg string) (map[string]string, error) { 149 | results := map[string]string{} 150 | 151 | r, err := regexp.Compile(reg) 152 | 153 | if err != nil { 154 | return results, err 155 | } 156 | 157 | for k, v := range *e.envs { 158 | if r.MatchString(k) { 159 | results[k] = v 160 | } 161 | } 162 | 163 | return results, nil 164 | } 165 | 166 | // FindEntriesUnsecured is insecured version of FindEntriesUnsecured to avoid the burden 167 | // of rechecking errors if it was done already. If any errors occurred cause 168 | // the variable is missing or not a boolean value, it returns default empty map. 169 | // This function has to be used carefully. 170 | func (e Env) FindEntriesUnsecured(reg string) map[string]string { 171 | results := map[string]string{} 172 | 173 | r, err := regexp.Compile(reg) 174 | 175 | if err != nil { 176 | return results 177 | } 178 | 179 | for k, v := range *e.envs { 180 | if r.MatchString(k) { 181 | results[k] = v 182 | } 183 | } 184 | 185 | return results 186 | } 187 | -------------------------------------------------------------------------------- /env_test.go: -------------------------------------------------------------------------------- 1 | package envh 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/sirupsen/logrus" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestNewEnvQuery(t *testing.T) { 12 | setTestingEnvs() 13 | result := NewEnv() 14 | 15 | assert.Equal(t, "test1", (*result.envs)["TEST1"], "Must extract and parse environment variables") 16 | assert.Contains(t, "=test2=", (*result.envs)["TEST2"], "Must extract and parse environment variables") 17 | } 18 | 19 | func TestGetAllValues(t *testing.T) { 20 | setTestingEnvs() 21 | 22 | q := NewEnv() 23 | 24 | keys := q.GetAllValues() 25 | 26 | results := []string{} 27 | 28 | for _, v := range keys { 29 | if v == "test1" || v == "=test2=" { 30 | results = append(results, v) 31 | } 32 | } 33 | 34 | assert.Len(t, results, 2, "Must contains 2 elements") 35 | } 36 | 37 | func TestGetAllKeys(t *testing.T) { 38 | setTestingEnvs() 39 | 40 | q := NewEnv() 41 | 42 | keys := q.GetAllKeys() 43 | 44 | results := []string{} 45 | 46 | for _, k := range keys { 47 | if k == "TEST1" || k == "TEST2" { 48 | results = append(results, k) 49 | } 50 | } 51 | 52 | assert.Len(t, results, 2, "Must contains 2 elements") 53 | } 54 | 55 | func TestFindEntries(t *testing.T) { 56 | setTestingEnvs() 57 | 58 | q := NewEnv() 59 | 60 | keys, err := q.FindEntries(".*TEST\\d.*") 61 | 62 | assert.NoError(t, err, "Must return no errors") 63 | assert.Len(t, keys, 2, "Must contains 2 elements") 64 | assert.Equal(t, "test1", keys["TEST1"], "Must have env key and value") 65 | assert.Equal(t, "=test2=", keys["TEST2"], "Must have env key and value") 66 | 67 | _, err = q.FindEntries("?") 68 | 69 | assert.EqualError(t, err, "error parsing regexp: missing argument to repetition operator: `?`", "Must return an error when regexp is unvalid") 70 | } 71 | 72 | func TestFindEntriesUnsecured(t *testing.T) { 73 | setTestingEnvs() 74 | 75 | q := NewEnv() 76 | 77 | keys := q.FindEntriesUnsecured(".*TEST\\d.*") 78 | 79 | assert.Len(t, keys, 2, "Must contains 2 elements") 80 | assert.Equal(t, "test1", keys["TEST1"], "Must have env key and value") 81 | assert.Equal(t, "=test2=", keys["TEST2"], "Must have env key and value") 82 | 83 | keys = q.FindEntriesUnsecured("?") 84 | assert.Len(t, keys, 0, "Must contains 0 elements") 85 | } 86 | 87 | func TestGetString(t *testing.T) { 88 | setTestingEnvs() 89 | 90 | q := NewEnv() 91 | 92 | value, err := q.GetString("TEST1") 93 | 94 | assert.NoError(t, err, "Must return no errors") 95 | assert.Equal(t, "test1", value, "Must return value") 96 | 97 | value, err = q.GetString("TEST100") 98 | 99 | assert.EqualError(t, err, "Variable not found", "Must return an error when variable can't be found") 100 | assert.Equal(t, "", value, "Must return empty string") 101 | } 102 | 103 | func TestGetStringUnsecured(t *testing.T) { 104 | setTestingEnvs() 105 | 106 | q := NewEnv() 107 | 108 | value := q.GetStringUnsecured("TEST1") 109 | 110 | assert.Equal(t, "test1", value, "Must return value") 111 | 112 | value = q.GetStringUnsecured("TEST100") 113 | 114 | assert.Equal(t, "", value, "Must return empty string") 115 | } 116 | 117 | func TestGetInt(t *testing.T) { 118 | err := os.Setenv("TEST3", "1") 119 | 120 | if err != nil { 121 | logrus.Fatal(err) 122 | } 123 | 124 | q := NewEnv() 125 | 126 | value, err := q.GetInt("TEST3") 127 | 128 | assert.NoError(t, err, "Must return no errors") 129 | assert.Equal(t, 1, value, "Must return value") 130 | 131 | value, err = q.GetInt("TEST100") 132 | 133 | assert.EqualError(t, err, "Variable not found", "Must return an error when variable can't be found") 134 | assert.Equal(t, 0, value, "Must return value") 135 | 136 | value, err = q.GetInt("TEST1") 137 | 138 | assert.EqualError(t, err, `Value "test1" can't be converted to type "int"`, "Must return an error when variable can't be found") 139 | assert.Equal(t, 0, value, "Must return empty string") 140 | } 141 | 142 | func TestGetIntUnsecured(t *testing.T) { 143 | err := os.Setenv("TEST3", "1") 144 | 145 | if err != nil { 146 | logrus.Fatal(err) 147 | } 148 | 149 | q := NewEnv() 150 | 151 | value := q.GetIntUnsecured("TEST3") 152 | 153 | assert.Equal(t, 1, value, "Must return value") 154 | 155 | value = q.GetIntUnsecured("TEST100") 156 | 157 | assert.Equal(t, 0, value, "Must return value") 158 | 159 | value = q.GetIntUnsecured("TEST1") 160 | 161 | assert.Equal(t, 0, value, "Must return empty string") 162 | } 163 | 164 | func TestGetBool(t *testing.T) { 165 | err := os.Setenv("TEST4", "true") 166 | 167 | if err != nil { 168 | logrus.Fatal(err) 169 | } 170 | 171 | q := NewEnv() 172 | 173 | value, err := q.GetBool("TEST4") 174 | 175 | assert.NoError(t, err, "Must return no errors") 176 | assert.Equal(t, true, value, "Must return value") 177 | 178 | value, err = q.GetBool("TEST100") 179 | 180 | assert.EqualError(t, err, "Variable not found", "Must return an error when variable can't be found") 181 | assert.Equal(t, false, value, "Must return value") 182 | 183 | value, err = q.GetBool("TEST1") 184 | 185 | assert.EqualError(t, err, `Value "test1" can't be converted to type "bool"`, "Must return an error when variable can't be found") 186 | assert.Equal(t, false, value, "Must return empty string") 187 | } 188 | 189 | func TestGetBoolUnsecured(t *testing.T) { 190 | err := os.Setenv("TEST4", "true") 191 | 192 | if err != nil { 193 | logrus.Fatal(err) 194 | } 195 | 196 | q := NewEnv() 197 | 198 | value := q.GetBoolUnsecured("TEST4") 199 | 200 | assert.Equal(t, true, value, "Must return value") 201 | 202 | value = q.GetBoolUnsecured("TEST100") 203 | 204 | assert.Equal(t, false, value, "Must return value") 205 | 206 | value = q.GetBoolUnsecured("TEST1") 207 | 208 | assert.Equal(t, false, value, "Must return empty string") 209 | } 210 | 211 | func TestGetFloat(t *testing.T) { 212 | err := os.Setenv("TEST5", "0.01") 213 | 214 | if err != nil { 215 | logrus.Fatal(err) 216 | } 217 | 218 | q := NewEnv() 219 | 220 | value, err := q.GetFloat("TEST5") 221 | 222 | assert.NoError(t, err, "Must return no errors") 223 | assert.Equal(t, float32(0.01), value, "Must return value") 224 | 225 | value, err = q.GetFloat("TEST100") 226 | 227 | assert.EqualError(t, err, "Variable not found", "Must return an error when variable can't be found") 228 | assert.Equal(t, float32(0), value, "Must return value") 229 | 230 | value, err = q.GetFloat("TEST1") 231 | 232 | assert.EqualError(t, err, `Value "test1" can't be converted to type "float"`, "Must return an error when variable can't be found") 233 | assert.Equal(t, float32(0), value, "Must return empty string") 234 | } 235 | 236 | func TestGetFloatUnsecured(t *testing.T) { 237 | err := os.Setenv("TEST5", "0.01") 238 | 239 | if err != nil { 240 | logrus.Fatal(err) 241 | } 242 | 243 | q := NewEnv() 244 | 245 | value := q.GetFloatUnsecured("TEST5") 246 | 247 | assert.Equal(t, float32(0.01), value, "Must return value") 248 | 249 | value = q.GetFloatUnsecured("TEST100") 250 | 251 | assert.Equal(t, float32(0), value, "Must return value") 252 | 253 | value = q.GetFloatUnsecured("TEST1") 254 | 255 | assert.Equal(t, float32(0), value, "Must return empty string") 256 | } 257 | -------------------------------------------------------------------------------- /struct_test.go: -------------------------------------------------------------------------------- 1 | package envh 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/stretchr/testify/assert" 8 | "testing" 9 | ) 10 | 11 | func TestPopulateStructWithNoPointersGiven(t *testing.T) { 12 | type POPULATESTRUCT struct{} 13 | 14 | tree, err := NewEnvTree("test", "_") 15 | 16 | assert.NoError(t, err, "Must return no errors") 17 | 18 | err = populateStructFromEnvTree(POPULATESTRUCT{}, &tree, false) 19 | 20 | assert.EqualError(t, err, `Type "struct" is not supported : you must provide "pointer to struct"`) 21 | 22 | err = populateStructFromEnvTree(8, &tree, false) 23 | 24 | assert.EqualError(t, err, `Type "int" is not supported : you must provide "pointer to struct"`) 25 | } 26 | 27 | func TestPopulateStructWithInnerPointer(t *testing.T) { 28 | type TEST7 struct { 29 | TEST8 int 30 | TEST9 float32 31 | TEST10 string 32 | TEST11 bool 33 | } 34 | 35 | type TEST4 struct { 36 | TEST5 *string 37 | TEST6 TEST7 38 | } 39 | 40 | type POPULATESTRUCT struct { 41 | TEST2 string 42 | TEST3 TEST4 43 | } 44 | 45 | actual := POPULATESTRUCT{} 46 | 47 | tree, err := NewEnvTree("test", "_") 48 | 49 | assert.NoError(t, err) 50 | 51 | err = populateStructFromEnvTree(&actual, &tree, false) 52 | 53 | restoreEnvs() 54 | 55 | assert.EqualError(t, err, `Type "ptr" is not supported : you must provide "int32, float32, string, boolean or struct"`) 56 | } 57 | 58 | func TestPopulateStructWithTypeErrors(t *testing.T) { 59 | type TEST5 struct { 60 | TEST6 int 61 | } 62 | 63 | type POPULATESTRUCT struct { 64 | TEST1 float32 65 | TEST2 int 66 | TEST3 bool 67 | TEST4 TEST5 68 | } 69 | 70 | type g struct { 71 | init func() 72 | checkError func(err error) 73 | } 74 | 75 | tests := []g{ 76 | { 77 | init: func() { 78 | setEnv("POPULATESTRUCT_TEST1", "value1") 79 | }, 80 | checkError: func(err error) { 81 | assert.EqualError(t, err, `Value "value1" can't be converted to type "float"`) 82 | }, 83 | }, 84 | { 85 | init: func() { 86 | setEnv("POPULATESTRUCT_TEST2", "value2") 87 | }, 88 | checkError: func(err error) { 89 | assert.EqualError(t, err, `Value "value2" can't be converted to type "int"`) 90 | }, 91 | }, 92 | { 93 | init: func() { 94 | setEnv("POPULATESTRUCT_TEST3", "value3") 95 | }, 96 | checkError: func(err error) { 97 | assert.EqualError(t, err, `Value "value3" can't be converted to type "bool"`) 98 | }, 99 | }, 100 | { 101 | init: func() { 102 | setEnv("POPULATESTRUCT_TEST4_TEST6", "value4") 103 | }, 104 | checkError: func(err error) { 105 | assert.EqualError(t, err, `Value "value4" can't be converted to type "int"`) 106 | }, 107 | }, 108 | } 109 | 110 | for _, s := range tests { 111 | actual := POPULATESTRUCT{} 112 | 113 | s.init() 114 | 115 | tree, err := NewEnvTree("POPULATESTRUCT", "_") 116 | 117 | assert.NoError(t, err) 118 | 119 | err = populateStructFromEnvTree(&actual, &tree, false) 120 | s.checkError(err) 121 | restoreEnvs() 122 | } 123 | } 124 | 125 | func TestPopulateStructWithStrictCheckDisabled(t *testing.T) { 126 | type TEST7 struct { 127 | TEST8 int 128 | TEST9 float32 129 | TEST10 string 130 | TEST11 bool 131 | } 132 | 133 | type TEST4 struct { 134 | TEST5 string 135 | TEST6 TEST7 136 | } 137 | 138 | type POPULATESTRUCT struct { 139 | TEST2 string 140 | TEST3 TEST4 141 | } 142 | 143 | actual := POPULATESTRUCT{} 144 | 145 | setEnv("POPULATESTRUCT_TEST2", "value1") 146 | setEnv("POPULATESTRUCT_TEST3_TEST6_TEST8", "1") 147 | setEnv("POPULATESTRUCT_TEST3_TEST6_TEST9", "1.1") 148 | setEnv("POPULATESTRUCT_TEST3_TEST6_TEST10", "value test 10") 149 | setEnv("POPULATESTRUCT_TEST3_TEST6_TEST11", "true") 150 | 151 | tree, err := NewEnvTree("POPULATESTRUCT", "_") 152 | 153 | assert.NoError(t, err) 154 | 155 | err = populateStructFromEnvTree(&actual, &tree, false) 156 | 157 | restoreEnvs() 158 | 159 | expected := POPULATESTRUCT{ 160 | "value1", 161 | TEST4{ 162 | "", 163 | TEST7{ 164 | 1, 165 | 1.1, 166 | "value test 10", 167 | true, 168 | }, 169 | }, 170 | } 171 | 172 | assert.NoError(t, err) 173 | assert.Equal(t, expected, actual, "Must popuplate struct with value given by environment variables") 174 | } 175 | 176 | func TestPopulateStructWithStrictCheckEnabled(t *testing.T) { 177 | type POPULATESTRUCT struct { 178 | TEST8 int 179 | TEST9 float32 180 | TEST10 string 181 | TEST11 bool 182 | } 183 | 184 | type g struct { 185 | init func() 186 | checkError func(err error) 187 | } 188 | 189 | tests := []g{ 190 | { 191 | init: func() { 192 | }, 193 | checkError: func(err error) { 194 | assert.EqualError(t, err, `Variable not found`) 195 | }, 196 | }, 197 | { 198 | init: func() { 199 | setEnv("POPULATESTRUCT_TEST8", "1") 200 | }, 201 | checkError: func(err error) { 202 | assert.EqualError(t, err, `Variable not found`) 203 | }, 204 | }, 205 | { 206 | init: func() { 207 | setEnv("POPULATESTRUCT_TEST8", "1") 208 | setEnv("POPULATESTRUCT_TEST9", "1.1") 209 | }, 210 | checkError: func(err error) { 211 | assert.EqualError(t, err, `Variable not found`) 212 | }, 213 | }, 214 | { 215 | init: func() { 216 | setEnv("POPULATESTRUCT_TEST8", "1") 217 | setEnv("POPULATESTRUCT_TEST9", "1.1") 218 | setEnv("POPULATESTRUCT_TEST10", "test") 219 | }, 220 | checkError: func(err error) { 221 | assert.EqualError(t, err, `Variable not found`) 222 | }, 223 | }, 224 | } 225 | 226 | for _, s := range tests { 227 | actual := POPULATESTRUCT{} 228 | 229 | s.init() 230 | 231 | tree, err := NewEnvTree("POPULATESTRUCT", "_") 232 | 233 | assert.NoError(t, err) 234 | 235 | err = populateStructFromEnvTree(&actual, &tree, true) 236 | s.checkError(err) 237 | restoreEnvs() 238 | } 239 | } 240 | 241 | type SUM struct { 242 | LEFTOPERAND int 243 | RIGHTOPERAND int 244 | RESULT int 245 | } 246 | 247 | func (s *SUM) Walk(tree *EnvTree, keyChain []string) (bool, error) { 248 | if iterator, ok := map[string]func(*EnvTree, []string) (bool, error){ 249 | "SUM_LEFTOPERAND": s.validateLeftOperand, 250 | "SUM_RESULT": s.setResult, 251 | }[strings.Join(keyChain, "_")]; ok { 252 | return iterator(tree, keyChain) 253 | } 254 | 255 | return false, nil 256 | 257 | } 258 | 259 | func (s *SUM) setResult(tree *EnvTree, keyChain []string) (bool, error) { 260 | left, err := tree.FindInt("SUM", "LEFTOPERAND") 261 | 262 | if err != nil { 263 | return true, fmt.Errorf(`Can't find "SUM_LEFTOPERAND"`) 264 | } 265 | 266 | right, err := tree.FindInt("SUM", "RIGHTOPERAND") 267 | 268 | if err != nil { 269 | return true, fmt.Errorf(`Can't find "SUM_LEFTOPERAND"`) 270 | } 271 | 272 | s.RESULT = left + right 273 | 274 | return true, nil 275 | } 276 | 277 | func (s *SUM) validateLeftOperand(tree *EnvTree, keyChain []string) (bool, error) { 278 | val, err := tree.FindInt(keyChain...) 279 | 280 | if err != nil { 281 | return false, fmt.Errorf(`"SUM_LEFTOPERAND" can't be found`) 282 | } 283 | 284 | if val <= 0 { 285 | return false, fmt.Errorf(`"LEFTOPERAND" must be greater than 0`) 286 | } 287 | 288 | return false, nil 289 | } 290 | 291 | func TestPopulateStructWithCustomSet(t *testing.T) { 292 | setEnv("SUM_LEFTOPERAND", "1") 293 | setEnv("SUM_RIGHTOPERAND", "2") 294 | 295 | actual := SUM{} 296 | 297 | tree, err := NewEnvTree("SUM", "_") 298 | 299 | assert.NoError(t, err) 300 | 301 | err = populateStructFromEnvTree(&actual, &tree, false) 302 | 303 | assert.NoError(t, err) 304 | 305 | expected := SUM{LEFTOPERAND: 1, RIGHTOPERAND: 2, RESULT: 3} 306 | 307 | assert.Equal(t, expected, actual, "Must set result field") 308 | 309 | restoreEnvs() 310 | } 311 | 312 | func TestPopulateStructWithCustomSetTriggeringAnError(t *testing.T) { 313 | setEnv("SUM_LEFTOPERAND", "2") 314 | 315 | actual := SUM{} 316 | 317 | tree, err := NewEnvTree("SUM", "_") 318 | 319 | assert.NoError(t, err) 320 | 321 | err = populateStructFromEnvTree(&actual, &tree, false) 322 | 323 | assert.EqualError(t, err, `Can't find "SUM_LEFTOPERAND"`, "Must bubble up an error from Populate function") 324 | 325 | restoreEnvs() 326 | } 327 | 328 | func TestPopulateStructWithCustomValidationTriggeringAnError(t *testing.T) { 329 | setEnv("SUM_LEFTOPERAND", "0") 330 | setEnv("SUM_RIGHTOPERAND", "2") 331 | 332 | actual := SUM{} 333 | 334 | tree, err := NewEnvTree("SUM", "_") 335 | 336 | assert.NoError(t, err) 337 | 338 | err = populateStructFromEnvTree(&actual, &tree, false) 339 | 340 | assert.EqualError(t, err, `"LEFTOPERAND" must be greater than 0`, "Must validate data") 341 | 342 | restoreEnvs() 343 | } 344 | 345 | type FIXREFERENCEMESSUP struct { 346 | TEST1 struct { 347 | TEST2 struct { 348 | TEST3 struct { 349 | TEST4 string 350 | TEST5 string 351 | TEST6 string 352 | } 353 | TEST7 map[string]string 354 | } 355 | TEST8 map[string]map[string]string 356 | } 357 | } 358 | 359 | func (s *FIXREFERENCEMESSUP) Walk(tree *EnvTree, keyChain []string) (bool, error) { 360 | if iterator, ok := map[string]func(*EnvTree, []string) (bool, error){ 361 | "FIXREFERENCEMESSUP_TEST1_TEST2_TEST7": s.fillMap, 362 | "FIXREFERENCEMESSUP_TEST1_TEST8": func(*EnvTree, []string) (bool, error) { return true, nil }, 363 | }[strings.Join(keyChain, "_")]; ok { 364 | return iterator(tree, keyChain) 365 | } 366 | 367 | return false, nil 368 | } 369 | 370 | func (s *FIXREFERENCEMESSUP) fillMap(tree *EnvTree, keyChain []string) (bool, error) { 371 | s.TEST1.TEST2.TEST7 = map[string]string{} 372 | s.TEST1.TEST2.TEST7["hello"] = "world!" 373 | return true, nil 374 | } 375 | 376 | func TestFixReferenceMessUp(t *testing.T) { 377 | 378 | actual := FIXREFERENCEMESSUP{} 379 | 380 | setEnv("FIXREFERENCEMESSUP_TEST1_TEST2_TEST3_TEST4", "4") 381 | setEnv("FIXREFERENCEMESSUP_TEST1_TEST2_TEST3_TEST5", "5") 382 | setEnv("FIXREFERENCEMESSUP_TEST1_TEST2_TEST3_TEST6", "6") 383 | setEnv("FIXREFERENCEMESSUP_TEST1_TEST2_TEST7_HELLO", "world!") 384 | setEnv("FIXREFERENCEMESSUP_TEST1_TEST8_TEST", "test") 385 | 386 | tree, err := NewEnvTree("FIXREFERENCEMESSUP", "_") 387 | 388 | assert.NoError(t, err) 389 | 390 | err = populateStructFromEnvTree(&actual, &tree, false) 391 | 392 | assert.NoError(t, err) 393 | 394 | expected := FIXREFERENCEMESSUP{} 395 | expected.TEST1.TEST2.TEST3.TEST4 = "4" 396 | expected.TEST1.TEST2.TEST3.TEST5 = "5" 397 | expected.TEST1.TEST2.TEST3.TEST6 = "6" 398 | expected.TEST1.TEST2.TEST7 = map[string]string{} 399 | expected.TEST1.TEST2.TEST7["hello"] = "world!" 400 | 401 | assert.Equal(t, expected, actual) 402 | } 403 | -------------------------------------------------------------------------------- /env_tree.go: -------------------------------------------------------------------------------- 1 | package envh 2 | 3 | import ( 4 | "regexp" 5 | "strings" 6 | ) 7 | 8 | // EnvTree manages environment variables through a tree structure 9 | // to store a config the same way as in a yaml file or whatever 10 | // format allows to store a config hierarchically 11 | type EnvTree struct { 12 | root *node 13 | } 14 | 15 | // NewEnvTree creates an environment variable tree. 16 | // A delimiter is used to split key, reg is a regexp 17 | // used to filter entries 18 | func NewEnvTree(reg string, delimiter string) (EnvTree, error) { 19 | r, err := regexp.Compile(reg) 20 | 21 | if err != nil { 22 | return EnvTree{}, err 23 | } 24 | 25 | t := createTreeFromDelimiterFilteringByRegexp(r, delimiter) 26 | 27 | return EnvTree{t}, nil 28 | } 29 | 30 | // FindString returns a string if key chain exists 31 | // or an error otherwise 32 | func (e EnvTree) FindString(keyChain ...string) (string, error) { 33 | return getString(getNodeValueByKeyChain(e.root, &keyChain)) 34 | } 35 | 36 | // FindStringUnsecured is insecured version of FindString to avoid the burden 37 | // of rechecking errors if it was done already. If any errors occurred cause 38 | // the variable is missing, it returns default zero string value. 39 | // This function has to be used carefully 40 | func (e EnvTree) FindStringUnsecured(keyChain ...string) string { 41 | if val, err := getString(getNodeValueByKeyChain(e.root, &keyChain)); err == nil { 42 | return val 43 | } 44 | 45 | return "" 46 | } 47 | 48 | // FindInt returns an integer if key chain exists 49 | // or an error if value is not an integer or doesn't exist 50 | func (e EnvTree) FindInt(keyChain ...string) (int, error) { 51 | return getInt(getNodeValueByKeyChain(e.root, &keyChain)) 52 | } 53 | 54 | // FindIntUnsecured is insecured version of FindInt to avoid the burden 55 | // of rechecking errors if it was done already. If any errors occurred cause 56 | // the variable is missing or not an int value, it returns default zero int value. 57 | // This function has to be used carefully 58 | func (e EnvTree) FindIntUnsecured(keyChain ...string) int { 59 | if val, err := getInt(getNodeValueByKeyChain(e.root, &keyChain)); err == nil { 60 | return val 61 | } 62 | 63 | return 0 64 | } 65 | 66 | // FindFloat returns a float if key chain exists 67 | // or an error if value is not a float or doesn't exist 68 | func (e EnvTree) FindFloat(keyChain ...string) (float32, error) { 69 | return getFloat(getNodeValueByKeyChain(e.root, &keyChain)) 70 | } 71 | 72 | // FindFloatUnsecured is insecured version of FindFloat to avoid the burden 73 | // of rechecking errors if it was done already. If any errors occurred cause 74 | // the variable is missing or not a floating value, it returns default zero floating value. 75 | // This function has to be used carefully 76 | func (e EnvTree) FindFloatUnsecured(keyChain ...string) float32 { 77 | if val, err := getFloat(getNodeValueByKeyChain(e.root, &keyChain)); err == nil { 78 | return val 79 | } 80 | 81 | return 0 82 | } 83 | 84 | // FindBool returns a boolean if key chain exists 85 | // or an error if value is not a boolean or doesn't exist 86 | func (e EnvTree) FindBool(keyChain ...string) (bool, error) { 87 | return getBool(getNodeValueByKeyChain(e.root, &keyChain)) 88 | } 89 | 90 | // FindBoolUnsecured is insecured version of FindBool to avoid the burden 91 | // of rechecking errors if it was done already. If any errors occurred cause 92 | // the variable is missing or not a boolean value, it returns default zero boolean value. 93 | // This function has to be used carefully 94 | func (e EnvTree) FindBoolUnsecured(keyChain ...string) bool { 95 | if val, err := getBool(getNodeValueByKeyChain(e.root, &keyChain)); err == nil { 96 | return val 97 | } 98 | 99 | return false 100 | } 101 | 102 | // IsExistingSubTree returns true if key chain has a tree associated or false if not 103 | func (e EnvTree) IsExistingSubTree(keyChain ...string) bool { 104 | _, exists := e.root.findNodeByKeyChain(&keyChain) 105 | 106 | return exists 107 | } 108 | 109 | // HasSubTreeValue returns true if key chain has a value or false if not. 110 | // If sub node doesn't exist, it returns an error ErrNodeNotFound 111 | // as second value 112 | func (e EnvTree) HasSubTreeValue(keyChain ...string) (bool, error) { 113 | n, exists := e.root.findNodeByKeyChain(&keyChain) 114 | 115 | if !exists { 116 | return false, NodeNotFoundError{keyChain} 117 | } 118 | 119 | return n.hasValue, nil 120 | } 121 | 122 | // HasSubTreeValueUnsecured is insecured version of HasSubTreeValue to avoid the burden 123 | // of rechecking errors if it was done already. If any errors occurred cause 124 | // the node doesn't exist, it returns false. 125 | // This function has to be used carefully 126 | func (e EnvTree) HasSubTreeValueUnsecured(keyChain ...string) bool { 127 | n, exists := e.root.findNodeByKeyChain(&keyChain) 128 | 129 | if !exists { 130 | return false 131 | } 132 | 133 | return n.hasValue 134 | } 135 | 136 | // FindSubTree returns underlying tree from key chain, 137 | // for instance given A -> B -> C -> D tree, 138 | // "A" "B" "C" key chain will return C sub tree. 139 | // If no node is found, it returns an error ErrNodeNotFound as 140 | // second value 141 | func (e EnvTree) FindSubTree(keyChain ...string) (EnvTree, error) { 142 | if n, exists := e.root.findNodeByKeyChain(&keyChain); exists { 143 | return EnvTree{n}, nil 144 | } 145 | 146 | return EnvTree{}, NodeNotFoundError{keyChain} 147 | } 148 | 149 | // FindSubTreeUnsecured is insecured version of FindSubTree to avoid the burden 150 | // of rechecking errors if it was done already. If any errors occurred cause 151 | // the node doesn't exist, it returns empty EnvTree. 152 | // This function has to be used carefully 153 | func (e EnvTree) FindSubTreeUnsecured(keyChain ...string) EnvTree { 154 | if n, exists := e.root.findNodeByKeyChain(&keyChain); exists { 155 | return EnvTree{n} 156 | } 157 | 158 | return EnvTree{} 159 | } 160 | 161 | // FindChildrenKeys returns all children keys for a given key chain. 162 | // If sub node doesn't exist, it returns an error ErrNodeNotFound 163 | // as second value 164 | func (e EnvTree) FindChildrenKeys(keyChain ...string) ([]string, error) { 165 | n, exists := e.root.findNodeByKeyChain(&keyChain) 166 | 167 | if !exists { 168 | return []string{}, NodeNotFoundError{keyChain} 169 | } 170 | 171 | keys := []string{} 172 | 173 | for _, c := range n.children { 174 | keys = append(keys, c.key) 175 | } 176 | 177 | return keys, nil 178 | } 179 | 180 | // FindChildrenKeysUnsecured is insecured version of FindChildrenKeys to avoid the burden 181 | // of rechecking errors if it was done already. If any errors occurred cause 182 | // the node doesn't exist, it returns empty string slice. 183 | // This function has to be used carefully 184 | func (e EnvTree) FindChildrenKeysUnsecured(keyChain ...string) []string { 185 | n, exists := e.root.findNodeByKeyChain(&keyChain) 186 | 187 | if !exists { 188 | return []string{} 189 | } 190 | 191 | keys := []string{} 192 | 193 | for _, c := range n.children { 194 | keys = append(keys, c.key) 195 | } 196 | 197 | return keys 198 | } 199 | 200 | // GetChildrenKeys retrieves all current tree children node keys 201 | func (e EnvTree) GetChildrenKeys() []string { 202 | keys := []string{} 203 | 204 | for _, c := range e.root.children { 205 | keys = append(keys, c.key) 206 | } 207 | 208 | return keys 209 | } 210 | 211 | // GetString returns current tree value as string if value exists 212 | // or an error as second parameter 213 | func (e EnvTree) GetString() (string, error) { 214 | return getString(getRootValue(e)) 215 | } 216 | 217 | // GetStringUnsecured is insecured version of GetString to avoid the burden 218 | // of rechecking errors if it was done already. If any errors occurred cause 219 | // the variable is missing, it returns default zero string value. 220 | // This function has to be used carefully 221 | func (e EnvTree) GetStringUnsecured() string { 222 | if val, err := getString(getRootValue(e)); err == nil { 223 | return val 224 | } 225 | 226 | return "" 227 | } 228 | 229 | // GetInt returns current tree value as int if value exists 230 | // or an error if value is not an integer or doesn't exist 231 | func (e EnvTree) GetInt() (int, error) { 232 | return getInt(getRootValue(e)) 233 | } 234 | 235 | // GetIntUnsecured is insecured version of GetInt to avoid the burden 236 | // of rechecking errors if it was done already. If any errors occurred cause 237 | // the variable is missing or not an int value, it returns default zero int value. 238 | // This function has to be used carefully 239 | func (e EnvTree) GetIntUnsecured() int { 240 | if val, err := getInt(getRootValue(e)); err == nil { 241 | return val 242 | } 243 | 244 | return 0 245 | } 246 | 247 | // GetFloat returns current tree value as float if value exists 248 | // or an error if value is not a float or doesn't exist 249 | func (e EnvTree) GetFloat() (float32, error) { 250 | return getFloat(getRootValue(e)) 251 | } 252 | 253 | // GetFloatUnsecured is insecured version of GetFloat to avoid the burden 254 | // of rechecking errors if it was done already. If any errors occurred cause 255 | // the variable is missing or not a floating value, it returns default zero floating value. 256 | // This function has to be used carefully 257 | func (e EnvTree) GetFloatUnsecured() float32 { 258 | if val, err := getFloat(getRootValue(e)); err == nil { 259 | return val 260 | } 261 | 262 | return 0 263 | } 264 | 265 | // GetBool returns current tree value as boolean if value exists 266 | // or an error if value is not a boolean or doesn't exist 267 | func (e EnvTree) GetBool() (bool, error) { 268 | return getBool(getRootValue(e)) 269 | } 270 | 271 | // GetBoolUnsecured is insecured version of GetBool to avoid the burden 272 | // of rechecking errors if it was done already. If any errors occurred cause 273 | // the variable is missing or not a boolean value, it returns default zero boolean value. 274 | // This function has to be used carefully 275 | func (e EnvTree) GetBoolUnsecured() bool { 276 | if val, err := getBool(getRootValue(e)); err == nil { 277 | return val 278 | } 279 | 280 | return false 281 | } 282 | 283 | // HasValue returns true if current tree has a value defined 284 | // false otherwise 285 | func (e EnvTree) HasValue() bool { 286 | return e.root.hasValue 287 | } 288 | 289 | // GetKey returns current tree key 290 | func (e EnvTree) GetKey() string { 291 | return e.root.key 292 | } 293 | 294 | // PopulateStruct fills a structure with datas extracted. 295 | // Missing values are ignored and only type errors are reported. 296 | // It's possible to control the way struct fields are defined 297 | // implementing StructWalker interface on structure, 298 | // checkout StructWalker documentation for further examples. 299 | func (e EnvTree) PopulateStruct(structure interface{}) error { 300 | return populateStructFromEnvTree(structure, &e, false) 301 | } 302 | 303 | // PopulateStructWithStrictMode fills a structure with datas extracted. 304 | // A missing environment variable returns an error and type errors are reported. 305 | // It's possible to control the way struct fields are defined 306 | // implementing StructWalker interface on structure, 307 | // checkout StructWalker documentation for further examples. 308 | func (e EnvTree) PopulateStructWithStrictMode(structure interface{}) error { 309 | return populateStructFromEnvTree(structure, &e, true) 310 | } 311 | 312 | func getRootValue(tree EnvTree) func() (string, bool) { 313 | return func() (string, bool) { 314 | if tree.root.hasValue { 315 | return tree.root.value, true 316 | } 317 | 318 | return "", false 319 | } 320 | } 321 | 322 | func createBranch(key string, value string, delimiter string, current *node) { 323 | for _, component := range strings.Split(key, delimiter) { 324 | n, exists := current.findNodeByKey(component) 325 | 326 | if exists { 327 | current = n 328 | } else { 329 | child := newNode() 330 | child.key = component 331 | current.appendNode(child) 332 | current = child 333 | } 334 | } 335 | 336 | current.hasValue = true 337 | current.value = value 338 | } 339 | 340 | func createTreeFromDelimiterFilteringByRegexp(reg *regexp.Regexp, delimiter string) *node { 341 | rootNode := newNode() 342 | 343 | for key, value := range *parseVars() { 344 | if reg.MatchString(key) { 345 | createBranch(key, value, delimiter, rootNode) 346 | } 347 | } 348 | 349 | return rootNode 350 | } 351 | 352 | func getNodeValueByKeyChain(node *node, keyChain *[]string) func() (string, bool) { 353 | return func() (string, bool) { 354 | n, exists := node.findNodeByKeyChain(keyChain) 355 | 356 | if !exists { 357 | return "", false 358 | } 359 | 360 | return n.value, true 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /env_tree_example_test.go: -------------------------------------------------------------------------------- 1 | package envh 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "sort" 8 | ) 9 | 10 | func ExampleEnvTree_FindString() { 11 | os.Clearenv() 12 | setEnv("ENVH_DB_USERNAME", "foo") 13 | setEnv("ENVH_DB_PASSWORD", "bar") 14 | setEnv("ENVH_DB_PORT", "3306") 15 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 16 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 17 | setEnv("ENVH_MAILER_USERNAME", "foo") 18 | setEnv("ENVH_MAILER_PASSWORD", "bar") 19 | setEnv("ENVH_MAILER_ENABLED", "true") 20 | 21 | env, err := NewEnvTree("^ENVH", "_") 22 | 23 | if err != nil { 24 | return 25 | } 26 | 27 | fmt.Println(env.FindString("ENVH", "DB", "USERNAME")) 28 | fmt.Println(env.FindString("ENVH", "DB", "WHATEVER")) 29 | // Output: 30 | // foo 31 | // Variable not found 32 | } 33 | 34 | func ExampleEnvTree_FindStringUnsecured() { 35 | os.Clearenv() 36 | setEnv("ENVH_DB_USERNAME", "foo") 37 | setEnv("ENVH_DB_PASSWORD", "bar") 38 | setEnv("ENVH_DB_PORT", "3306") 39 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 40 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 41 | setEnv("ENVH_MAILER_USERNAME", "foo") 42 | setEnv("ENVH_MAILER_PASSWORD", "bar") 43 | setEnv("ENVH_MAILER_ENABLED", "true") 44 | 45 | env, err := NewEnvTree("^ENVH", "_") 46 | 47 | if err != nil { 48 | return 49 | } 50 | 51 | fmt.Println(env.FindStringUnsecured("ENVH", "DB", "USERNAME")) 52 | fmt.Println(env.FindStringUnsecured("ENVH", "DB", "WHATEVER")) 53 | // Output: 54 | // foo 55 | // 56 | } 57 | 58 | func ExampleEnvTree_FindInt() { 59 | os.Clearenv() 60 | setEnv("ENVH_DB_USERNAME", "foo") 61 | setEnv("ENVH_DB_PASSWORD", "bar") 62 | setEnv("ENVH_DB_PORT", "3306") 63 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 64 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 65 | setEnv("ENVH_MAILER_USERNAME", "foo") 66 | setEnv("ENVH_MAILER_PASSWORD", "bar") 67 | setEnv("ENVH_MAILER_ENABLED", "true") 68 | 69 | env, err := NewEnvTree("^ENVH", "_") 70 | 71 | if err != nil { 72 | return 73 | } 74 | 75 | fmt.Println(env.FindInt("ENVH", "DB", "PORT")) 76 | fmt.Println(env.FindInt("ENVH", "DB", "USERNAME")) 77 | fmt.Println(env.FindInt("ENVH", "DB", "WHATEVER")) 78 | // Output: 79 | // 3306 80 | // 0 Value "foo" can't be converted to type "int" 81 | // 0 Variable not found 82 | } 83 | 84 | func ExampleEnvTree_FindIntUnsecured() { 85 | os.Clearenv() 86 | setEnv("ENVH_DB_USERNAME", "foo") 87 | setEnv("ENVH_DB_PASSWORD", "bar") 88 | setEnv("ENVH_DB_PORT", "3306") 89 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 90 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 91 | setEnv("ENVH_MAILER_USERNAME", "foo") 92 | setEnv("ENVH_MAILER_PASSWORD", "bar") 93 | setEnv("ENVH_MAILER_ENABLED", "true") 94 | 95 | env, err := NewEnvTree("^ENVH", "_") 96 | 97 | if err != nil { 98 | return 99 | } 100 | 101 | fmt.Println(env.FindIntUnsecured("ENVH", "DB", "PORT")) 102 | fmt.Println(env.FindIntUnsecured("ENVH", "DB", "USERNAME")) 103 | fmt.Println(env.FindIntUnsecured("ENVH", "DB", "WHATEVER")) 104 | // Output: 105 | // 3306 106 | // 0 107 | // 0 108 | } 109 | 110 | func ExampleEnvTree_FindBool() { 111 | os.Clearenv() 112 | setEnv("ENVH_DB_USERNAME", "foo") 113 | setEnv("ENVH_DB_PASSWORD", "bar") 114 | setEnv("ENVH_DB_PORT", "3306") 115 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 116 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 117 | setEnv("ENVH_MAILER_USERNAME", "foo") 118 | setEnv("ENVH_MAILER_PASSWORD", "bar") 119 | setEnv("ENVH_MAILER_ENABLED", "true") 120 | 121 | env, err := NewEnvTree("^ENVH", "_") 122 | 123 | if err != nil { 124 | return 125 | } 126 | 127 | fmt.Println(env.FindBool("ENVH", "MAILER", "ENABLED")) 128 | fmt.Println(env.FindBool("ENVH", "DB", "USERNAME")) 129 | fmt.Println(env.FindBool("ENVH", "DB", "WHATEVER")) 130 | // Output: 131 | // true 132 | // false Value "foo" can't be converted to type "bool" 133 | // false Variable not found 134 | } 135 | 136 | func ExampleEnvTree_FindBoolUnsecured() { 137 | os.Clearenv() 138 | setEnv("ENVH_DB_USERNAME", "foo") 139 | setEnv("ENVH_DB_PASSWORD", "bar") 140 | setEnv("ENVH_DB_PORT", "3306") 141 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 142 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 143 | setEnv("ENVH_MAILER_USERNAME", "foo") 144 | setEnv("ENVH_MAILER_PASSWORD", "bar") 145 | setEnv("ENVH_MAILER_ENABLED", "true") 146 | 147 | env, err := NewEnvTree("^ENVH", "_") 148 | 149 | if err != nil { 150 | return 151 | } 152 | 153 | fmt.Println(env.FindBoolUnsecured("ENVH", "MAILER", "ENABLED")) 154 | fmt.Println(env.FindBoolUnsecured("ENVH", "DB", "USERNAME")) 155 | fmt.Println(env.FindBoolUnsecured("ENVH", "DB", "WHATEVER")) 156 | // Output: 157 | // true 158 | // false 159 | // false 160 | } 161 | 162 | func ExampleEnvTree_FindFloat() { 163 | os.Clearenv() 164 | setEnv("ENVH_DB_USERNAME", "foo") 165 | setEnv("ENVH_DB_PASSWORD", "bar") 166 | setEnv("ENVH_DB_PORT", "3306") 167 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 168 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 169 | setEnv("ENVH_MAILER_USERNAME", "foo") 170 | setEnv("ENVH_MAILER_PASSWORD", "bar") 171 | setEnv("ENVH_MAILER_ENABLED", "true") 172 | 173 | env, err := NewEnvTree("^ENVH", "_") 174 | 175 | if err != nil { 176 | return 177 | } 178 | 179 | fmt.Println(env.FindFloat("ENVH", "DB", "USAGE", "LIMIT")) 180 | fmt.Println(env.FindFloat("ENVH", "DB", "USERNAME")) 181 | fmt.Println(env.FindFloat("ENVH", "DB", "WHATEVER")) 182 | // Output: 183 | // 95.6 184 | // 0 Value "foo" can't be converted to type "float" 185 | // 0 Variable not found 186 | } 187 | 188 | func ExampleEnvTree_FindFloatUnsecured() { 189 | os.Clearenv() 190 | setEnv("ENVH_DB_USERNAME", "foo") 191 | setEnv("ENVH_DB_PASSWORD", "bar") 192 | setEnv("ENVH_DB_PORT", "3306") 193 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 194 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 195 | setEnv("ENVH_MAILER_USERNAME", "foo") 196 | setEnv("ENVH_MAILER_PASSWORD", "bar") 197 | setEnv("ENVH_MAILER_ENABLED", "true") 198 | 199 | env, err := NewEnvTree("^ENVH", "_") 200 | 201 | if err != nil { 202 | return 203 | } 204 | 205 | fmt.Println(env.FindFloatUnsecured("ENVH", "DB", "USAGE", "LIMIT")) 206 | fmt.Println(env.FindFloatUnsecured("ENVH", "DB", "USERNAME")) 207 | fmt.Println(env.FindFloatUnsecured("ENVH", "DB", "WHATEVER")) 208 | // Output: 209 | // 95.6 210 | // 0 211 | // 0 212 | } 213 | 214 | func ExampleEnvTree_IsExistingSubTree() { 215 | os.Clearenv() 216 | setEnv("ENVH_DB_USERNAME", "foo") 217 | setEnv("ENVH_DB_PASSWORD", "bar") 218 | setEnv("ENVH_DB_PORT", "3306") 219 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 220 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 221 | setEnv("ENVH_MAILER_USERNAME", "foo") 222 | setEnv("ENVH_MAILER_PASSWORD", "bar") 223 | setEnv("ENVH_MAILER_ENABLED", "true") 224 | 225 | env, err := NewEnvTree("^ENVH", "_") 226 | 227 | if err != nil { 228 | return 229 | } 230 | 231 | fmt.Println(env.IsExistingSubTree("ENVH", "MAILER", "HOST")) 232 | fmt.Println(env.IsExistingSubTree("ENVH", "MAILER", "WHATEVER")) 233 | // Output: 234 | // true 235 | // false 236 | } 237 | 238 | func ExampleEnvTree_HasSubTreeValue() { 239 | os.Clearenv() 240 | setEnv("ENVH_DB_USERNAME", "foo") 241 | setEnv("ENVH_DB_PASSWORD", "bar") 242 | setEnv("ENVH_DB_PORT", "3306") 243 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 244 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 245 | setEnv("ENVH_MAILER_USERNAME", "foo") 246 | setEnv("ENVH_MAILER_PASSWORD", "bar") 247 | setEnv("ENVH_MAILER_ENABLED", "true") 248 | 249 | env, err := NewEnvTree("^ENVH", "_") 250 | 251 | if err != nil { 252 | return 253 | } 254 | 255 | fmt.Println(env.HasSubTreeValue("ENVH", "MAILER", "HOST")) 256 | fmt.Println(env.HasSubTreeValue("ENVH", "MAILER")) 257 | fmt.Println(env.HasSubTreeValue("ENVH", "MAILER", "WHATEVER")) 258 | // Output: 259 | // true 260 | // false 261 | // false No node found at path "ENVH -> MAILER -> WHATEVER" 262 | } 263 | 264 | func ExampleEnvTree_HasSubTreeValueUnsecured() { 265 | os.Clearenv() 266 | setEnv("ENVH_DB_USERNAME", "foo") 267 | setEnv("ENVH_DB_PASSWORD", "bar") 268 | setEnv("ENVH_DB_PORT", "3306") 269 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 270 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 271 | setEnv("ENVH_MAILER_USERNAME", "foo") 272 | setEnv("ENVH_MAILER_PASSWORD", "bar") 273 | setEnv("ENVH_MAILER_ENABLED", "true") 274 | 275 | env, err := NewEnvTree("^ENVH", "_") 276 | 277 | if err != nil { 278 | return 279 | } 280 | 281 | fmt.Println(env.HasSubTreeValueUnsecured("ENVH", "MAILER", "HOST")) 282 | fmt.Println(env.HasSubTreeValueUnsecured("ENVH", "MAILER")) 283 | fmt.Println(env.HasSubTreeValueUnsecured("ENVH", "MAILER", "WHATEVER")) 284 | // Output: 285 | // true 286 | // false 287 | // false 288 | } 289 | 290 | func ExampleEnvTree_HasValue() { 291 | os.Clearenv() 292 | setEnv("ENVH_DB_USERNAME", "foo") 293 | setEnv("ENVH_DB_PASSWORD", "bar") 294 | setEnv("ENVH_DB_PORT", "3306") 295 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 296 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 297 | setEnv("ENVH_MAILER_USERNAME", "foo") 298 | setEnv("ENVH_MAILER_PASSWORD", "bar") 299 | setEnv("ENVH_MAILER_ENABLED", "true") 300 | 301 | env, err := NewEnvTree("^ENVH", "_") 302 | 303 | if err != nil { 304 | return 305 | } 306 | 307 | usernameTree, err := env.FindSubTree("ENVH", "DB", "USERNAME") 308 | 309 | if err != nil { 310 | return 311 | } 312 | 313 | fmt.Println(env.HasValue()) 314 | fmt.Println(usernameTree.HasValue()) 315 | // Output: 316 | // false 317 | // true 318 | } 319 | 320 | func ExampleEnvTree_FindSubTree() { 321 | os.Clearenv() 322 | setEnv("ENVH_DB_USERNAME", "foo") 323 | setEnv("ENVH_DB_PASSWORD", "bar") 324 | setEnv("ENVH_DB_PORT", "3306") 325 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 326 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 327 | setEnv("ENVH_MAILER_USERNAME", "foo") 328 | setEnv("ENVH_MAILER_PASSWORD", "bar") 329 | setEnv("ENVH_MAILER_ENABLED", "true") 330 | 331 | env, err := NewEnvTree("^ENVH", "_") 332 | 333 | if err != nil { 334 | return 335 | } 336 | 337 | dbTree, err := env.FindSubTree("ENVH", "DB") 338 | dbChildrenKeys := dbTree.GetChildrenKeys() 339 | sort.Strings(dbChildrenKeys) 340 | 341 | fmt.Print(dbChildrenKeys) 342 | fmt.Print(" ") 343 | fmt.Println(err) 344 | 345 | mailerTree, err := env.FindSubTree("ENVH", "MAILER") 346 | mailerChildrenKeys := mailerTree.GetChildrenKeys() 347 | sort.Strings(mailerChildrenKeys) 348 | 349 | fmt.Print(mailerChildrenKeys) 350 | fmt.Print(" ") 351 | fmt.Println(err) 352 | 353 | fmt.Println(env.FindSubTree("ENVH", "MAILER", "WHATEVER")) 354 | // Output: 355 | // [PASSWORD PORT USAGE USERNAME] 356 | // [ENABLED HOST PASSWORD USERNAME] 357 | // {} No node found at path "ENVH -> MAILER -> WHATEVER" 358 | } 359 | 360 | func ExampleEnvTree_FindSubTreeUnsecured() { 361 | os.Clearenv() 362 | setEnv("ENVH_DB_USERNAME", "foo") 363 | setEnv("ENVH_DB_PASSWORD", "bar") 364 | setEnv("ENVH_DB_PORT", "3306") 365 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 366 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 367 | setEnv("ENVH_MAILER_USERNAME", "foo") 368 | setEnv("ENVH_MAILER_PASSWORD", "bar") 369 | setEnv("ENVH_MAILER_ENABLED", "true") 370 | 371 | env, err := NewEnvTree("^ENVH", "_") 372 | 373 | if err != nil { 374 | return 375 | } 376 | 377 | dbTree := env.FindSubTreeUnsecured("ENVH", "DB") 378 | dbChildrenKeys := dbTree.GetChildrenKeys() 379 | sort.Strings(dbChildrenKeys) 380 | 381 | fmt.Println(dbChildrenKeys) 382 | 383 | mailerTree := env.FindSubTreeUnsecured("ENVH", "MAILER") 384 | mailerChildrenKeys := mailerTree.GetChildrenKeys() 385 | sort.Strings(mailerChildrenKeys) 386 | 387 | fmt.Println(mailerChildrenKeys) 388 | 389 | fmt.Println(env.FindSubTreeUnsecured("ENVH", "MAILER", "WHATEVER")) 390 | // Output: 391 | // [PASSWORD PORT USAGE USERNAME] 392 | // [ENABLED HOST PASSWORD USERNAME] 393 | // {} 394 | } 395 | 396 | func ExampleEnvTree_GetKey() { 397 | os.Clearenv() 398 | setEnv("ENVH_DB_USERNAME", "foo") 399 | setEnv("ENVH_DB_PASSWORD", "bar") 400 | setEnv("ENVH_DB_PORT", "3306") 401 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 402 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 403 | setEnv("ENVH_MAILER_USERNAME", "foo") 404 | setEnv("ENVH_MAILER_PASSWORD", "bar") 405 | setEnv("ENVH_MAILER_ENABLED", "true") 406 | 407 | env, err := NewEnvTree("^ENVH", "_") 408 | 409 | if err != nil { 410 | return 411 | } 412 | 413 | dbTree, err := env.FindSubTree("ENVH", "DB") 414 | 415 | if err != nil { 416 | return 417 | } 418 | 419 | fmt.Println(dbTree.GetKey()) 420 | // Output: DB 421 | } 422 | 423 | func ExampleEnvTree_FindChildrenKeys() { 424 | os.Clearenv() 425 | setEnv("ENVH_DB_USERNAME", "foo") 426 | setEnv("ENVH_DB_PASSWORD", "bar") 427 | setEnv("ENVH_DB_PORT", "3306") 428 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 429 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 430 | setEnv("ENVH_MAILER_USERNAME", "foo") 431 | setEnv("ENVH_MAILER_PASSWORD", "bar") 432 | setEnv("ENVH_MAILER_ENABLED", "true") 433 | 434 | env, err := NewEnvTree("^ENVH", "_") 435 | 436 | if err != nil { 437 | return 438 | } 439 | 440 | children, err := env.FindChildrenKeys("ENVH", "DB") 441 | 442 | sort.Strings(children) 443 | 444 | fmt.Print(children) 445 | fmt.Print(" ") 446 | fmt.Println(err) 447 | fmt.Println(env.FindChildrenKeys("ENVH", "WHATEVER")) 448 | // Output: 449 | // [PASSWORD PORT USAGE USERNAME] 450 | // [] No node found at path "ENVH -> WHATEVER" 451 | } 452 | 453 | func ExampleEnvTree_FindChildrenKeysUnsecured() { 454 | os.Clearenv() 455 | setEnv("ENVH_DB_USERNAME", "foo") 456 | setEnv("ENVH_DB_PASSWORD", "bar") 457 | setEnv("ENVH_DB_PORT", "3306") 458 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 459 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 460 | setEnv("ENVH_MAILER_USERNAME", "foo") 461 | setEnv("ENVH_MAILER_PASSWORD", "bar") 462 | setEnv("ENVH_MAILER_ENABLED", "true") 463 | 464 | env, err := NewEnvTree("^ENVH", "_") 465 | 466 | if err != nil { 467 | return 468 | } 469 | 470 | children := env.FindChildrenKeysUnsecured("ENVH", "DB") 471 | 472 | sort.Strings(children) 473 | 474 | fmt.Println(children) 475 | fmt.Println(env.FindChildrenKeysUnsecured("ENVH", "WHATEVER")) 476 | // Output: 477 | // [PASSWORD PORT USAGE USERNAME] 478 | // [] 479 | } 480 | 481 | func ExampleEnvTree_GetBool() { 482 | os.Clearenv() 483 | setEnv("ENVH_DB_USERNAME", "foo") 484 | setEnv("ENVH_DB_PASSWORD", "bar") 485 | setEnv("ENVH_DB_PORT", "3306") 486 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 487 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 488 | setEnv("ENVH_MAILER_USERNAME", "foo") 489 | setEnv("ENVH_MAILER_PASSWORD", "bar") 490 | setEnv("ENVH_MAILER_ENABLED", "true") 491 | 492 | env, err := NewEnvTree("^ENVH", "_") 493 | 494 | if err != nil { 495 | return 496 | } 497 | 498 | enabledTree, err := env.FindSubTree("ENVH", "MAILER", "ENABLED") 499 | 500 | if err != nil { 501 | return 502 | } 503 | 504 | fmt.Println(env.GetBool()) 505 | fmt.Println(enabledTree.GetBool()) 506 | // Output: 507 | // false Variable not found 508 | // true 509 | } 510 | 511 | func ExampleEnvTree_GetBoolUnsecured() { 512 | os.Clearenv() 513 | setEnv("ENVH_DB_USERNAME", "foo") 514 | setEnv("ENVH_DB_PASSWORD", "bar") 515 | setEnv("ENVH_DB_PORT", "3306") 516 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 517 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 518 | setEnv("ENVH_MAILER_USERNAME", "foo") 519 | setEnv("ENVH_MAILER_PASSWORD", "bar") 520 | setEnv("ENVH_MAILER_ENABLED", "true") 521 | 522 | env, err := NewEnvTree("^ENVH", "_") 523 | 524 | if err != nil { 525 | return 526 | } 527 | 528 | enabledTree, err := env.FindSubTree("ENVH", "MAILER", "ENABLED") 529 | 530 | if err != nil { 531 | return 532 | } 533 | 534 | fmt.Println(env.GetBoolUnsecured()) 535 | fmt.Println(enabledTree.GetBoolUnsecured()) 536 | // Output: 537 | // false 538 | // true 539 | } 540 | 541 | func ExampleEnvTree_GetInt() { 542 | os.Clearenv() 543 | setEnv("ENVH_DB_USERNAME", "foo") 544 | setEnv("ENVH_DB_PASSWORD", "bar") 545 | setEnv("ENVH_DB_PORT", "3306") 546 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 547 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 548 | setEnv("ENVH_MAILER_USERNAME", "foo") 549 | setEnv("ENVH_MAILER_PASSWORD", "bar") 550 | setEnv("ENVH_MAILER_ENABLED", "true") 551 | 552 | env, err := NewEnvTree("^ENVH", "_") 553 | 554 | if err != nil { 555 | return 556 | } 557 | 558 | portTree, err := env.FindSubTree("ENVH", "DB", "PORT") 559 | 560 | if err != nil { 561 | return 562 | } 563 | 564 | fmt.Println(env.GetInt()) 565 | fmt.Println(portTree.GetInt()) 566 | // Output: 567 | // 0 Variable not found 568 | // 3306 569 | } 570 | 571 | func ExampleEnvTree_GetIntUnsecured() { 572 | os.Clearenv() 573 | setEnv("ENVH_DB_USERNAME", "foo") 574 | setEnv("ENVH_DB_PASSWORD", "bar") 575 | setEnv("ENVH_DB_PORT", "3306") 576 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 577 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 578 | setEnv("ENVH_MAILER_USERNAME", "foo") 579 | setEnv("ENVH_MAILER_PASSWORD", "bar") 580 | setEnv("ENVH_MAILER_ENABLED", "true") 581 | 582 | env, err := NewEnvTree("^ENVH", "_") 583 | 584 | if err != nil { 585 | return 586 | } 587 | 588 | portTree, err := env.FindSubTree("ENVH", "DB", "PORT") 589 | 590 | if err != nil { 591 | return 592 | } 593 | 594 | fmt.Println(env.GetIntUnsecured()) 595 | fmt.Println(portTree.GetIntUnsecured()) 596 | // Output: 597 | // 0 598 | // 3306 599 | } 600 | 601 | func ExampleEnvTree_GetString() { 602 | os.Clearenv() 603 | setEnv("ENVH_DB_USERNAME", "foo") 604 | setEnv("ENVH_DB_PASSWORD", "bar") 605 | setEnv("ENVH_DB_PORT", "3306") 606 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 607 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 608 | setEnv("ENVH_MAILER_USERNAME", "foo") 609 | setEnv("ENVH_MAILER_PASSWORD", "bar") 610 | setEnv("ENVH_MAILER_ENABLED", "true") 611 | 612 | env, err := NewEnvTree("^ENVH", "_") 613 | 614 | if err != nil { 615 | return 616 | } 617 | 618 | usernameTree, err := env.FindSubTree("ENVH", "DB", "USERNAME") 619 | 620 | if err != nil { 621 | return 622 | } 623 | 624 | fmt.Println(env.GetString()) 625 | fmt.Println(usernameTree.GetString()) 626 | // Output: 627 | // Variable not found 628 | // foo 629 | } 630 | 631 | func ExampleEnvTree_GetStringUnsecured() { 632 | os.Clearenv() 633 | setEnv("ENVH_DB_USERNAME", "foo") 634 | setEnv("ENVH_DB_PASSWORD", "bar") 635 | setEnv("ENVH_DB_PORT", "3306") 636 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 637 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 638 | setEnv("ENVH_MAILER_USERNAME", "foo") 639 | setEnv("ENVH_MAILER_PASSWORD", "bar") 640 | setEnv("ENVH_MAILER_ENABLED", "true") 641 | 642 | env, err := NewEnvTree("^ENVH", "_") 643 | 644 | if err != nil { 645 | return 646 | } 647 | 648 | usernameTree, err := env.FindSubTree("ENVH", "DB", "USERNAME") 649 | 650 | if err != nil { 651 | return 652 | } 653 | 654 | fmt.Println(env.GetStringUnsecured()) 655 | fmt.Println(usernameTree.GetStringUnsecured()) 656 | // Output: 657 | // 658 | // foo 659 | } 660 | 661 | func ExampleEnvTree_GetFloat() { 662 | os.Clearenv() 663 | setEnv("ENVH_DB_USERNAME", "foo") 664 | setEnv("ENVH_DB_PASSWORD", "bar") 665 | setEnv("ENVH_DB_PORT", "3306") 666 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 667 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 668 | setEnv("ENVH_MAILER_USERNAME", "foo") 669 | setEnv("ENVH_MAILER_PASSWORD", "bar") 670 | setEnv("ENVH_MAILER_ENABLED", "true") 671 | 672 | env, err := NewEnvTree("^ENVH", "_") 673 | 674 | if err != nil { 675 | return 676 | } 677 | 678 | portTree, err := env.FindSubTree("ENVH", "DB", "USAGE", "LIMIT") 679 | 680 | if err != nil { 681 | return 682 | } 683 | 684 | fmt.Println(env.GetFloat()) 685 | fmt.Println(portTree.GetFloat()) 686 | // Output: 687 | // 0 Variable not found 688 | // 95.6 689 | } 690 | 691 | func ExampleEnvTree_GetFloatUnsecured() { 692 | os.Clearenv() 693 | setEnv("ENVH_DB_USERNAME", "foo") 694 | setEnv("ENVH_DB_PASSWORD", "bar") 695 | setEnv("ENVH_DB_PORT", "3306") 696 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 697 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 698 | setEnv("ENVH_MAILER_USERNAME", "foo") 699 | setEnv("ENVH_MAILER_PASSWORD", "bar") 700 | setEnv("ENVH_MAILER_ENABLED", "true") 701 | 702 | env, err := NewEnvTree("^ENVH", "_") 703 | 704 | if err != nil { 705 | return 706 | } 707 | 708 | portTree, err := env.FindSubTree("ENVH", "DB", "USAGE", "LIMIT") 709 | 710 | if err != nil { 711 | return 712 | } 713 | 714 | fmt.Println(env.GetFloatUnsecured()) 715 | fmt.Println(portTree.GetFloatUnsecured()) 716 | // Output: 717 | // 0 718 | // 95.6 719 | } 720 | 721 | func ExampleEnvTree_GetChildrenKeys() { 722 | os.Clearenv() 723 | setEnv("ENVH_DB_USERNAME", "foo") 724 | setEnv("ENVH_DB_PASSWORD", "bar") 725 | setEnv("ENVH_DB_PORT", "3306") 726 | setEnv("ENVH_DB_USAGE_LIMIT", "95.6") 727 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 728 | setEnv("ENVH_MAILER_USERNAME", "foo") 729 | setEnv("ENVH_MAILER_PASSWORD", "bar") 730 | setEnv("ENVH_MAILER_ENABLED", "true") 731 | 732 | env, err := NewEnvTree("^ENVH", "_") 733 | 734 | if err != nil { 735 | return 736 | } 737 | 738 | dbTree, err := env.FindSubTree("ENVH", "DB") 739 | 740 | if err != nil { 741 | return 742 | } 743 | 744 | children := dbTree.GetChildrenKeys() 745 | 746 | sort.Strings(children) 747 | 748 | fmt.Println(children) 749 | // Output: 750 | // [PASSWORD PORT USAGE USERNAME] 751 | } 752 | 753 | func ExampleEnvTree_PopulateStruct() { 754 | type ENVH struct { 755 | DB struct { 756 | USERNAME string 757 | PASSWORD string 758 | PORT int 759 | USAGELIMIT float32 760 | } 761 | MAILER struct { 762 | HOST string 763 | USERNAME string 764 | PASSWORD string 765 | ENABLED bool 766 | } 767 | } 768 | 769 | os.Clearenv() 770 | setEnv("ENVH_DB_USERNAME", "foo") 771 | setEnv("ENVH_DB_PASSWORD", "bar") 772 | setEnv("ENVH_DB_PORT", "3306") 773 | setEnv("ENVH_DB_USAGELIMIT", "95.6") 774 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 775 | setEnv("ENVH_MAILER_USERNAME", "foo") 776 | setEnv("ENVH_MAILER_PASSWORD", "bar") 777 | setEnv("ENVH_MAILER_ENABLED", "true") 778 | 779 | env, err := NewEnvTree("^ENVH", "_") 780 | 781 | if err != nil { 782 | return 783 | } 784 | 785 | s := ENVH{} 786 | 787 | err = env.PopulateStruct(&s) 788 | 789 | if err != nil { 790 | return 791 | } 792 | 793 | b, err := json.Marshal(s) 794 | 795 | if err != nil { 796 | return 797 | } 798 | 799 | fmt.Println(string(b)) 800 | // Output: 801 | // {"DB":{"USERNAME":"foo","PASSWORD":"bar","PORT":3306,"USAGELIMIT":95.6},"MAILER":{"HOST":"127.0.0.1","USERNAME":"foo","PASSWORD":"bar","ENABLED":true}} 802 | } 803 | 804 | func ExampleEnvTree_PopulateStructWithStrictMode() { 805 | type ENVH struct { 806 | DB struct { 807 | USERNAME string 808 | PASSWORD string 809 | PORT int 810 | USAGELIMIT float32 811 | } 812 | MAILER struct { 813 | HOST string 814 | USERNAME string 815 | PASSWORD string 816 | ENABLED bool 817 | } 818 | } 819 | 820 | os.Clearenv() 821 | setEnv("ENVH_DB_USERNAME", "foo") 822 | setEnv("ENVH_DB_PASSWORD", "bar") 823 | setEnv("ENVH_DB_PORT", "3306") 824 | setEnv("ENVH_DB_USAGELIMIT", "95.6") 825 | setEnv("ENVH_MAILER_HOST", "127.0.0.1") 826 | setEnv("ENVH_MAILER_USERNAME", "foo") 827 | setEnv("ENVH_MAILER_PASSWORD", "bar") 828 | 829 | env, err := NewEnvTree("^ENVH", "_") 830 | 831 | if err != nil { 832 | return 833 | } 834 | 835 | s := ENVH{} 836 | 837 | err = env.PopulateStructWithStrictMode(&s) 838 | 839 | fmt.Println(err) 840 | // Output: 841 | // Variable not found 842 | } 843 | -------------------------------------------------------------------------------- /env_tree_test.go: -------------------------------------------------------------------------------- 1 | package envh 2 | 3 | import ( 4 | "regexp" 5 | "sort" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestCreateTreeFromDelimiterFilteringByRegexp(t *testing.T) { 13 | setTestingEnvsForTree() 14 | 15 | n := createTreeFromDelimiterFilteringByRegexp(regexp.MustCompile("ENVH"), "_") 16 | 17 | for key, expected := range map[string]string{"TEST3": "test1", "TEST4": "test2", "TEST6": "test3", "TEST1": "test5", "TEST2": "test4"} { 18 | nodes := n.findAllNodesByKey(key, true) 19 | 20 | assert.Len(t, *nodes, 1, "Must contains 1 element") 21 | assert.Equal(t, expected, (*nodes)[0].value, "Must have correct value") 22 | } 23 | } 24 | 25 | func TestCreateTreeFromDelimiterFilteringByRegexpAndFindAllKeysWithAKey(t *testing.T) { 26 | setTestingEnvsForTree() 27 | 28 | n := createTreeFromDelimiterFilteringByRegexp(regexp.MustCompile("ENVH"), "_") 29 | 30 | nodes := n.findAllNodesByKey("TEST2", false) 31 | 32 | assert.Len(t, *nodes, 2, "Must contains 2 elements") 33 | } 34 | 35 | func TestNewEnvTreeWithAnInvalidRegexp(t *testing.T) { 36 | setTestingEnvsForTree() 37 | 38 | _, err := NewEnvTree("**", "_") 39 | 40 | assert.EqualError(t, err, "error parsing regexp: missing argument to repetition operator: `*`", "Must return an error when regexp is invalid") 41 | } 42 | 43 | func rebuildKeys(n *node, tmp []string, keys *[]string) { 44 | for _, child := range (*n).children { 45 | t := append(tmp, child.key) 46 | 47 | rebuildKeys(child, t, keys) 48 | } 49 | 50 | if n.hasValue { 51 | *keys = append(*keys, strings.Join(tmp, "_")) 52 | } 53 | } 54 | 55 | func TestNewEnvTree(t *testing.T) { 56 | setTestingEnvsForTree() 57 | 58 | envTree, err := NewEnvTree("ENVH", "_") 59 | 60 | assert.NoError(t, err, "Must returns no error") 61 | 62 | result := []string{} 63 | 64 | rebuildKeys(envTree.root, []string{}, &result) 65 | 66 | expected := []string{ 67 | "ENVH_TEST1_TEST5_TEST6", 68 | "ENVH_TEST1_TEST2_TEST4", 69 | "ENVH_TEST1_TEST2_TEST3", 70 | "ENVH_TEST1_TEST7_TEST2", 71 | "ENVH_TEST1", 72 | } 73 | 74 | sort.Strings(expected) 75 | sort.Strings(result) 76 | 77 | assert.Equal(t, expected, result, "Must store all environment variables starting with envh in a tree") 78 | } 79 | 80 | func TestFindStringFromTree(t *testing.T) { 81 | setEnv("ENVH_TEST1_TEST2_TEST3", "test1") 82 | 83 | envTree, err := NewEnvTree("ENVH", "_") 84 | 85 | assert.NoError(t, err, "Must returns no error") 86 | 87 | value, err := envTree.FindString("ENVH", "TEST1", "TEST2", "TEST3") 88 | 89 | assert.NoError(t, err, "Must return no errors") 90 | assert.Equal(t, "test1", value, "Must return value") 91 | 92 | value, err = envTree.FindString("ENVH_TEST1000") 93 | 94 | assert.EqualError(t, err, "Variable not found", "Must return an error when variable can't be found") 95 | assert.Equal(t, "", value, "Must return empty string") 96 | } 97 | 98 | func TestFindStringUnsecuredFromTree(t *testing.T) { 99 | setEnv("ENVH_TEST1_TEST2_TEST3", "test1") 100 | 101 | envTree, err := NewEnvTree("ENVH", "_") 102 | 103 | assert.NoError(t, err, "Must returns no error") 104 | 105 | value := envTree.FindStringUnsecured("ENVH", "TEST1", "TEST2", "TEST3") 106 | 107 | assert.Equal(t, "test1", value, "Must return value") 108 | 109 | value = envTree.FindStringUnsecured("ENVH_TEST1000") 110 | 111 | assert.Equal(t, "", value, "Must return empty string") 112 | } 113 | 114 | func TestFindIntFromTree(t *testing.T) { 115 | setEnv("ENVH_TEST1_TEST2_INT", "1") 116 | setEnv("ENVH_TEST1_TEST2_STRING", "test") 117 | 118 | envTree, err := NewEnvTree("ENVH", "_") 119 | 120 | assert.NoError(t, err, "Must returns no error") 121 | 122 | value, err := envTree.FindInt("ENVH", "TEST1", "TEST2", "INT") 123 | 124 | assert.NoError(t, err, "Must return no errors") 125 | assert.Equal(t, 1, value, "Must return value") 126 | 127 | value, err = envTree.FindInt("TEST100") 128 | 129 | assert.EqualError(t, err, "Variable not found", "Must return an error when variable can't be found") 130 | assert.Equal(t, 0, value, "Must return value") 131 | 132 | value, err = envTree.FindInt("ENVH", "TEST1", "TEST2", "STRING") 133 | 134 | assert.EqualError(t, err, `Value "test" can't be converted to type "int"`, "Must return an error when variable can't be converted") 135 | assert.Equal(t, 0, value, "Must return empty string") 136 | } 137 | 138 | func TestFindIntUnsecuredFromTree(t *testing.T) { 139 | setEnv("ENVH_TEST1_TEST2_INT", "1") 140 | setEnv("ENVH_TEST1_TEST2_STRING", "test") 141 | 142 | envTree, err := NewEnvTree("ENVH", "_") 143 | 144 | assert.NoError(t, err, "Must returns no error") 145 | 146 | value := envTree.FindIntUnsecured("ENVH", "TEST1", "TEST2", "INT") 147 | 148 | assert.Equal(t, 1, value, "Must return value") 149 | 150 | value = envTree.FindIntUnsecured("TEST100") 151 | 152 | assert.Equal(t, 0, value, "Must return value") 153 | 154 | value = envTree.FindIntUnsecured("ENVH", "TEST1", "TEST2", "STRING") 155 | 156 | assert.Equal(t, 0, value, "Must return empty string") 157 | } 158 | 159 | func TestFindBoolFromTree(t *testing.T) { 160 | setEnv("ENVH_TEST1_TEST2_BOOL", "1") 161 | setEnv("ENVH_TEST1_TEST2_STRING", "test") 162 | 163 | envTree, err := NewEnvTree("ENVH", "_") 164 | 165 | assert.NoError(t, err, "Must returns no error") 166 | 167 | value, err := envTree.FindBool("ENVH", "TEST1", "TEST2", "BOOL") 168 | 169 | assert.NoError(t, err, "Must return no errors") 170 | assert.Equal(t, true, value, "Must return value") 171 | 172 | value, err = envTree.FindBool("TEST100") 173 | 174 | assert.EqualError(t, err, "Variable not found", "Must return an error when variable can't be found") 175 | assert.Equal(t, false, value, "Must return value") 176 | 177 | value, err = envTree.FindBool("ENVH", "TEST1", "TEST2", "STRING") 178 | 179 | assert.EqualError(t, err, `Value "test" can't be converted to type "bool"`, "Must return an error when variable can't be converted") 180 | assert.Equal(t, false, value, "Must return empty string") 181 | } 182 | 183 | func TestFindBoolUnsecuredFromTree(t *testing.T) { 184 | setEnv("ENVH_TEST1_TEST2_BOOL", "1") 185 | setEnv("ENVH_TEST1_TEST2_STRING", "test") 186 | 187 | envTree, err := NewEnvTree("ENVH", "_") 188 | 189 | assert.NoError(t, err, "Must returns no error") 190 | 191 | value := envTree.FindBoolUnsecured("ENVH", "TEST1", "TEST2", "BOOL") 192 | 193 | assert.Equal(t, true, value, "Must return value") 194 | 195 | value = envTree.FindBoolUnsecured("TEST100") 196 | 197 | assert.Equal(t, false, value, "Must return value") 198 | 199 | value = envTree.FindBoolUnsecured("ENVH", "TEST1", "TEST2", "STRING") 200 | 201 | assert.Equal(t, false, value, "Must return empty string") 202 | } 203 | 204 | func TestFindFloatFromTree(t *testing.T) { 205 | setEnv("ENVH_TEST1_TEST2_FLOAT", "0.01") 206 | setEnv("ENVH_TEST1_TEST2_STRING", "test") 207 | 208 | envTree, err := NewEnvTree("ENVH", "_") 209 | 210 | assert.NoError(t, err, "Must returns no error") 211 | 212 | value, err := envTree.FindFloat("ENVH", "TEST1", "TEST2", "FLOAT") 213 | 214 | assert.NoError(t, err, "Must return no errors") 215 | assert.Equal(t, float32(0.01), value, "Must return value") 216 | 217 | value, err = envTree.FindFloat("TEST100") 218 | 219 | assert.EqualError(t, err, "Variable not found", "Must return an error when variable can't be found") 220 | assert.Equal(t, float32(0), value, "Must return value") 221 | 222 | value, err = envTree.FindFloat("ENVH", "TEST1", "TEST2", "STRING") 223 | 224 | assert.EqualError(t, err, `Value "test" can't be converted to type "float"`, "Must return an error when variable can't be converted") 225 | assert.Equal(t, float32(0), value, "Must return empty string") 226 | } 227 | 228 | func TestFindFloatUnsecuredFromTree(t *testing.T) { 229 | setEnv("ENVH_TEST1_TEST2_FLOAT", "0.01") 230 | setEnv("ENVH_TEST1_TEST2_STRING", "test") 231 | 232 | envTree, err := NewEnvTree("ENVH", "_") 233 | 234 | assert.NoError(t, err, "Must returns no error") 235 | 236 | value := envTree.FindFloatUnsecured("ENVH", "TEST1", "TEST2", "FLOAT") 237 | 238 | assert.Equal(t, float32(0.01), value, "Must return value") 239 | 240 | value = envTree.FindFloatUnsecured("TEST100") 241 | 242 | assert.Equal(t, float32(0), value, "Must return value") 243 | 244 | value = envTree.FindFloatUnsecured("ENVH", "TEST1", "TEST2", "STRING") 245 | 246 | assert.Equal(t, float32(0), value, "Must return empty string") 247 | } 248 | 249 | func TestIsExistingSubTreeFromTree(t *testing.T) { 250 | setEnv("ENVH_TEST1_TEST2_TEST3", "test") 251 | 252 | envTree, err := NewEnvTree("ENVH", "_") 253 | 254 | assert.NoError(t, err, "Must returns no error") 255 | 256 | assert.True(t, envTree.IsExistingSubTree("ENVH", "TEST1", "TEST2", "TEST3"), "Must return true if environment node exists") 257 | 258 | assert.False(t, envTree.IsExistingSubTree("ENVH", "TEST1", "TEST2", "TEST10000"), "Must return false if environment node doesn't exist") 259 | } 260 | 261 | func TestHasSubTreeValueFromTree(t *testing.T) { 262 | setEnv("ENVH_TEST10_TEST20_TEST30", "test") 263 | 264 | envTree, err := NewEnvTree("ENVH", "_") 265 | 266 | assert.NoError(t, err, "Must returns no error") 267 | 268 | v, err := envTree.HasSubTreeValue("ENVH", "TEST10", "TEST20", "TEST30") 269 | 270 | assert.NoError(t, err, "Must returns no error") 271 | assert.True(t, v, "Must return true if environment node has a value") 272 | 273 | v, err = envTree.HasSubTreeValue("ENVH", "TEST10", "TEST20") 274 | 275 | assert.NoError(t, err, "Must returns no error") 276 | assert.False(t, v, "Must return false if environment node doesn't have value") 277 | 278 | _, err = envTree.HasSubTreeValue("ENVH", "TEST10", "TEST20", "TEST10000") 279 | 280 | assert.EqualError(t, err, `No node found at path "ENVH -> TEST10 -> TEST20 -> TEST10000"`, "Must returns an error, node doesn't exists") 281 | } 282 | 283 | func TestHasSubTreeValueUnsecured(t *testing.T) { 284 | setEnv("ENVH_TEST10_TEST20_TEST30", "test") 285 | 286 | envTree, err := NewEnvTree("ENVH", "_") 287 | 288 | assert.NoError(t, err, "Must returns no error") 289 | 290 | v := envTree.HasSubTreeValueUnsecured("ENVH", "TEST10", "TEST20", "TEST30") 291 | 292 | assert.True(t, v, "Must return true if environment node has a value") 293 | 294 | v = envTree.HasSubTreeValueUnsecured("ENVH", "TEST10", "TEST20") 295 | 296 | assert.False(t, v, "Must return false if environment node doesn't have value") 297 | } 298 | 299 | func TestFindChildrenKeysFromTree(t *testing.T) { 300 | setEnv("ENVH_TEST11_TEST12_TEST13_TEST14", "test1") 301 | setEnv("ENVH_TEST11_TEST12_TEST13_TEST15", "test2") 302 | setEnv("ENVH_TEST11_TEST12_TEST13_TEST16", "test3") 303 | 304 | envTree, err := NewEnvTree("ENVH", "_") 305 | 306 | assert.NoError(t, err, "Must returns no error") 307 | 308 | v, err := envTree.FindChildrenKeys("ENVH", "TEST11", "TEST12", "TEST13") 309 | 310 | sort.Strings(v) 311 | 312 | assert.NoError(t, err, "Must returns no error") 313 | assert.Equal(t, []string{"TEST14", "TEST15", "TEST16"}, v, "Must return all node children keys") 314 | 315 | v, err = envTree.FindChildrenKeys("ENVH", "TEST11", "TEST12", "TEST13", "TEST14") 316 | 317 | assert.NoError(t, err, "Must returns no error") 318 | assert.Empty(t, v, "Must return no node children keys") 319 | 320 | _, err = envTree.FindChildrenKeys("ENVH", "TEST11", "TEST12", "TEST13", "TEST10000") 321 | 322 | assert.EqualError(t, err, `No node found at path "ENVH -> TEST11 -> TEST12 -> TEST13 -> TEST10000"`, "Must returns an error, node doesn't exists") 323 | } 324 | 325 | func TestFindChildrenKeysUnsecuredFromTree(t *testing.T) { 326 | setEnv("ENVH_TEST11_TEST12_TEST13_TEST14", "test1") 327 | setEnv("ENVH_TEST11_TEST12_TEST13_TEST15", "test2") 328 | setEnv("ENVH_TEST11_TEST12_TEST13_TEST16", "test3") 329 | 330 | envTree, err := NewEnvTree("ENVH", "_") 331 | 332 | assert.NoError(t, err, "Must returns no error") 333 | 334 | v := envTree.FindChildrenKeysUnsecured("ENVH", "TEST11", "TEST12", "TEST13") 335 | 336 | sort.Strings(v) 337 | 338 | assert.Equal(t, []string{"TEST14", "TEST15", "TEST16"}, v, "Must return all node children keys") 339 | 340 | v = envTree.FindChildrenKeysUnsecured("ENVH", "TEST11", "TEST12", "TEST13", "TEST14") 341 | 342 | assert.Empty(t, v, "Must return no node children keys") 343 | 344 | v = envTree.FindChildrenKeysUnsecured("ENVH", "TEST11", "TEST12", "TEST13", "TEST10000") 345 | 346 | assert.Empty(t, v, "Must return empty value") 347 | } 348 | 349 | func TestFindSubTreeFromTree(t *testing.T) { 350 | setEnv("ENVH_TEST11_TEST12_TEST13_TEST14", "test1") 351 | setEnv("ENVH_TEST11_TEST12_TEST13_TEST15", "test2") 352 | setEnv("ENVH_TEST11_TEST12_TEST13_TEST16", "test3") 353 | 354 | envTree, err := NewEnvTree("ENVH", "_") 355 | 356 | assert.NoError(t, err, "Must returns no error") 357 | 358 | tree, err := envTree.FindSubTree("ENVH", "TEST11", "TEST12", "TEST13") 359 | 360 | assert.NoError(t, err, "Must returns no error") 361 | 362 | v, err := tree.FindString("TEST14") 363 | 364 | assert.NoError(t, err, "Must returns no error") 365 | 366 | assert.Equal(t, "test1", v, "Must return value corresponding to key TEST14") 367 | 368 | _, err = envTree.FindSubTree("ENVH", "TEST11", "TEST12", "TEST13", "TEST10000") 369 | 370 | assert.EqualError(t, err, `No node found at path "ENVH -> TEST11 -> TEST12 -> TEST13 -> TEST10000"`, "Must returns an error, node doesn't exists") 371 | } 372 | 373 | func TestFindSubTreeUnsecuredFromTree(t *testing.T) { 374 | setEnv("ENVH_TEST11_TEST12_TEST13_TEST14", "test1") 375 | setEnv("ENVH_TEST11_TEST12_TEST13_TEST15", "test2") 376 | setEnv("ENVH_TEST11_TEST12_TEST13_TEST16", "test3") 377 | 378 | envTree, err := NewEnvTree("ENVH", "_") 379 | 380 | assert.NoError(t, err, "Must returns no error") 381 | 382 | tree := envTree.FindSubTreeUnsecured("ENVH", "TEST11", "TEST12", "TEST13") 383 | 384 | v, err := tree.FindString("TEST14") 385 | 386 | assert.NoError(t, err, "Must returns no error") 387 | assert.Equal(t, "test1", v, "Must return value corresponding to key TEST14") 388 | 389 | tree = envTree.FindSubTreeUnsecured("ENVH", "TEST11", "TEST12", "TEST13", "TEST10000") 390 | 391 | assert.Equal(t, EnvTree{}, tree, "Must return empty value") 392 | } 393 | 394 | func TestGetStringFromTree(t *testing.T) { 395 | setEnv("ENVH_TEST1_TEST2_TEST3", "test1") 396 | 397 | envTree, err := NewEnvTree("ENVH", "_") 398 | 399 | assert.NoError(t, err, "Must returns no error") 400 | 401 | subTree, err := envTree.FindSubTree("ENVH", "TEST1", "TEST2", "TEST3") 402 | 403 | assert.NoError(t, err, "Must returns no error") 404 | 405 | value, err := subTree.GetString() 406 | 407 | assert.NoError(t, err, "Must return no errors") 408 | assert.Equal(t, "test1", value, "Must return value") 409 | 410 | subTree, err = envTree.FindSubTree("ENVH", "TEST1", "TEST2") 411 | 412 | assert.NoError(t, err, "Must return no errors") 413 | 414 | value, err = subTree.GetString() 415 | 416 | assert.EqualError(t, err, "Variable not found", "Must return an error when variable can't be found") 417 | assert.Equal(t, "", value, "Must return empty string") 418 | } 419 | 420 | func TestGetStringUnsecuredFromTree(t *testing.T) { 421 | setEnv("ENVH_TEST1_TEST2_TEST3", "test1") 422 | 423 | envTree, err := NewEnvTree("ENVH", "_") 424 | 425 | assert.NoError(t, err, "Must returns no error") 426 | 427 | subTree, err := envTree.FindSubTree("ENVH", "TEST1", "TEST2", "TEST3") 428 | 429 | assert.NoError(t, err, "Must returns no error") 430 | 431 | value := subTree.GetStringUnsecured() 432 | 433 | assert.Equal(t, "test1", value, "Must return value") 434 | 435 | subTree, err = envTree.FindSubTree("ENVH", "TEST1", "TEST2") 436 | 437 | assert.NoError(t, err, "Must return no errors") 438 | 439 | value = subTree.GetStringUnsecured() 440 | 441 | assert.Equal(t, "", value, "Must return empty string") 442 | } 443 | 444 | func TestGetIntFromTree(t *testing.T) { 445 | setEnv("ENVH_TEST1_TEST2_INT", "1") 446 | setEnv("ENVH_TEST1_TEST2_STRING", "test") 447 | 448 | envTree, err := NewEnvTree("ENVH", "_") 449 | 450 | assert.NoError(t, err, "Must returns no error") 451 | 452 | subTree, err := envTree.FindSubTree("ENVH", "TEST1", "TEST2", "INT") 453 | 454 | assert.NoError(t, err, "Must return no errors") 455 | 456 | value, err := subTree.GetInt() 457 | 458 | assert.NoError(t, err, "Must return no errors") 459 | assert.Equal(t, 1, value, "Must return value") 460 | 461 | subTree, err = envTree.FindSubTree("ENVH", "TEST1", "TEST2") 462 | 463 | assert.NoError(t, err, "Must return no errors") 464 | 465 | value, err = subTree.GetInt() 466 | 467 | assert.EqualError(t, err, "Variable not found", "Must return an error when variable can't be found") 468 | assert.Equal(t, 0, value, "Must return value") 469 | 470 | subTree, err = envTree.FindSubTree("ENVH", "TEST1", "TEST2", "STRING") 471 | 472 | assert.NoError(t, err, "Must return no errors") 473 | 474 | value, err = subTree.GetInt() 475 | 476 | assert.EqualError(t, err, `Value "test" can't be converted to type "int"`, "Must return an error when variable can't be converted") 477 | assert.Equal(t, 0, value, "Must return empty string") 478 | } 479 | 480 | func TestGetIntUnsecuredFromTree(t *testing.T) { 481 | setEnv("ENVH_TEST1_TEST2_INT", "1") 482 | setEnv("ENVH_TEST1_TEST2_STRING", "test") 483 | 484 | envTree, err := NewEnvTree("ENVH", "_") 485 | 486 | assert.NoError(t, err, "Must returns no error") 487 | 488 | subTree, err := envTree.FindSubTree("ENVH", "TEST1", "TEST2", "INT") 489 | 490 | assert.NoError(t, err, "Must returns no error") 491 | 492 | value := subTree.GetIntUnsecured() 493 | 494 | assert.Equal(t, 1, value, "Must return value") 495 | 496 | subTree, err = envTree.FindSubTree("ENVH", "TEST1", "TEST2") 497 | 498 | assert.NoError(t, err, "Must return no errors") 499 | 500 | value = subTree.GetIntUnsecured() 501 | 502 | assert.Equal(t, 0, value, "Must return value") 503 | 504 | subTree, err = envTree.FindSubTree("ENVH", "TEST1", "TEST2", "STRING") 505 | 506 | assert.NoError(t, err, "Must return no errors") 507 | 508 | value = subTree.GetIntUnsecured() 509 | 510 | assert.Equal(t, 0, value, "Must return empty string") 511 | } 512 | 513 | func TestGetBoolFromTree(t *testing.T) { 514 | setEnv("ENVH_TEST1_TEST2_BOOL", "true") 515 | setEnv("ENVH_TEST1_TEST2_STRING", "test") 516 | 517 | envTree, err := NewEnvTree("ENVH", "_") 518 | 519 | assert.NoError(t, err, "Must returns no error") 520 | 521 | subTree, err := envTree.FindSubTree("ENVH", "TEST1", "TEST2", "BOOL") 522 | 523 | assert.NoError(t, err, "Must return no errors") 524 | 525 | value, err := subTree.GetBool() 526 | 527 | assert.NoError(t, err, "Must return no errors") 528 | assert.Equal(t, true, value, "Must return value") 529 | 530 | subTree, err = envTree.FindSubTree("ENVH", "TEST1", "TEST2") 531 | 532 | assert.NoError(t, err, "Must return no errors") 533 | 534 | value, err = subTree.GetBool() 535 | 536 | assert.EqualError(t, err, "Variable not found", "Must return an error when variable can't be found") 537 | assert.Equal(t, false, value, "Must return value") 538 | 539 | subTree, err = envTree.FindSubTree("ENVH", "TEST1", "TEST2", "STRING") 540 | 541 | assert.NoError(t, err, "Must return no errors") 542 | 543 | value, err = subTree.GetBool() 544 | 545 | assert.EqualError(t, err, `Value "test" can't be converted to type "bool"`, "Must return an error when variable can't be converted") 546 | assert.Equal(t, false, value, "Must return empty string") 547 | } 548 | 549 | func TestGetBoolUnsecuredFromTree(t *testing.T) { 550 | setEnv("ENVH_TEST1_TEST2_BOOL", "true") 551 | setEnv("ENVH_TEST1_TEST2_STRING", "test") 552 | 553 | envTree, err := NewEnvTree("ENVH", "_") 554 | 555 | assert.NoError(t, err, "Must returns no error") 556 | 557 | subTree, err := envTree.FindSubTree("ENVH", "TEST1", "TEST2", "BOOL") 558 | 559 | assert.NoError(t, err, "Must return no errors") 560 | 561 | value := subTree.GetBoolUnsecured() 562 | 563 | assert.Equal(t, true, value, "Must return value") 564 | 565 | subTree, err = envTree.FindSubTree("ENVH", "TEST1", "TEST2") 566 | 567 | assert.NoError(t, err, "Must return no errors") 568 | 569 | value = subTree.GetBoolUnsecured() 570 | 571 | assert.Equal(t, false, value, "Must return value") 572 | 573 | subTree, err = envTree.FindSubTree("ENVH", "TEST1", "TEST2", "STRING") 574 | 575 | assert.NoError(t, err, "Must return no errors") 576 | 577 | value = subTree.GetBoolUnsecured() 578 | 579 | assert.Equal(t, false, value, "Must return empty string") 580 | } 581 | 582 | func TestGetFloatFromTree(t *testing.T) { 583 | setEnv("ENVH_TEST1_TEST2_FLOAT", "0.01") 584 | setEnv("ENVH_TEST1_TEST2_STRING", "test") 585 | 586 | envTree, err := NewEnvTree("ENVH", "_") 587 | 588 | assert.NoError(t, err, "Must returns no error") 589 | 590 | subTree, err := envTree.FindSubTree("ENVH", "TEST1", "TEST2", "FLOAT") 591 | 592 | assert.NoError(t, err, "Must return no errors") 593 | 594 | value, err := subTree.GetFloat() 595 | 596 | assert.NoError(t, err, "Must return no errors") 597 | assert.Equal(t, float32(0.01), value, "Must return value") 598 | 599 | subTree, err = envTree.FindSubTree("ENVH", "TEST1", "TEST2") 600 | 601 | assert.NoError(t, err, "Must return no errors") 602 | 603 | value, err = subTree.GetFloat() 604 | 605 | assert.EqualError(t, err, "Variable not found", "Must return an error when variable can't be found") 606 | assert.Equal(t, float32(0), value, "Must return value") 607 | 608 | subTree, err = envTree.FindSubTree("ENVH", "TEST1", "TEST2", "STRING") 609 | 610 | assert.NoError(t, err, "Must return no errors") 611 | 612 | value, err = subTree.GetFloat() 613 | 614 | assert.EqualError(t, err, `Value "test" can't be converted to type "float"`, "Must return an error when variable can't be converted") 615 | assert.Equal(t, float32(0), value, "Must return empty string") 616 | } 617 | 618 | func TestGetFloatUnsecuredFromTree(t *testing.T) { 619 | setEnv("ENVH_TEST1_TEST2_FLOAT", "0.01") 620 | setEnv("ENVH_TEST1_TEST2_STRING", "test") 621 | 622 | envTree, err := NewEnvTree("ENVH", "_") 623 | 624 | assert.NoError(t, err, "Must returns no error") 625 | 626 | subTree, err := envTree.FindSubTree("ENVH", "TEST1", "TEST2", "FLOAT") 627 | 628 | assert.NoError(t, err, "Must return no errors") 629 | 630 | value := subTree.GetFloatUnsecured() 631 | 632 | assert.Equal(t, float32(0.01), value, "Must return value") 633 | 634 | subTree, err = envTree.FindSubTree("ENVH", "TEST1", "TEST2") 635 | 636 | assert.NoError(t, err, "Must return no errors") 637 | 638 | value = subTree.GetFloatUnsecured() 639 | 640 | assert.Equal(t, float32(0), value, "Must return value") 641 | 642 | subTree, err = envTree.FindSubTree("ENVH", "TEST1", "TEST2", "STRING") 643 | 644 | assert.NoError(t, err, "Must return no errors") 645 | 646 | value = subTree.GetFloatUnsecured() 647 | 648 | assert.Equal(t, float32(0), value, "Must return empty string") 649 | } 650 | 651 | func TestGetChildrenKeys(t *testing.T) { 652 | setEnv("KEY1", "test") 653 | setEnv("KEY2", "test") 654 | setEnv("KEY3", "test") 655 | 656 | envTree, err := NewEnvTree("^KEY[0-9]", " ") 657 | 658 | assert.NoError(t, err, "Must returns no errors") 659 | 660 | result := envTree.GetChildrenKeys() 661 | 662 | sort.Strings(result) 663 | 664 | assert.Equal(t, []string{"KEY1", "KEY2", "KEY3"}, result, "Must returns an array of all child keys") 665 | 666 | subTree, err := envTree.FindSubTree("KEY1") 667 | 668 | assert.NoError(t, err, "Must returns no errors") 669 | 670 | assert.Empty(t, subTree.GetChildrenKeys(), "Must returns an empty array of keys") 671 | } 672 | 673 | func TestHasValue(t *testing.T) { 674 | setEnv("ENVH_KEY1_KEY2", "test") 675 | 676 | envTree, err := NewEnvTree("ENVH", "_") 677 | 678 | assert.NoError(t, err, "Must returns no errors") 679 | 680 | subTree, err := envTree.FindSubTree("ENVH", "KEY1", "KEY2") 681 | 682 | assert.NoError(t, err, "Must returns no errors") 683 | 684 | result := subTree.HasValue() 685 | 686 | assert.True(t, result, "Must returns true, sub tree has value") 687 | 688 | subTree, err = envTree.FindSubTree("ENVH", "KEY1") 689 | 690 | assert.NoError(t, err, "Must returns no errors") 691 | 692 | result = subTree.HasValue() 693 | 694 | assert.False(t, result, "Must returns false, sub tree has value") 695 | } 696 | 697 | func TestGetKey(t *testing.T) { 698 | setEnv("KEY1", "test") 699 | 700 | envTree, err := NewEnvTree("KEY[0-9]", " ") 701 | 702 | assert.NoError(t, err, "Must returns no errors") 703 | 704 | result := envTree.GetKey() 705 | 706 | assert.Equal(t, result, "", "Must returns an empty string, original root node has no value") 707 | 708 | subTree, err := envTree.FindSubTree("KEY1") 709 | 710 | assert.NoError(t, err, "Must returns no errors") 711 | 712 | result = subTree.GetKey() 713 | 714 | assert.Equal(t, result, "KEY1", "Must returns key") 715 | } 716 | 717 | func TestPopulateStruct(t *testing.T) { 718 | setEnv("TEST_WHATEVER", "string") 719 | 720 | type TEST struct { 721 | WHATEVER string 722 | } 723 | 724 | envTree, err := NewEnvTree("TEST", "_") 725 | 726 | assert.NoError(t, err) 727 | 728 | actual := TEST{} 729 | 730 | err = envTree.PopulateStruct(&actual) 731 | 732 | assert.NoError(t, err) 733 | assert.Equal(t, TEST{"string"}, actual) 734 | 735 | restoreEnvs() 736 | } 737 | 738 | func TestPopulateStructWithStrictModeWithAnError(t *testing.T) { 739 | setEnv("TEST_STRING", "string") 740 | 741 | type TEST struct { 742 | WHATEVER string 743 | } 744 | 745 | envTree, err := NewEnvTree("TEST", "_") 746 | 747 | assert.NoError(t, err) 748 | 749 | actual := TEST{} 750 | 751 | err = envTree.PopulateStructWithStrictMode(&actual) 752 | 753 | assert.EqualError(t, err, "Variable not found") 754 | 755 | restoreEnvs() 756 | } 757 | --------------------------------------------------------------------------------