├── .gitignore ├── LICENSE ├── README.md ├── env.go ├── env_test.go └── go.mod /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Magic Numbers™ 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Babyenv 2 | ======= 3 | 4 | [![GoDoc Badge](https://godoc.org/github.com/meowgorithm/babylogger?status.svg)](http://godoc.org/github.com/meowgorithm/babyenv) 5 | 6 | Package babyenv collects environment variables and places them in corresponding 7 | struct fields. It aims to reduce the boilerplate in reading data from the 8 | environment. 9 | 10 | The struct should contain `env` tags indicating the names of corresponding 11 | environment variables. The values of those environment variables will be then 12 | collected and placed into the struct. If nothing is found, struct fields will 13 | be given their default values (for example, `bool`s will be `false`). 14 | 15 | ```go 16 | type config struct { 17 | Name string `env:"NAME"` 18 | } 19 | ``` 20 | 21 | Default values can also be provided in the `default` tag. 22 | 23 | ```go 24 | type config struct { 25 | Name string `env:"NAME" default:"Jane"` 26 | } 27 | ``` 28 | 29 | A 'required' flag can also be set in the following format: 30 | 31 | ```go 32 | type config struct { 33 | Name string `env:"NAME,required"` 34 | } 35 | ``` 36 | 37 | If a required flag is set the 'default' tag will be ignored. 38 | 39 | 40 | ## Example 41 | 42 | ```go 43 | package main 44 | 45 | import ( 46 | "fmt" 47 | "os" 48 | "github.com/meowgorithm/babyenv" 49 | ) 50 | 51 | type config struct { 52 | Debug bool `env:"DEBUG"` 53 | Port string `env:"PORT" default:"8000"` 54 | Workers int `env:"WORKERS" default:"16"` 55 | Name string `env:"NAME,required"` 56 | } 57 | 58 | func main() { 59 | os.Setenv("DEBUG", "true") 60 | os.Setenv("WORKERS", "4") 61 | os.Setenv("NAME", "Jane") 62 | 63 | var cfg config 64 | if err := babyenv.Parse(&cfg); err != nil { 65 | log.Fatalf("could not get environment vars: %v", err) 66 | } 67 | 68 | fmt.Printf("%b\n%s\n%d\n%s", cfg.Debug, cfg.Port, cfg.Workers, cfg.Name) 69 | 70 | // Output: 71 | // true 72 | // 8000 73 | // 4 74 | // Jane 75 | } 76 | ``` 77 | 78 | 79 | ## Supported Types 80 | 81 | Currently, only the following types are supported: 82 | 83 | * `string` 84 | * `bool` 85 | * `int` 86 | * `int64` 87 | * `[]byte`/`[]uint8` 88 | * `*string` 89 | * `*bool` 90 | * `*int` 91 | * `*int64` 92 | * `*[]byte`/`*[]uint8` 93 | 94 | Pull requests are welcome, especially for new types. 95 | 96 | 97 | ## Credit 98 | 99 | This is entirely based on [caarlos0][carlos]’s [env][carlosenv] package. 100 | This one simply has a slightly different interface, and less functionality. 101 | 102 | [carlos]: https://github.com/caarlos0 103 | [carlosenv]: https://github.com/caarlos0/env 104 | 105 | 106 | ## LICENSE 107 | 108 | MIT 109 | -------------------------------------------------------------------------------- /env.go: -------------------------------------------------------------------------------- 1 | // Package babyenv collects environment variables and places them in 2 | // corresponding struct fields. It aims to reduce the boilerplate in reading 3 | // data from the environment. 4 | // 5 | // The struct should contain `env` tags indicating the names of corresponding 6 | // environment variables. The values of those environment variables will be 7 | // then collected and placed into the struct. If nothing is found, struct 8 | // fields will be given their default values (for example, `bool`s will be 9 | // `false`). 10 | // 11 | // type config struct { 12 | // Name string `env:"NAME"` 13 | // } 14 | // 15 | // Default values can also be provided in the `default` tag. 16 | // 17 | // `env:"NAME" default:"Jane"` 18 | // 19 | // A 'required' flag can also be set in the following format: 20 | // 21 | // `env:"NAME,required"` 22 | // 23 | // If a required flag is set the 'default' tag will be ignored. 24 | // 25 | // Only a few types are supported: string, bool, int, []byte, *string, *bool, 26 | // *int, *[]byte. An error will be returned if other types are attempted to 27 | // be processed. 28 | // 29 | // Example: 30 | // 31 | // package main 32 | // 33 | // import ( 34 | // "fmt" 35 | // "os" 36 | // "github.com/magicnumbers/babyenv" 37 | // ) 38 | // 39 | // type config struct { 40 | // Debug bool `env:"DEBUG"` 41 | // Port string `env:"PORT" default:"8000"` 42 | // Workers int `env:"WORKERS" default:"16"` 43 | // Name string `env:"NAME,required"` 44 | // } 45 | // 46 | // func main() { 47 | // os.Setenv("DEBUG", "true") 48 | // os.Setenv("WORKERS", "4") 49 | // os.Setenv("NAME", "Jane") 50 | // 51 | // var cfg config 52 | // if err := babyenv.Parse(&cfg); err != nil { 53 | // log.Fatalf("could not get environment vars: %v", err) 54 | // } 55 | // 56 | // fmt.Printf("%b\n%s\n%d\n%s", cfg.Debug, cfg.Port, cfg.Workers, cfg.Name) 57 | // 58 | // // Output: 59 | // // true 60 | // // 8000 61 | // // 4 62 | // // Jane 63 | // } 64 | package babyenv 65 | 66 | import ( 67 | "errors" 68 | "fmt" 69 | "os" 70 | "reflect" 71 | "strconv" 72 | "strings" 73 | ) 74 | 75 | var ( 76 | // ErrorNotAStructPointer indicates that we were expecting a pointer to a 77 | // struct but we didn't get it. This is returned when parsing a passed 78 | // struct. 79 | ErrorNotAStructPointer = errors.New("expected a pointer to a struct") 80 | ) 81 | 82 | // ErrorUnsettable is used when a field cannot be set 83 | type ErrorUnsettable struct { 84 | FieldName string 85 | } 86 | 87 | // Error implements the error interface 88 | func (e *ErrorUnsettable) Error() string { 89 | return fmt.Sprintf("can't set field %s", e.FieldName) 90 | } 91 | 92 | // ErrorUnsupportedType is used when we attempt to parse a struct field of an 93 | // unsupported type 94 | type ErrorUnsupportedType struct { 95 | Type reflect.Type 96 | } 97 | 98 | // Error implements the error interface 99 | func (e *ErrorUnsupportedType) Error() string { 100 | return fmt.Sprintf("unsupported type %v", e.Type) 101 | } 102 | 103 | // ErrorEnvVarRequired is used when a `required` flag is used and the value of 104 | // the corresponding environment variable is empty 105 | type ErrorEnvVarRequired struct { 106 | Name string 107 | } 108 | 109 | // Error implements the error interface 110 | func (e *ErrorEnvVarRequired) Error() string { 111 | return fmt.Sprintf("%s is required", e.Name) 112 | } 113 | 114 | // Parse parses a struct for environment variables, placing found values in the 115 | // struct, altering it. We look at the 'env' tag for the environment variable 116 | // names, and the 'default' for the default value to the corresponding 117 | // environment variable. 118 | func Parse(cfg interface{}) error { 119 | 120 | // Make sure we've got a pointer 121 | val := reflect.ValueOf(cfg) 122 | if val.Kind() != reflect.Ptr { 123 | return ErrorNotAStructPointer 124 | } 125 | 126 | // Make sure our pointer points to a struct 127 | ref := val.Elem() 128 | if ref.Kind() != reflect.Struct { 129 | return ErrorNotAStructPointer 130 | } 131 | 132 | return parseFields(ref) 133 | } 134 | 135 | // Interate over the fields of a struct, looking for `env` tags indicating 136 | // environment variable names and `default` inicating default values. We're 137 | // expecting a pointer to a struct here, and either environment variables or 138 | // defaults will be placed in the struct. If a non-struct pointer is passed we 139 | // return an error. 140 | // 141 | // Note that a required flag can also be passed in the form of: 142 | // 143 | // VarName string `env:"VAR_NAME,required"` 144 | // 145 | // If a required flag is set, and the environment variable is empty, the 146 | // `default` tag is ignored. 147 | func parseFields(ref reflect.Value) error { 148 | for i := 0; i < ref.NumField(); i++ { 149 | var ( 150 | field = ref.Field(i) 151 | fieldKind = ref.Field(i).Kind() 152 | fieldTags = ref.Type().Field(i).Tag 153 | fieldName = ref.Type().Field(i).Name 154 | envVarName string 155 | required bool 156 | ) 157 | 158 | tagVal := fieldTags.Get("env") 159 | if tagVal == "" || tagVal == "-" { 160 | continue 161 | } 162 | 163 | if !field.CanSet() { 164 | return &ErrorUnsettable{fieldName} 165 | } 166 | 167 | // The tag we're looking at will look something like one of these: 168 | // 169 | // `env:"NAME"` 170 | // `env:"NAME,required"` 171 | // 172 | // Here we split on the comma and sort out the parts. 173 | tagValParts := strings.Split(tagVal, ",") 174 | if len(tagValParts) == 0 { // This should never happen 175 | continue 176 | } else if len(tagValParts) >= 1 { 177 | envVarName = tagValParts[0] 178 | } 179 | if len(tagValParts) >= 2 && strings.TrimSpace(tagValParts[1]) == "required" { 180 | required = true 181 | } 182 | 183 | // Get the value of the environment var 184 | envVarVal := os.Getenv(envVarName) 185 | 186 | // Return an error if the required flag is set and the env var is empty 187 | if envVarVal == "" && required { 188 | return &ErrorEnvVarRequired{envVarName} 189 | } 190 | 191 | defaultVal := fieldTags.Get("default") 192 | 193 | // Is the situation such that we should set a default value? We only 194 | // do it if the value of the given environment varaiable is empty, and 195 | // we have a non-empty default value. 196 | shouldSetDefault := len(envVarVal) == 0 && len(defaultVal) > 0 && defaultVal != "-" 197 | 198 | // Set the field accoring to it's kind 199 | switch fieldKind { 200 | 201 | case reflect.String: 202 | if shouldSetDefault { 203 | field.SetString(defaultVal) 204 | continue 205 | } 206 | field.SetString(envVarVal) 207 | 208 | case reflect.Bool: 209 | if shouldSetDefault { 210 | if err := setBool(field, defaultVal); err != nil { 211 | return err 212 | } 213 | continue 214 | } 215 | if err := setBool(field, envVarVal); err != nil { 216 | return err 217 | } 218 | 219 | case reflect.Int: 220 | if shouldSetDefault { 221 | if err := setInt(field, defaultVal); err != nil { 222 | return err 223 | } 224 | continue 225 | } 226 | if err := setInt(field, envVarVal); err != nil { 227 | return err 228 | } 229 | 230 | case reflect.Int64: 231 | if shouldSetDefault { 232 | if err := setInt64(field, defaultVal); err != nil { 233 | return err 234 | } 235 | continue 236 | } 237 | if err := setInt64(field, envVarVal); err != nil { 238 | return err 239 | } 240 | 241 | // Slices are a whole can of worms 242 | case reflect.Slice: 243 | switch field.Type().Elem().Kind() { 244 | 245 | // []uint8 is an alias for []byte 246 | case reflect.Uint8: 247 | if shouldSetDefault { 248 | field.SetBytes([]byte(defaultVal)) 249 | continue 250 | } 251 | field.SetBytes([]byte(envVarVal)) 252 | 253 | default: 254 | return &ErrorUnsupportedType{field.Type()} 255 | 256 | } 257 | 258 | // Pointers are also a whole other can of worms 259 | case reflect.Ptr: 260 | ptr := field.Type().Elem() 261 | 262 | switch ptr.Kind() { 263 | 264 | case reflect.String: 265 | if shouldSetDefault { 266 | field.Set(reflect.ValueOf(&defaultVal)) 267 | continue 268 | } 269 | field.Set(reflect.ValueOf(&envVarVal)) 270 | 271 | case reflect.Bool: 272 | if shouldSetDefault { 273 | if err := setBoolPointer(field, defaultVal); err != nil { 274 | return err 275 | } 276 | continue 277 | } 278 | if err := setBoolPointer(field, envVarVal); err != nil { 279 | return err 280 | } 281 | 282 | case reflect.Int: 283 | if shouldSetDefault { 284 | if err := setIntPointer(field, defaultVal); err != nil { 285 | return err 286 | } 287 | continue 288 | } 289 | if err := setIntPointer(field, envVarVal); err != nil { 290 | return err 291 | } 292 | 293 | case reflect.Int64: 294 | if shouldSetDefault { 295 | if err := setInt64Pointer(field, defaultVal); err != nil { 296 | return err 297 | } 298 | continue 299 | } 300 | if err := setInt64Pointer(field, envVarVal); err != nil { 301 | return err 302 | } 303 | 304 | // A poiner to a slice!! Whole other level 305 | case reflect.Slice: 306 | 307 | switch ptr.Elem().Kind() { 308 | 309 | // *[]uint8 is an alias for *[]byte 310 | case reflect.Uint8: 311 | var byteSlice []byte 312 | if shouldSetDefault { 313 | byteSlice = []byte(defaultVal) 314 | } else { 315 | byteSlice = []byte(envVarVal) 316 | } 317 | field.Set(reflect.ValueOf(&byteSlice)) 318 | 319 | default: 320 | return &ErrorUnsupportedType{field.Type()} 321 | 322 | } 323 | 324 | default: 325 | return &ErrorUnsupportedType{field.Type()} 326 | } 327 | 328 | default: 329 | return &ErrorUnsupportedType{field.Type()} 330 | } 331 | 332 | } 333 | 334 | return nil 335 | } 336 | 337 | func setBool(v reflect.Value, s string) error { 338 | if s == "" { 339 | // Default to false 340 | v.SetBool(false) 341 | return nil 342 | } 343 | 344 | b, err := strconv.ParseBool(s) 345 | if err != nil { 346 | return err 347 | } 348 | v.SetBool(b) 349 | return nil 350 | } 351 | 352 | func setInt(v reflect.Value, s string) error { 353 | if s == "" { 354 | // Default to 0 355 | v.SetInt(0) 356 | return nil 357 | } 358 | 359 | n, err := strconv.ParseInt(s, 10, 32) 360 | if err != nil { 361 | return err 362 | } 363 | v.SetInt(n) 364 | return nil 365 | } 366 | 367 | func setInt64(v reflect.Value, s string) error { 368 | if s == "" { 369 | // Default to 0 370 | v.SetInt(0) 371 | return nil 372 | } 373 | 374 | n, err := strconv.ParseInt(s, 10, 64) 375 | if err != nil { 376 | return err 377 | } 378 | v.SetInt(n) 379 | return nil 380 | } 381 | 382 | func setBoolPointer(v reflect.Value, s string) error { 383 | if s == "" { 384 | // Default to false 385 | b := false 386 | v.Set(reflect.ValueOf(&b)) 387 | return nil 388 | } 389 | 390 | b, err := strconv.ParseBool(s) 391 | if err != nil { 392 | return err 393 | } 394 | 395 | v.Set(reflect.ValueOf(&b)) 396 | return nil 397 | } 398 | 399 | func setIntPointer(v reflect.Value, s string) error { 400 | if s == "" { 401 | // Default to 0 402 | n := 0 403 | v.Set(reflect.ValueOf(&n)) 404 | return nil 405 | } 406 | 407 | i64, err := strconv.ParseInt(s, 10, 32) 408 | if err != nil { 409 | return err 410 | } 411 | i := int(i64) 412 | 413 | v.Set(reflect.ValueOf(&i)) 414 | return nil 415 | } 416 | 417 | func setInt64Pointer(v reflect.Value, s string) error { 418 | if s == "" { 419 | // Default to 0 420 | n := 0 421 | v.Set(reflect.ValueOf(&n)) 422 | return nil 423 | } 424 | 425 | i, err := strconv.ParseInt(s, 10, 64) 426 | if err != nil { 427 | return err 428 | } 429 | 430 | v.Set(reflect.ValueOf(&i)) 431 | return nil 432 | } 433 | -------------------------------------------------------------------------------- /env_test.go: -------------------------------------------------------------------------------- 1 | package babyenv 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | "testing" 7 | ) 8 | 9 | func TestParse(t *testing.T) { 10 | type config struct { 11 | A bool `env:"A"` 12 | B string `env:"B"` 13 | C int `env:"C"` 14 | D []byte `env:"D"` 15 | E int64 `env:"E"` 16 | } 17 | 18 | a := true 19 | b := "xxx" 20 | c := 16 21 | d := []byte("yyy") 22 | var e int64 = 64 23 | 24 | os.Setenv("A", strconv.FormatBool(a)) 25 | os.Setenv("B", b) 26 | os.Setenv("C", strconv.FormatInt(int64(c), 10)) 27 | os.Setenv("D", string(d)) 28 | os.Setenv("E", strconv.FormatInt(e, 10)) 29 | 30 | var cfg config 31 | if err := Parse(&cfg); err != nil { 32 | t.Errorf("error while parsing: %v", err) 33 | return 34 | } 35 | 36 | if !cfg.A { 37 | t.Errorf("failed parsing bool; expected %#v, got %#v", a, cfg.A) 38 | } 39 | if cfg.B != b { 40 | t.Errorf("failed parsing string; expected %#v, got %#v", b, cfg.B) 41 | } 42 | if cfg.C != c { 43 | t.Errorf("failed parsing int; expected %#v, got %#v", c, cfg.C) 44 | } 45 | if cfg.D == nil { 46 | t.Errorf("failed parsing byte[]; expected %#v, got nil", d) 47 | } else if string(cfg.D) != string(d) { 48 | t.Errorf("failed parsing []byte; expected %#v, got %#v", d, cfg.D) 49 | } 50 | if cfg.E != e { 51 | t.Errorf("failed parsing int64; expected %#v, got %#v", c, cfg.E) 52 | } 53 | } 54 | 55 | func TestParseWithDefaults(t *testing.T) { 56 | type config struct { 57 | A bool `env:"A" default:"true"` 58 | B string `env:"B" default:"xxx"` 59 | C int `env:"C" default:"16"` 60 | D []byte `env:"D" default:"yyy"` 61 | E int64 `env:"E" default:"64"` 62 | } 63 | 64 | a := true 65 | b := "xxx" 66 | c := 16 67 | d := []byte("yyy") 68 | var e int64 = 64 69 | 70 | os.Unsetenv("A") 71 | os.Unsetenv("B") 72 | os.Unsetenv("C") 73 | os.Unsetenv("D") 74 | os.Unsetenv("E") 75 | 76 | var cfg config 77 | if err := Parse(&cfg); err != nil { 78 | t.Errorf("error while parsing: %v", err) 79 | return 80 | } 81 | 82 | if cfg.A != a { 83 | t.Errorf("failed parsing bool; expected %#v, got %#v", a, cfg.A) 84 | } 85 | if cfg.B != b { 86 | t.Errorf("failed parsing string; expected %#v, got %#v", b, cfg.B) 87 | } 88 | if cfg.C != c { 89 | t.Errorf("failed parsing int; expected %#v, got %#v", c, cfg.C) 90 | } 91 | if cfg.D == nil { 92 | t.Errorf("failed parsing byte[]; expected %#v, got nil", d) 93 | } else if string(cfg.D) != string(d) { 94 | t.Errorf("failed parsing []byte; expected %#v, got %#v", d, cfg.D) 95 | } 96 | if cfg.E != e { 97 | t.Errorf("failed parsing int64; expected %#v, got %#v", e, cfg.E) 98 | } 99 | } 100 | 101 | func TestParsePointers(t *testing.T) { 102 | type config struct { 103 | A *bool `env:"A"` 104 | B *string `env:"B"` 105 | C *int `env:"C"` 106 | D *[]byte `env:"D"` 107 | E *int64 `env:"E"` 108 | } 109 | 110 | a := true 111 | b := "xxx" 112 | c := 16 113 | d := []byte("yyy") 114 | var e int64 = 64 115 | 116 | os.Setenv("A", strconv.FormatBool(a)) 117 | os.Setenv("B", b) 118 | os.Setenv("C", strconv.FormatInt(int64(c), 10)) 119 | os.Setenv("D", string(d)) 120 | os.Setenv("E", strconv.FormatInt(e, 10)) 121 | 122 | var cfg config 123 | if err := Parse(&cfg); err != nil { 124 | t.Errorf("error while parsing: %v", err) 125 | return 126 | } 127 | 128 | if cfg.A == nil { 129 | t.Errorf("failed parsing *bool; expected %#v, got nil", a) 130 | } else if *cfg.A != a { 131 | t.Errorf("failed parsing *bool; expected %#v, got %#v", a, *cfg.A) 132 | } 133 | 134 | if cfg.B == nil { 135 | t.Errorf("failed parsing *string; expected %#v, got nil", b) 136 | } else if *cfg.B != b { 137 | t.Errorf("failed parsing *string; expected %#v, got %#v", b, *cfg.B) 138 | } 139 | 140 | if cfg.C == nil { 141 | t.Errorf("failed parsing *int; expected %#v, got nil", c) 142 | } else if *cfg.C != c { 143 | t.Errorf("failed parsing *int; expected %#v, got %#v", c, *cfg.C) 144 | } 145 | 146 | if cfg.D == nil { 147 | t.Errorf("failed parsing *[]byte; expected %#v, got nil", d) 148 | } else if string(*cfg.D) != string(d) { 149 | t.Errorf("failed parsing *[]byte; expected %#v, got %#v", d, *cfg.D) 150 | } 151 | 152 | if cfg.E == nil { 153 | t.Errorf("failed parsing *int64; expected %#v, got nil", e) 154 | } else if *cfg.E != e { 155 | t.Errorf("failed parsing *int64; expected %#v, got %#v", e, *cfg.E) 156 | } 157 | } 158 | 159 | func TestParsePointersWithDefaults(t *testing.T) { 160 | type config struct { 161 | A *bool `env:"A" default:"true"` 162 | B *string `env:"B" default:"xxx"` 163 | C *int `env:"C" default:"16"` 164 | D *[]byte `env:"D" default:"yyy"` 165 | } 166 | 167 | a := true 168 | b := "xxx" 169 | c := 16 170 | d := []byte("yyy") 171 | 172 | os.Unsetenv("A") 173 | os.Unsetenv("B") 174 | os.Unsetenv("C") 175 | os.Unsetenv("D") 176 | 177 | var cfg config 178 | if err := Parse(&cfg); err != nil { 179 | t.Errorf("error while parsing: %v", err) 180 | return 181 | } 182 | 183 | if cfg.A == nil { 184 | t.Errorf("failed parsing *bool; expected %#v, got nil", a) 185 | } else if *cfg.A != a { 186 | t.Errorf("failed parsing *bool; expected %#v, got %#v", a, *cfg.A) 187 | } 188 | 189 | if cfg.B == nil { 190 | t.Errorf("failed parsing *string; expected %#v, got nil", b) 191 | } else if *cfg.B != b { 192 | t.Errorf("failed parsing *string; expected %#v, got %#v", b, *cfg.B) 193 | } 194 | 195 | if cfg.C == nil { 196 | t.Errorf("failed parsing *int; expected %#v, got nil", c) 197 | } else if *cfg.C != c { 198 | t.Errorf("failed parsing *int; expected %#v, got %#v", c, *cfg.C) 199 | } 200 | 201 | if cfg.D == nil { 202 | t.Errorf("failed parsing *[]byte; expected %#v, got nil", d) 203 | } else if string(*cfg.D) != string(d) { 204 | t.Errorf("failed parsing *[]byte; expected %#v, got %#v", d, *cfg.D) 205 | } 206 | } 207 | 208 | func TestRequiredFlag(t *testing.T) { 209 | type config struct { 210 | A bool `env:"A,required"` 211 | } 212 | 213 | os.Unsetenv("A") 214 | 215 | var cfg config 216 | if err := Parse(&cfg); err == nil { 217 | t.Errorf("expected an error because of an unfulfilled 'require' flag") 218 | } 219 | } 220 | 221 | func TestUnexportedFieldBehavior(t *testing.T) { 222 | type a struct { 223 | a bool 224 | } 225 | 226 | type b struct { 227 | b bool `env:"b"` 228 | } 229 | 230 | var aEnv a 231 | if err := Parse(&aEnv); err != nil { 232 | t.Errorf("received an unexpected error while parsing a struct with an unexported field with no 'env' tag: %v", err) 233 | } 234 | 235 | var bEnv b 236 | if err := Parse(&bEnv); err == nil { 237 | t.Error("expected an error parsing a field with an 'env' tag on an unexported struct") 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/meowgorithm/babyenv 2 | 3 | go 1.13 4 | --------------------------------------------------------------------------------