pax_global_header00006660000000000000000000000064140044134730014512gustar00rootroot0000000000000052 comment=0985ad15299dc026eb3c4f98c748578bfe2798fe babyenv-1.3.1/000077500000000000000000000000001400441347300131425ustar00rootroot00000000000000babyenv-1.3.1/.gitignore000066400000000000000000000000121400441347300151230ustar00rootroot00000000000000.DS_Store babyenv-1.3.1/LICENSE000066400000000000000000000020611400441347300141460ustar00rootroot00000000000000MIT License Copyright (c) 2019 Magic Numbers™ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. babyenv-1.3.1/README.md000066400000000000000000000042361400441347300144260ustar00rootroot00000000000000Babyenv ======= [![GoDoc Badge](https://godoc.org/github.com/meowgorithm/babylogger?status.svg)](http://godoc.org/github.com/meowgorithm/babyenv) Package babyenv collects environment variables and places them in corresponding struct fields. It aims to reduce the boilerplate in reading data from the environment. The struct should contain `env` tags indicating the names of corresponding environment variables. The values of those environment variables will be then collected and placed into the struct. If nothing is found, struct fields will be given their default values (for example, `bool`s will be `false`). ```go type config struct { Name string `env:"NAME"` } ``` Default values can also be provided in the `default` tag. ```go type config struct { Name string `env:"NAME" default:"Jane"` } ``` A 'required' flag can also be set in the following format: ```go type config struct { Name string `env:"NAME,required"` } ``` If a required flag is set the 'default' tag will be ignored. ## Example ```go package main import ( "fmt" "os" "github.com/meowgorithm/babyenv" ) type config struct { Debug bool `env:"DEBUG"` Port string `env:"PORT" default:"8000"` Workers int `env:"WORKERS" default:"16"` Name string `env:"NAME,required"` } func main() { os.Setenv("DEBUG", "true") os.Setenv("WORKERS", "4") os.Setenv("NAME", "Jane") var cfg config if err := babyenv.Parse(&cfg); err != nil { log.Fatalf("could not get environment vars: %v", err) } fmt.Printf("%b\n%s\n%d\n%s", cfg.Debug, cfg.Port, cfg.Workers, cfg.Name) // Output: // true // 8000 // 4 // Jane } ``` ## Supported Types Currently, only the following types are supported: * `string` * `bool` * `int` * `int64` * `[]byte` * `*string` * `*bool` * `*int` * `*[]byte` Pull requests are welcome, especially for new types. ## Credit This is entirely based on the [caarlos0][carlos]’s [env][carlosenv] package. This one simply has a slightly different interface, and less functionality. [carlos]: https://github.com/caarlos0 [carlosenv]: https://github.com/caarlos0/env ## LICENSE MIT babyenv-1.3.1/env.go000066400000000000000000000232311400441347300142620ustar00rootroot00000000000000// Package babyenv collects environment variables and places them in // corresponding struct fields. It aims to reduce the boilerplate in reading // data from the environment. // // The struct should contain `env` tags indicating the names of corresponding // environment variables. The values of those environment variables will be // then collected and placed into the struct. If nothing is found, struct // fields will be given their default values (for example, `bool`s will be // `false`). // // type config struct { // Name string `env:"NAME"` // } // // Default values can also be provided in the `default` tag. // // `env:"NAME" default:"Jane"` // // A 'required' flag can also be set in the following format: // // `env:"NAME,required"` // // If a required flag is set the 'default' tag will be ignored. // // Only a few types are supported: string, bool, int, []byte, *string, *bool, // *int, *[]byte. An error will be returned if other types are attempted to // be processed. // // Example: // // package main // // import ( // "fmt" // "os" // "github.com/magicnumbers/babyenv" // ) // // type config struct { // Debug bool `env:"DEBUG"` // Port string `env:"PORT" default:"8000"` // Workers int `env:"WORKERS" default:"16"` // Name string `env:"NAME,required"` // } // // func main() { // os.Setenv("DEBUG", "true") // os.Setenv("WORKERS", "4") // os.Setenv("NAME", "Jane") // // var cfg config // if err := babyenv.Parse(&cfg); err != nil { // log.Fatalf("could not get environment vars: %v", err) // } // // fmt.Printf("%b\n%s\n%d\n%s", cfg.Debug, cfg.Port, cfg.Workers, cfg.Name) // // // Output: // // true // // 8000 // // 4 // // Jane // } package babyenv import ( "errors" "fmt" "os" "reflect" "strconv" "strings" ) var ( // ErrorNotAStructPointer indicates that we were expecting a pointer to a // struct but we didn't get it. This is returned when parsing a passed // struct. ErrorNotAStructPointer = errors.New("expected a pointer to a struct") ) // ErrorUnsettable is used when a field cannot be set type ErrorUnsettable struct { FieldName string } // Error implements the error interface func (e *ErrorUnsettable) Error() string { return fmt.Sprintf("can't set field %s", e.FieldName) } // ErrorUnsupportedType is used when we attempt to parse a struct field of an // unsupported type type ErrorUnsupportedType struct { Type reflect.Type } // Error implements the error interface func (e *ErrorUnsupportedType) Error() string { return fmt.Sprintf("unsupported type %v", e.Type) } // ErrorEnvVarRequired is used when a `required` flag is used and the value of // the corresponding environment variable is empty type ErrorEnvVarRequired struct { Name string } // Error implements the error interface func (e *ErrorEnvVarRequired) Error() string { return fmt.Sprintf("%s is required", e.Name) } // Parse parses a struct for environment variables, placing found values in the // struct, altering it. We look at the 'env' tag for the environment variable // names, and the 'default' for the default value to the corresponding // environment variable. func Parse(cfg interface{}) error { // Make sure we've got a pointer val := reflect.ValueOf(cfg) if val.Kind() != reflect.Ptr { return ErrorNotAStructPointer } // Make sure our pointer points to a struct ref := val.Elem() if ref.Kind() != reflect.Struct { return ErrorNotAStructPointer } return parseFields(ref) } // Interate over the fields of a struct, looking for `env` tags indicating // environment variable names and `default` inicating default values. We're // expecting a pointer to a struct here, and either environment variables or // defaults will be placed in the struct. If a non-struct pointer is passed we // return an error. // // Note that a required flag can also be passed in the form of: // // VarName string `env:"VAR_NAME,required"` // // If a required flag is set, and the environment variable is empty, the // `default` tag is ignored. func parseFields(ref reflect.Value) error { for i := 0; i < ref.NumField(); i++ { var ( field = ref.Field(i) fieldKind = ref.Field(i).Kind() fieldTags = ref.Type().Field(i).Tag fieldName = ref.Type().Field(i).Name envVarName string required bool ) tagVal := fieldTags.Get("env") if tagVal == "" || tagVal == "-" { continue } if !field.CanSet() { return &ErrorUnsettable{fieldName} } // The tag we're looking at will look something like one of these: // // `env:"NAME"` // `env:"NAME,required"` // // Here we split on the comma and sort out the parts. tagValParts := strings.Split(tagVal, ",") if len(tagValParts) == 0 { // This should never happen continue } else if len(tagValParts) >= 1 { envVarName = tagValParts[0] } if len(tagValParts) >= 2 && strings.TrimSpace(tagValParts[1]) == "required" { required = true } // Get the value of the environment var envVarVal := os.Getenv(envVarName) // Return an error if the required flag is set and the env var is empty if envVarVal == "" && required { return &ErrorEnvVarRequired{envVarName} } defaultVal := fieldTags.Get("default") // Is the situation such that we should set a default value? We only // do it if the value of the given environment varaiable is empty, and // we have a non-empty default value. shouldSetDefault := len(envVarVal) == 0 && len(defaultVal) > 0 && defaultVal != "-" // Set the field accoring to it's kind switch fieldKind { case reflect.String: if shouldSetDefault { field.SetString(defaultVal) continue } field.SetString(envVarVal) case reflect.Bool: if shouldSetDefault { if err := setBool(field, defaultVal); err != nil { return err } continue } if err := setBool(field, envVarVal); err != nil { return err } case reflect.Int: if shouldSetDefault { if err := setInt(field, defaultVal); err != nil { return err } continue } if err := setInt(field, envVarVal); err != nil { return err } case reflect.Int64: if shouldSetDefault { if err := setInt64(field, defaultVal); err != nil { return err } continue } if err := setInt64(field, envVarVal); err != nil { return err } // Slices are a whole can of worms case reflect.Slice: switch field.Type().Elem().Kind() { // A reflect.Uint8 doubles as a byte array case reflect.Uint8: if shouldSetDefault { field.SetBytes([]byte(defaultVal)) continue } field.SetBytes([]byte(envVarVal)) default: return &ErrorUnsupportedType{field.Type()} } // Pointers are also a whole other can of worms case reflect.Ptr: ptr := field.Type().Elem() switch ptr.Kind() { case reflect.String: if shouldSetDefault { field.Set(reflect.ValueOf(&defaultVal)) continue } field.Set(reflect.ValueOf(&envVarVal)) case reflect.Bool: if shouldSetDefault { if err := setBoolPointer(field, defaultVal); err != nil { return err } continue } if err := setBoolPointer(field, envVarVal); err != nil { return err } case reflect.Int: if shouldSetDefault { if err := setIntPointer(field, defaultVal); err != nil { return err } continue } if err := setIntPointer(field, envVarVal); err != nil { return err } case reflect.Int64: if shouldSetDefault { if err := setInt64Pointer(field, defaultVal); err != nil { return err } continue } if err := setInt64Pointer(field, envVarVal); err != nil { return err } // A poiner to a slice!! Whole other level case reflect.Slice: switch ptr.Elem().Kind() { // Again, a reflect.Uint8 also works as a byte array case reflect.Uint8: var byteSlice []byte if shouldSetDefault { byteSlice = []byte(defaultVal) } else { byteSlice = []byte(envVarVal) } field.Set(reflect.ValueOf(&byteSlice)) default: return &ErrorUnsupportedType{field.Type()} } default: return &ErrorUnsupportedType{field.Type()} } default: return &ErrorUnsupportedType{field.Type()} } } return nil } func setBool(v reflect.Value, s string) error { if s == "" { // Default to false v.SetBool(false) return nil } b, err := strconv.ParseBool(s) if err != nil { return err } v.SetBool(b) return nil } func setInt(v reflect.Value, s string) error { if s == "" { // Default to 0 v.SetInt(0) return nil } n, err := strconv.ParseInt(s, 10, 32) if err != nil { return err } v.SetInt(n) return nil } func setInt64(v reflect.Value, s string) error { if s == "" { // Default to 0 v.SetInt(0) return nil } n, err := strconv.ParseInt(s, 10, 64) if err != nil { return err } v.SetInt(n) return nil } func setBoolPointer(v reflect.Value, s string) error { if s == "" { // Default to false b := false v.Set(reflect.ValueOf(&b)) return nil } b, err := strconv.ParseBool(s) if err != nil { return err } v.Set(reflect.ValueOf(&b)) return nil } func setIntPointer(v reflect.Value, s string) error { if s == "" { // Default to 0 n := 0 v.Set(reflect.ValueOf(&n)) return nil } i64, err := strconv.ParseInt(s, 10, 32) if err != nil { return err } i := int(i64) v.Set(reflect.ValueOf(&i)) return nil } func setInt64Pointer(v reflect.Value, s string) error { if s == "" { // Default to 0 n := 0 v.Set(reflect.ValueOf(&n)) return nil } i, err := strconv.ParseInt(s, 10, 64) if err != nil { return err } v.Set(reflect.ValueOf(&i)) return nil } babyenv-1.3.1/env_test.go000066400000000000000000000125701400441347300153250ustar00rootroot00000000000000package babyenv import ( "os" "strconv" "testing" ) func TestParse(t *testing.T) { type config struct { A bool `env:"A"` B string `env:"B"` C int `env:"C"` D []byte `env:"D"` E int64 `env:"E"` } a := true b := "xxx" c := 16 d := []byte("yyy") var e int64 = 64 os.Setenv("A", strconv.FormatBool(a)) os.Setenv("B", b) os.Setenv("C", strconv.FormatInt(int64(c), 10)) os.Setenv("D", string(d)) os.Setenv("E", strconv.FormatInt(e, 10)) var cfg config if err := Parse(&cfg); err != nil { t.Errorf("error while parsing: %v", err) return } if !cfg.A { t.Errorf("failed parsing bool; expected %#v, got %#v", a, cfg.A) } if cfg.B != b { t.Errorf("failed parsing string; expected %#v, got %#v", b, cfg.B) } if cfg.C != c { t.Errorf("failed parsing int; expected %#v, got %#v", c, cfg.C) } if cfg.D == nil { t.Errorf("failed parsing byte[]; expected %#v, got nil", d) } else if string(cfg.D) != string(d) { t.Errorf("failed parsing []byte; expected %#v, got %#v", d, cfg.D) } if cfg.E != e { t.Errorf("failed parsing int64; expected %#v, got %#v", c, cfg.E) } } func TestParseWithDefaults(t *testing.T) { type config struct { A bool `env:"A" default:"true"` B string `env:"B" default:"xxx"` C int `env:"C" default:"16"` D []byte `env:"D" default:"yyy"` E int64 `env:"E" default:"64"` } a := true b := "xxx" c := 16 d := []byte("yyy") var e int64 = 64 os.Unsetenv("A") os.Unsetenv("B") os.Unsetenv("C") os.Unsetenv("D") os.Unsetenv("E") var cfg config if err := Parse(&cfg); err != nil { t.Errorf("error while parsing: %v", err) return } if cfg.A != a { t.Errorf("failed parsing bool; expected %#v, got %#v", a, cfg.A) } if cfg.B != b { t.Errorf("failed parsing string; expected %#v, got %#v", b, cfg.B) } if cfg.C != c { t.Errorf("failed parsing int; expected %#v, got %#v", c, cfg.C) } if cfg.D == nil { t.Errorf("failed parsing byte[]; expected %#v, got nil", d) } else if string(cfg.D) != string(d) { t.Errorf("failed parsing []byte; expected %#v, got %#v", d, cfg.D) } if cfg.E != e { t.Errorf("failed parsing int64; expected %#v, got %#v", e, cfg.E) } } func TestParsePointers(t *testing.T) { type config struct { A *bool `env:"A"` B *string `env:"B"` C *int `env:"C"` D *[]byte `env:"D"` E *int64 `env:"E"` } a := true b := "xxx" c := 16 d := []byte("yyy") var e int64 = 64 os.Setenv("A", strconv.FormatBool(a)) os.Setenv("B", b) os.Setenv("C", strconv.FormatInt(int64(c), 10)) os.Setenv("D", string(d)) os.Setenv("E", strconv.FormatInt(e, 10)) var cfg config if err := Parse(&cfg); err != nil { t.Errorf("error while parsing: %v", err) return } if cfg.A == nil { t.Errorf("failed parsing *bool; expected %#v, got nil", a) } else if *cfg.A != a { t.Errorf("failed parsing *bool; expected %#v, got %#v", a, *cfg.A) } if cfg.B == nil { t.Errorf("failed parsing *string; expected %#v, got nil", b) } else if *cfg.B != b { t.Errorf("failed parsing *string; expected %#v, got %#v", b, *cfg.B) } if cfg.C == nil { t.Errorf("failed parsing *int; expected %#v, got nil", c) } else if *cfg.C != c { t.Errorf("failed parsing *int; expected %#v, got %#v", c, *cfg.C) } if cfg.D == nil { t.Errorf("failed parsing *[]byte; expected %#v, got nil", d) } else if string(*cfg.D) != string(d) { t.Errorf("failed parsing *[]byte; expected %#v, got %#v", d, *cfg.D) } if cfg.E == nil { t.Errorf("failed parsing *int64; expected %#v, got nil", e) } else if *cfg.E != e { t.Errorf("failed parsing *int64; expected %#v, got %#v", e, *cfg.E) } } func TestParsePointersWithDefaults(t *testing.T) { type config struct { A *bool `env:"A" default:"true"` B *string `env:"B" default:"xxx"` C *int `env:"C" default:"16"` D *[]byte `env:"D" default:"yyy"` } a := true b := "xxx" c := 16 d := []byte("yyy") os.Unsetenv("A") os.Unsetenv("B") os.Unsetenv("C") os.Unsetenv("D") var cfg config if err := Parse(&cfg); err != nil { t.Errorf("error while parsing: %v", err) return } if cfg.A == nil { t.Errorf("failed parsing *bool; expected %#v, got nil", a) } else if *cfg.A != a { t.Errorf("failed parsing *bool; expected %#v, got %#v", a, *cfg.A) } if cfg.B == nil { t.Errorf("failed parsing *string; expected %#v, got nil", b) } else if *cfg.B != b { t.Errorf("failed parsing *string; expected %#v, got %#v", b, *cfg.B) } if cfg.C == nil { t.Errorf("failed parsing *int; expected %#v, got nil", c) } else if *cfg.C != c { t.Errorf("failed parsing *int; expected %#v, got %#v", c, *cfg.C) } if cfg.D == nil { t.Errorf("failed parsing *[]byte; expected %#v, got nil", d) } else if string(*cfg.D) != string(d) { t.Errorf("failed parsing *[]byte; expected %#v, got %#v", d, *cfg.D) } } func TestRequiredFlag(t *testing.T) { type config struct { A bool `env:"A,required"` } os.Unsetenv("A") var cfg config if err := Parse(&cfg); err == nil { t.Errorf("expected an error because of an unfulfilled 'require' flag") } } func TestUnexportedFieldBehavior(t *testing.T) { type a struct { a bool } type b struct { b bool `env:"b"` } var aEnv a if err := Parse(&aEnv); err != nil { t.Errorf("received an unexpected error while parsing a struct with an unexported field with no 'env' tag: %v", err) } var bEnv b if err := Parse(&bEnv); err == nil { t.Error("expected an error parsing a field with an 'env' tag on an unexported struct") } } babyenv-1.3.1/go.mod000066400000000000000000000000571400441347300142520ustar00rootroot00000000000000module github.com/meowgorithm/babyenv go 1.13