├── .gitignore ├── LICENSE.txt ├── README.md ├── envstruct.go ├── envstruct_suite_test.go ├── envstruct_test.go ├── example └── example.go ├── helheim_test.go ├── report.go ├── report_test.go ├── report_url_old_test.go └── report_url_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Brady Love 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![GoDoc](https://godoc.org/github.com/bradylove/envstruct?status.png)](https://godoc.org/github.com/bradylove/envstruct) 2 | 3 | # envstruct 4 | 5 | ## Deprecated This package is no longer maintained and has been moved to [code.cloudfoundry.org/go-envstruct](https://code.cloudfoundry.org/go-envstruct) 6 | 7 | envstruct is a simple library for populating values on structs from environment 8 | variables. 9 | 10 | ## Usage 11 | 12 | Export some environment variables. 13 | 14 | ``` 15 | $ export HOST_IP="127.0.0.1" 16 | $ export HOST_PORT="443" 17 | $ export PASSWORD="abc123" 18 | ``` 19 | 20 | *Note:* The environment variables are case 21 | sensitive. The casing of the set environment variable must match the casing in 22 | the struct tag. 23 | 24 | Write some code. In this example, `Ip` requires that the `HOST_IP` environment variable is set to non empty value and 25 | `Port` defaults to `80` if `HOST_PORT` is an empty value. Then we use the `envstruct.WriteReport()` to print a 26 | table with a report of what fields are on the struct, the type, the environment variable where the value is read from, 27 | whether or not it is required, and the value. If using when `envstruct.WriteReport()` you wish to omit a sensitive 28 | value you can add `noreport` to the struct tag as shown with `Password` 29 | 30 | ``` 31 | package main 32 | 33 | import "github.com/bradylove/envstruct" 34 | 35 | type HostInfo struct { 36 | IP string `env:"HOST_IP,required"` 37 | Password string `env:"PASSWORD,noreport"` 38 | Port int `env:"HOST_PORT"` 39 | } 40 | 41 | func main() { 42 | hi := HostInfo{Port: 80} 43 | 44 | err := envstruct.Load(&hi) 45 | if err != nil { 46 | panic(err) 47 | } 48 | 49 | envstruct.WriteReport(&hi) 50 | } 51 | ``` 52 | 53 | Run your code and rejoice! 54 | 55 | ``` 56 | $ go run example/example.go 57 | FIELD NAME: TYPE: ENV: REQUIRED: VALUE: 58 | Ip string HOST_IP true 127.0.0.1 59 | Password string PASSWORD false (OMITTED) 60 | Port int HOST_PORT false 80 61 | ``` 62 | 63 | ## Supported Types 64 | 65 | - [x] string 66 | - [x] bool (`true` and `1` results in true value, anything else results in false value) 67 | - [x] int 68 | - [x] int8 69 | - [x] int16 70 | - [x] int32 71 | - [x] int64 72 | - [x] uint 73 | - [x] uint8 74 | - [x] uint16 75 | - [x] uint32 76 | - [x] uint64 77 | - [ ] float32 78 | - [ ] float64 79 | - [ ] complex64 80 | - [ ] complex128 81 | - [x] []slice (Slices of any other supported type. Environment variable should have coma separated values) 82 | - [x] time.Duration 83 | 84 | ## Running Tests 85 | 86 | Run tests using ginkgo. 87 | 88 | ``` 89 | $ go get github.com/apoydence/eachers 90 | $ go get github.com/onsi/ginkgo/ginkgo 91 | $ go get github.com/onsi/gomega 92 | $ ginkgo 93 | ``` 94 | 95 | ### MIT License 96 | 97 | Copyright (c) 2016 Brady Love 98 | 99 | Permission is hereby granted, free of charge, to any person obtaining a copy of 100 | this software and associated documentation files (the "Software"), to deal in 101 | the Software without restriction, including without limitation the rights to 102 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 103 | of the Software, and to permit persons to whom the Software is furnished to do 104 | so, subject to the following conditions: 105 | 106 | The above copyright notice and this permission notice shall be included in all 107 | copies or substantial portions of the Software. 108 | 109 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 110 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 111 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 112 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 113 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 114 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 115 | -------------------------------------------------------------------------------- /envstruct.go: -------------------------------------------------------------------------------- 1 | package envstruct 2 | 3 | import ( 4 | "fmt" 5 | "net/url" 6 | "os" 7 | "reflect" 8 | "strconv" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | const ( 14 | indexEnvVar = 0 15 | 16 | tagRequired = "required" 17 | tagNoReport = "noreport" 18 | ) 19 | 20 | // Unmarshaller is a type which unmarshals itself from an environment variable. 21 | type Unmarshaller interface { 22 | UnmarshalEnv(v string) error 23 | } 24 | 25 | // Load will use the `env` tags from a struct to populate the structs values and 26 | // perform validations. 27 | func Load(t interface{}) error { 28 | val := reflect.ValueOf(t).Elem() 29 | 30 | for i := 0; i < val.NumField(); i++ { 31 | valueField := val.Field(i) 32 | typeField := val.Type().Field(i) 33 | tag := typeField.Tag 34 | 35 | tagProperties := extractSliceInputs(tag.Get("env")) 36 | envVar := tagProperties[indexEnvVar] 37 | envVal := os.Getenv(envVar) 38 | 39 | required := tagPropertiesContains(tagProperties, tagRequired) 40 | 41 | if isInvalid(envVal, required) { 42 | return fmt.Errorf("%s is required but was empty", envVar) 43 | } 44 | 45 | if envVal == "" { 46 | continue 47 | } 48 | 49 | err := setField(valueField, envVal) 50 | if err != nil { 51 | return err 52 | } 53 | } 54 | 55 | return nil 56 | } 57 | 58 | func tagPropertiesContains(properties []string, match string) bool { 59 | for _, v := range properties { 60 | if v == match { 61 | return true 62 | } 63 | } 64 | 65 | return false 66 | } 67 | 68 | func unmarshaller(v reflect.Value) (Unmarshaller, bool) { 69 | if unmarshaller, ok := v.Interface().(Unmarshaller); ok { 70 | return unmarshaller, ok 71 | } 72 | if v.CanAddr() { 73 | return unmarshaller(v.Addr()) 74 | } 75 | return nil, false 76 | } 77 | 78 | func setField(value reflect.Value, input string) error { 79 | if unmarshaller, ok := unmarshaller(value); ok { 80 | return unmarshaller.UnmarshalEnv(input) 81 | } 82 | switch value.Type() { 83 | case reflect.TypeOf(time.Second): 84 | return setDuration(value, input) 85 | case reflect.TypeOf(&url.URL{}): 86 | return setURL(value, input) 87 | } 88 | 89 | switch value.Kind() { 90 | case reflect.String: 91 | return setString(value, input) 92 | case reflect.Bool: 93 | return setBool(value, input) 94 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: 95 | return setInt(value, input) 96 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: 97 | return setUint(value, input) 98 | case reflect.Slice: 99 | return setSlice(value, input) 100 | } 101 | 102 | return nil 103 | } 104 | 105 | func extractSliceInputs(input string) []string { 106 | inputs := strings.Split(input, ",") 107 | 108 | for i, v := range inputs { 109 | inputs[i] = strings.TrimSpace(v) 110 | } 111 | 112 | return inputs 113 | } 114 | 115 | func isInvalid(input string, required bool) bool { 116 | return required && input == "" 117 | } 118 | 119 | func setDuration(value reflect.Value, input string) error { 120 | d, err := time.ParseDuration(input) 121 | if err != nil { 122 | return err 123 | } 124 | 125 | value.Set(reflect.ValueOf(d)) 126 | 127 | return nil 128 | } 129 | 130 | func setURL(value reflect.Value, input string) error { 131 | u, err := url.Parse(input) 132 | if err != nil { 133 | return err 134 | } 135 | 136 | value.Set(reflect.ValueOf(u)) 137 | 138 | return nil 139 | } 140 | 141 | func setString(value reflect.Value, input string) error { 142 | value.SetString(input) 143 | 144 | return nil 145 | } 146 | 147 | func setBool(value reflect.Value, input string) error { 148 | value.SetBool(input == "true" || input == "1") 149 | 150 | return nil 151 | } 152 | 153 | func setInt(value reflect.Value, input string) error { 154 | n, err := strconv.ParseInt(input, 10, 64) 155 | if err != nil { 156 | return err 157 | } 158 | 159 | value.SetInt(int64(n)) 160 | 161 | return nil 162 | } 163 | 164 | func setUint(value reflect.Value, input string) error { 165 | n, err := strconv.ParseUint(input, 10, 64) 166 | if err != nil { 167 | return err 168 | } 169 | 170 | value.SetUint(uint64(n)) 171 | 172 | return nil 173 | } 174 | 175 | func setSlice(value reflect.Value, input string) error { 176 | inputs := extractSliceInputs(input) 177 | 178 | rs := reflect.MakeSlice(value.Type(), len(inputs), len(inputs)) 179 | for i, val := range inputs { 180 | err := setField(rs.Index(i), val) 181 | if err != nil { 182 | return err 183 | } 184 | } 185 | 186 | value.Set(rs) 187 | 188 | return nil 189 | } 190 | -------------------------------------------------------------------------------- /envstruct_suite_test.go: -------------------------------------------------------------------------------- 1 | //go:generate hel 2 | 3 | package envstruct_test 4 | 5 | import ( 6 | "net/url" 7 | "time" 8 | 9 | . "github.com/onsi/ginkgo" 10 | . "github.com/onsi/gomega" 11 | 12 | "testing" 13 | ) 14 | 15 | var ( 16 | baseEnvVars = map[string]string{ 17 | "STRING_THING": "stringy thingy", 18 | "REQUIRED_THING": "im so required", 19 | "BOOL_THING": "true", 20 | "INT_THING": "100", 21 | "INT8_THING": "20", 22 | "INT16_THING": "2000", 23 | "INT32_THING": "200000", 24 | "INT64_THING": "200000000", 25 | "UINT_THING": "100", 26 | "UINT8_THING": "20", 27 | "UINT16_THING": "2000", 28 | "UINT32_THING": "200000", 29 | "UINT64_THING": "200000000", 30 | "STRING_SLICE_THING": "one,two,three", 31 | "INT_SLICE_THING": "1,2,3", 32 | "DURATION_THING": "2s", 33 | "URL_THING": "http://github.com/some/path", 34 | "UNMARSHALLER_POINTER": "pointer", 35 | "UNMARSHALLER_VALUE": "value", 36 | "CaSe_SeNsItIvE_ThInG": "case sensitive", 37 | } 38 | ) 39 | 40 | type LargeTestStruct struct { 41 | NonEnvThing string 42 | DefaultThing string `env:"DEFAULT_THING"` 43 | StringThing string `env:"STRING_THING"` 44 | RequiredThing string `env:"REQUIRED_THING,noreport,required"` 45 | CaseSensitiveThing string `env:"CaSe_SeNsItIvE_ThInG"` 46 | 47 | BoolThing bool `env:"BOOL_THING"` 48 | 49 | IntThing int `env:"INT_THING"` 50 | Int8Thing int8 `env:"INT8_THING"` 51 | Int16Thing int16 `env:"INT16_THING"` 52 | Int32Thing int32 `env:"INT32_THING"` 53 | Int64Thing int64 `env:"INT64_THING"` 54 | UintThing uint `env:"UINT_THING"` 55 | Uint8Thing uint8 `env:"UINT8_THING"` 56 | Uint16Thing uint16 `env:"UINT16_THING"` 57 | Uint32Thing uint32 `env:"UINT32_THING"` 58 | Uint64Thing uint64 `env:"UINT64_THING"` 59 | 60 | StringSliceThing []string `env:"STRING_SLICE_THING"` 61 | IntSliceThing []int `env:"INT_SLICE_THING"` 62 | 63 | DurationThing time.Duration `env:"DURATION_THING"` 64 | URLThing *url.URL `env:"URL_THING"` 65 | 66 | UnmarshallerPointer *mockUnmarshaller `env:"UNMARSHALLER_POINTER"` 67 | UnmarshallerValue mockUnmarshaller `env:"UNMARSHALLER_VALUE"` 68 | } 69 | 70 | type SmallTestStruct struct { 71 | HiddenThing string `env:"HIDDEN_THING,noreport"` 72 | StringThing string `env:"STRING_THING"` 73 | BoolThing bool `env:"BOOL_THING"` 74 | IntThing int `env:"INT_THING"` 75 | URLThing *url.URL `env:"URL_THING"` 76 | StringSliceThing []string `env:"STRING_SLICE_THING"` 77 | CaseSensitiveThing string `env:"CaSe_SeNsItIvE_ThInG"` 78 | } 79 | 80 | func TestEnvstruct(t *testing.T) { 81 | RegisterFailHandler(Fail) 82 | RunSpecs(t, "Envstruct Suite") 83 | } 84 | -------------------------------------------------------------------------------- /envstruct_test.go: -------------------------------------------------------------------------------- 1 | package envstruct_test 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | 7 | "github.com/bradylove/envstruct" 8 | 9 | "fmt" 10 | "time" 11 | 12 | . "github.com/apoydence/eachers" 13 | . "github.com/onsi/ginkgo" 14 | . "github.com/onsi/gomega" 15 | ) 16 | 17 | var _ = Describe("envstruct", func() { 18 | Describe("Load()", func() { 19 | var ( 20 | ts LargeTestStruct 21 | loadError error 22 | envVars map[string]string 23 | ) 24 | 25 | BeforeEach(func() { 26 | ts = LargeTestStruct{} 27 | ts.UnmarshallerPointer = newMockUnmarshaller() 28 | ts.UnmarshallerPointer.UnmarshalEnvOutput.Ret0 <- nil 29 | um := newMockUnmarshaller() 30 | ts.UnmarshallerValue = *um 31 | ts.UnmarshallerValue.UnmarshalEnvOutput.Ret0 <- nil 32 | 33 | envVars = make(map[string]string) 34 | for k, v := range baseEnvVars { 35 | envVars[k] = v 36 | } 37 | }) 38 | 39 | JustBeforeEach(func() { 40 | for k, v := range envVars { 41 | os.Setenv(k, v) 42 | } 43 | }) 44 | 45 | Context("when load is successful", func() { 46 | JustBeforeEach(func() { 47 | loadError = envstruct.Load(&ts) 48 | }) 49 | 50 | AfterEach(func() { 51 | for k := range envVars { 52 | os.Setenv(k, "") 53 | } 54 | }) 55 | 56 | It("does not return an error", func() { 57 | Expect(loadError).ToNot(HaveOccurred()) 58 | }) 59 | 60 | Context("with unmarshallers", func() { 61 | It("passes the value to the pointer field", func() { 62 | Expect(ts.UnmarshallerPointer.UnmarshalEnvInput).To(BeCalled( 63 | With("pointer"), 64 | )) 65 | }) 66 | 67 | It("passes the value to the value field's address", func() { 68 | Expect(ts.UnmarshallerValue.UnmarshalEnvInput).To(BeCalled( 69 | With("value"), 70 | )) 71 | }) 72 | }) 73 | 74 | Context("with strings", func() { 75 | It("populates the string thing", func() { 76 | Expect(ts.StringThing).To(Equal("stringy thingy")) 77 | }) 78 | }) 79 | 80 | Describe("case sensitiveity", func() { 81 | It("populates the case sensitive thing", func() { 82 | Expect(ts.CaseSensitiveThing).To(Equal("case sensitive")) 83 | }) 84 | }) 85 | 86 | Context("with bools", func() { 87 | Context("with 'true'", func() { 88 | It("is true", func() { 89 | Expect(ts.BoolThing).To(BeTrue()) 90 | }) 91 | }) 92 | 93 | Context("with 'false'", func() { 94 | BeforeEach(func() { 95 | envVars["BOOL_THING"] = "false" 96 | }) 97 | 98 | It("is true", func() { 99 | Expect(ts.BoolThing).To(BeFalse()) 100 | }) 101 | }) 102 | 103 | Context("with '1'", func() { 104 | BeforeEach(func() { 105 | envVars["BOOL_THING"] = "1" 106 | }) 107 | 108 | It("is true", func() { 109 | Expect(ts.BoolThing).To(BeTrue()) 110 | }) 111 | }) 112 | 113 | Context("with '0'", func() { 114 | BeforeEach(func() { 115 | envVars["BOOL_THING"] = "0" 116 | }) 117 | 118 | It("is false", func() { 119 | Expect(ts.BoolThing).To(BeFalse()) 120 | }) 121 | }) 122 | }) 123 | 124 | Context("with ints", func() { 125 | It("populates the int thing", func() { 126 | Expect(ts.IntThing).To(Equal(100)) 127 | }) 128 | 129 | It("populates the int 8 thing", func() { 130 | Expect(ts.Int8Thing).To(Equal(int8(20))) 131 | }) 132 | 133 | It("populates the int 16 thing", func() { 134 | Expect(ts.Int16Thing).To(Equal(int16(2000))) 135 | }) 136 | 137 | It("populates the int 32 thing", func() { 138 | Expect(ts.Int32Thing).To(Equal(int32(200000))) 139 | }) 140 | 141 | It("populates the int 64 thing", func() { 142 | Expect(ts.Int64Thing).To(Equal(int64(200000000))) 143 | }) 144 | }) 145 | 146 | Context("with uints", func() { 147 | It("populates the uint thing", func() { 148 | Expect(ts.UintThing).To(Equal(uint(100))) 149 | }) 150 | 151 | It("populates the uint 8 thing", func() { 152 | Expect(ts.Uint8Thing).To(Equal(uint8(20))) 153 | }) 154 | 155 | It("populates the uint 16 thing", func() { 156 | Expect(ts.Uint16Thing).To(Equal(uint16(2000))) 157 | }) 158 | 159 | It("populates the uint 32 thing", func() { 160 | Expect(ts.Uint32Thing).To(Equal(uint32(200000))) 161 | }) 162 | 163 | It("populates the uint 64 thing", func() { 164 | Expect(ts.Uint64Thing).To(Equal(uint64(200000000))) 165 | }) 166 | }) 167 | 168 | Context("with comma separated strings", func() { 169 | Context("slice of strings", func() { 170 | It("populates a slice of strings", func() { 171 | Expect(ts.StringSliceThing).To(Equal([]string{"one", "two", "three"})) 172 | }) 173 | 174 | Context("with leading and trailing spaces", func() { 175 | BeforeEach(func() { 176 | envVars["STRING_SLICE_THING"] = "one , two , three" 177 | }) 178 | 179 | It("populates a slice of strings", func() { 180 | Expect(ts.StringSliceThing).To(Equal([]string{"one", "two", "three"})) 181 | }) 182 | }) 183 | }) 184 | 185 | Context("slice of ints", func() { 186 | It("populates a slice of ints", func() { 187 | Expect(ts.IntSliceThing).To(Equal([]int{1, 2, 3})) 188 | }) 189 | }) 190 | }) 191 | 192 | Context("with structs", func() { 193 | It("parses the duration string", func() { 194 | Expect(ts.DurationThing).To(Equal(2 * time.Second)) 195 | }) 196 | 197 | It("parses the url string", func() { 198 | Expect(ts.URLThing.Scheme).To(Equal("http")) 199 | Expect(ts.URLThing.Host).To(Equal("github.com")) 200 | Expect(ts.URLThing.Path).To(Equal("/some/path")) 201 | }) 202 | }) 203 | }) 204 | 205 | Context("with defaults", func() { 206 | It("honors default values if env var is empty", func() { 207 | ts.DefaultThing = "Default Value" 208 | 209 | Expect(envstruct.Load(&ts)).To(Succeed()) 210 | Expect(ts.DefaultThing).To(Equal("Default Value")) 211 | }) 212 | }) 213 | 214 | Context("when load is unsuccessful", func() { 215 | Context("when a required environment variable is not given", func() { 216 | BeforeEach(func() { 217 | envVars["REQUIRED_THING"] = "" 218 | }) 219 | 220 | It("returns a validation error", func() { 221 | loadError = envstruct.Load(&ts) 222 | 223 | Expect(loadError).To(MatchError(fmt.Errorf("REQUIRED_THING is required but was empty"))) 224 | }) 225 | }) 226 | 227 | Context("with an invalid int", func() { 228 | BeforeEach(func() { 229 | envVars["INT_THING"] = "Hello!" 230 | }) 231 | 232 | It("returns an error", func() { 233 | Expect(envstruct.Load(&ts)).ToNot(Succeed()) 234 | }) 235 | }) 236 | 237 | Context("with an invalid uint", func() { 238 | BeforeEach(func() { 239 | envVars["UINT_THING"] = "Hello!" 240 | }) 241 | 242 | It("returns an error", func() { 243 | Expect(envstruct.Load(&ts)).ToNot(Succeed()) 244 | }) 245 | }) 246 | 247 | Context("with a failing unmarshaller pointer", func() { 248 | BeforeEach(func() { 249 | ts.UnmarshallerPointer.UnmarshalEnvOutput.Ret0 = make(chan error, 100) 250 | ts.UnmarshallerPointer.UnmarshalEnvOutput.Ret0 <- errors.New("failed to unmarshal") 251 | }) 252 | 253 | It("returns an error", func() { 254 | Expect(envstruct.Load(&ts)).ToNot(Succeed()) 255 | }) 256 | }) 257 | 258 | Context("with a failing unmarshaller value", func() { 259 | BeforeEach(func() { 260 | ts.UnmarshallerValue.UnmarshalEnvOutput.Ret0 = make(chan error, 100) 261 | ts.UnmarshallerValue.UnmarshalEnvOutput.Ret0 <- errors.New("failed to unmarshal") 262 | }) 263 | 264 | It("returns an error", func() { 265 | Expect(envstruct.Load(&ts)).ToNot(Succeed()) 266 | }) 267 | }) 268 | }) 269 | }) 270 | }) 271 | -------------------------------------------------------------------------------- /example/example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/bradylove/envstruct" 4 | 5 | type HostInfo struct { 6 | IP string `env:"HOST_IP,required"` 7 | Password string `env:"PASSWORD,noreport"` 8 | Port int `env:"HOST_PORT"` 9 | } 10 | 11 | func main() { 12 | hi := HostInfo{Port: 80} 13 | 14 | err := envstruct.Load(&hi) 15 | if err != nil { 16 | panic(err) 17 | } 18 | 19 | envstruct.WriteReport(&hi) 20 | } 21 | -------------------------------------------------------------------------------- /helheim_test.go: -------------------------------------------------------------------------------- 1 | // This file was generated by github.com/nelsam/hel. Do not 2 | // edit this code by hand unless you *really* know what you're 3 | // doing. Expect any changes made manually to be overwritten 4 | // the next time hel regenerates this file. 5 | 6 | package envstruct_test 7 | 8 | type mockUnmarshaller struct { 9 | UnmarshalEnvCalled chan bool 10 | UnmarshalEnvInput struct { 11 | V chan string 12 | } 13 | UnmarshalEnvOutput struct { 14 | Ret0 chan error 15 | } 16 | } 17 | 18 | func newMockUnmarshaller() *mockUnmarshaller { 19 | m := &mockUnmarshaller{} 20 | m.UnmarshalEnvCalled = make(chan bool, 100) 21 | m.UnmarshalEnvInput.V = make(chan string, 100) 22 | m.UnmarshalEnvOutput.Ret0 = make(chan error, 100) 23 | return m 24 | } 25 | func (m *mockUnmarshaller) UnmarshalEnv(v string) error { 26 | m.UnmarshalEnvCalled <- true 27 | m.UnmarshalEnvInput.V <- v 28 | return <-m.UnmarshalEnvOutput.Ret0 29 | } 30 | -------------------------------------------------------------------------------- /report.go: -------------------------------------------------------------------------------- 1 | package envstruct 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "reflect" 8 | "strings" 9 | "text/tabwriter" 10 | ) 11 | 12 | var ReportWriter io.Writer = os.Stdout 13 | 14 | // WriteReport will take a struct that is setup for envstruct and print 15 | // out a report containing the struct field name, field type, environment 16 | // variable for that field, whether or not the field is required and 17 | // the value of that field. The report is written to `ReportWriter` 18 | // which defaults to `os.StdOut`. Sensetive values that you would not 19 | // want appearing in logs can be omitted with the `noreport` value in 20 | // the `env` struct tag. 21 | func WriteReport(t interface{}) error { 22 | w := tabwriter.NewWriter(ReportWriter, 0, 8, 2, ' ', 0) 23 | 24 | fmt.Fprintln(w, "FIELD NAME:\tTYPE:\tENV:\tREQUIRED:\tVALUE:") 25 | 26 | val := reflect.ValueOf(t).Elem() 27 | for i := 0; i < val.NumField(); i++ { 28 | valueField := val.Field(i) 29 | typeField := val.Type().Field(i) 30 | tag := typeField.Tag 31 | 32 | tagProperties := extractSliceInputs(tag.Get("env")) 33 | envVar := strings.ToUpper(tagProperties[indexEnvVar]) 34 | 35 | isRequired := tagPropertiesContains(tagProperties, tagRequired) 36 | 37 | var displayedValue interface{} = valueField 38 | if tagPropertiesContains(tagProperties, tagNoReport) { 39 | displayedValue = "(OMITTED)" 40 | } 41 | 42 | fmt.Fprintln(w, fmt.Sprintf( 43 | "%v\t%v\t%v\t%t\t%v", 44 | typeField.Name, 45 | valueField.Type(), 46 | envVar, 47 | isRequired, 48 | displayedValue)) 49 | } 50 | 51 | return w.Flush() 52 | } 53 | -------------------------------------------------------------------------------- /report_test.go: -------------------------------------------------------------------------------- 1 | package envstruct_test 2 | 3 | import ( 4 | "bytes" 5 | 6 | "github.com/bradylove/envstruct" 7 | 8 | "os" 9 | 10 | . "github.com/onsi/ginkgo" 11 | . "github.com/onsi/gomega" 12 | ) 13 | 14 | var _ = Describe("Report", func() { 15 | var ( 16 | ts SmallTestStruct 17 | outputText string 18 | ) 19 | 20 | Describe("Report()", func() { 21 | BeforeEach(func() { 22 | for k, v := range baseEnvVars { 23 | os.Setenv(k, v) 24 | } 25 | 26 | err := envstruct.Load(&ts) 27 | Expect(err).ToNot(HaveOccurred()) 28 | 29 | outputBuffer := bytes.NewBuffer(nil) 30 | envstruct.ReportWriter = outputBuffer 31 | 32 | err = envstruct.WriteReport(&ts) 33 | Expect(err).ToNot(HaveOccurred()) 34 | 35 | outputText = string(outputBuffer.Bytes()) 36 | }) 37 | 38 | It("prints a report of the given envstruct struct", func() { 39 | Expect(outputText).To(Equal(expectedReportOutput)) 40 | }) 41 | }) 42 | }) 43 | 44 | const ( 45 | expectedReportOutput = `FIELD NAME: TYPE: ENV: REQUIRED: VALUE: 46 | HiddenThing string HIDDEN_THING false (OMITTED) 47 | StringThing string STRING_THING false stringy thingy 48 | BoolThing bool BOOL_THING false true 49 | IntThing int INT_THING false 100 50 | URLThing *url.URL URL_THING false &{http github.com /some/path false } 51 | StringSliceThing []string STRING_SLICE_THING false [one two three] 52 | CaseSensitiveThing string CASE_SENSITIVE_THING false case sensitive 53 | ` 54 | ) 55 | -------------------------------------------------------------------------------- /report_url_old_test.go: -------------------------------------------------------------------------------- 1 | // +build !go1.7 2 | 3 | package envstruct_test 4 | 5 | const urlOutput = "&{http github.com /some/path }" 6 | -------------------------------------------------------------------------------- /report_url_test.go: -------------------------------------------------------------------------------- 1 | // +build go1.7 2 | 3 | package envstruct_test 4 | 5 | const urlOutput = "&{http github.com /some/path false }" 6 | --------------------------------------------------------------------------------