Skip to content

Commit 3bb055c

Browse files
Add time.Location support
2 parents 130ef83 + badbae6 commit 3bb055c

File tree

3 files changed

+150
-134
lines changed

3 files changed

+150
-134
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ There are following supported types:
192192
- maps (of any other supported type);
193193
- `time.Duration`;
194194
- `time.Time` (layout by default is RFC3339, may be overridden by `env-layout`);
195+
- `*time.Location` (time zone parsing [depends](https://pkg.go.dev/time#LoadLocation) on running machine)
195196
- any type implementing `cleanenv.Setter` interface.
196197

197198

cleanenv.go

Lines changed: 107 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"flag"
66
"fmt"
77
"io"
8-
"math"
98
"net/url"
109
"os"
1110
"path/filepath"
@@ -29,18 +28,25 @@ const (
2928
const (
3029
// Name of the environment variable or a list of names
3130
TagEnv = "env"
31+
3232
// Value parsing layout (for types like time.Time)
3333
TagEnvLayout = "env-layout"
34+
3435
// Default value
3536
TagEnvDefault = "env-default"
37+
3638
// Custom list and map separator
3739
TagEnvSeparator = "env-separator"
40+
3841
// Environment variable description
3942
TagEnvDescription = "env-description"
43+
4044
// Flag to mark a field as updatable
4145
TagEnvUpd = "env-upd"
46+
4247
// Flag to mark a field as required
4348
TagEnvRequired = "env-required"
49+
4450
// Flag to specify prefix for structure fields
4551
TagEnvPrefix = "env-prefix"
4652
)
@@ -49,15 +55,15 @@ const (
4955
//
5056
// To implement a custom value setter you need to add a SetValue function to your type that will receive a string raw value:
5157
//
52-
// type MyField string
58+
// type MyField string
5359
//
54-
// func (f *MyField) SetValue(s string) error {
55-
// if s == "" {
56-
// return fmt.Errorf("field value can't be empty")
57-
// }
58-
// *f = MyField("my field is: " + s)
59-
// return nil
60-
// }
60+
// func (f *MyField) SetValue(s string) error {
61+
// if s == "" {
62+
// return fmt.Errorf("field value can't be empty")
63+
// }
64+
// *f = MyField("my field is: " + s)
65+
// return nil
66+
// }
6167
type Setter interface {
6268
SetValue(string) error
6369
}
@@ -72,20 +78,20 @@ type Updater interface {
7278
//
7379
// Example:
7480
//
75-
// type ConfigDatabase struct {
76-
// Port string `yaml:"port" env:"PORT" env-default:"5432"`
77-
// Host string `yaml:"host" env:"HOST" env-default:"localhost"`
78-
// Name string `yaml:"name" env:"NAME" env-default:"postgres"`
79-
// User string `yaml:"user" env:"USER" env-default:"user"`
80-
// Password string `yaml:"password" env:"PASSWORD"`
81-
// }
81+
// type ConfigDatabase struct {
82+
// Port string `yaml:"port" env:"PORT" env-default:"5432"`
83+
// Host string `yaml:"host" env:"HOST" env-default:"localhost"`
84+
// Name string `yaml:"name" env:"NAME" env-default:"postgres"`
85+
// User string `yaml:"user" env:"USER" env-default:"user"`
86+
// Password string `yaml:"password" env:"PASSWORD"`
87+
// }
8288
//
83-
// var cfg ConfigDatabase
89+
// var cfg ConfigDatabase
8490
//
85-
// err := cleanenv.ReadConfig("config.yml", &cfg)
86-
// if err != nil {
87-
// ...
88-
// }
91+
// err := cleanenv.ReadConfig("config.yml", &cfg)
92+
// if err != nil {
93+
// ...
94+
// }
8995
func ReadConfig(path string, cfg interface{}) error {
9096
err := parseFile(path, cfg)
9197
if err != nil {
@@ -178,11 +184,58 @@ func parseENV(r io.Reader, _ interface{}) error {
178184
}
179185

180186
for env, val := range vars {
181-
os.Setenv(env, val)
187+
if err = os.Setenv(env, val); err != nil {
188+
return fmt.Errorf("set environment: %w", err)
189+
}
182190
}
191+
183192
return nil
184193
}
185194

195+
// parseSlice parses value into a slice of given type
196+
func parseSlice(valueType reflect.Type, value string, sep string, layout *string) (*reflect.Value, error) {
197+
sliceValue := reflect.MakeSlice(valueType, 0, 0)
198+
if valueType.Elem().Kind() == reflect.Uint8 {
199+
sliceValue = reflect.ValueOf([]byte(value))
200+
} else if len(strings.TrimSpace(value)) != 0 {
201+
values := strings.Split(value, sep)
202+
sliceValue = reflect.MakeSlice(valueType, len(values), len(values))
203+
204+
for i, val := range values {
205+
if err := parseValue(sliceValue.Index(i), val, sep, layout); err != nil {
206+
return nil, err
207+
}
208+
}
209+
}
210+
return &sliceValue, nil
211+
}
212+
213+
// parseMap parses value into a map of given type
214+
func parseMap(valueType reflect.Type, value string, sep string, layout *string) (*reflect.Value, error) {
215+
mapValue := reflect.MakeMap(valueType)
216+
if len(strings.TrimSpace(value)) != 0 {
217+
pairs := strings.Split(value, sep)
218+
for _, pair := range pairs {
219+
kvPair := strings.SplitN(pair, ":", 2)
220+
if len(kvPair) != 2 {
221+
return nil, fmt.Errorf("invalid map item: %q", pair)
222+
}
223+
k := reflect.New(valueType.Key()).Elem()
224+
err := parseValue(k, kvPair[0], sep, layout)
225+
if err != nil {
226+
return nil, err
227+
}
228+
v := reflect.New(valueType.Elem()).Elem()
229+
err = parseValue(v, kvPair[1], sep, layout)
230+
if err != nil {
231+
return nil, err
232+
}
233+
mapValue.SetMapIndex(k, v)
234+
}
235+
}
236+
return &mapValue, nil
237+
}
238+
186239
// structMeta is a structure metadata entity
187240
type structMeta struct {
188241
envList []string
@@ -198,14 +251,15 @@ type structMeta struct {
198251

199252
// isFieldValueZero determines if fieldValue empty or not
200253
func (sm *structMeta) isFieldValueZero() bool {
201-
return isZero(sm.fieldValue)
254+
return sm.fieldValue.IsZero()
202255
}
203256

204257
// parseFunc custom value parser function
205258
type parseFunc func(*reflect.Value, string, *string) error
206259

207260
// Any specific supported struct can be added here
208261
var validStructs = map[reflect.Type]parseFunc{
262+
209263
reflect.TypeOf(time.Time{}): func(field *reflect.Value, value string, layout *string) error {
210264
var l string
211265
if layout != nil {
@@ -220,6 +274,7 @@ var validStructs = map[reflect.Type]parseFunc{
220274
field.Set(reflect.ValueOf(val))
221275
return nil
222276
},
277+
223278
reflect.TypeOf(url.URL{}): func(field *reflect.Value, value string, _ *string) error {
224279
val, err := url.Parse(value)
225280
if err != nil {
@@ -228,6 +283,16 @@ var validStructs = map[reflect.Type]parseFunc{
228283
field.Set(reflect.ValueOf(*val))
229284
return nil
230285
},
286+
287+
reflect.TypeOf(&time.Location{}): func(field *reflect.Value, value string, _ *string) error {
288+
loc, err := time.LoadLocation(value)
289+
if err != nil {
290+
return err
291+
}
292+
293+
field.Set(reflect.ValueOf(loc))
294+
return nil
295+
},
231296
}
232297

233298
// readStructMetadata reads structure metadata (types, tags, etc.)
@@ -268,12 +333,14 @@ func readStructMetadata(cfgRoot interface{}) ([]structMeta, error) {
268333

269334
// process nested structure (except of supported ones)
270335
if fld := s.Field(idx); fld.Kind() == reflect.Struct {
336+
271337
// add structure to parsing stack
272338
if _, found := validStructs[fld.Type()]; !found {
273339
prefix, _ := fType.Tag.Lookup(TagEnvPrefix)
274340
cfgStack = append(cfgStack, cfgNode{fld.Addr().Interface(), sPrefix + prefix})
275341
continue
276342
}
343+
277344
// process time.Time
278345
if l, ok := fType.Tag.Lookup(TagEnvLayout); ok {
279346
layout = &l
@@ -357,9 +424,10 @@ func readEnvVars(cfg interface{}, update bool) error {
357424
}
358425

359426
if rawValue == nil && meta.required && meta.isFieldValueZero() {
360-
err := fmt.Errorf("field %q is required but the value is not provided",
361-
meta.fieldName)
362-
return err
427+
return fmt.Errorf(
428+
"field %q is required but the value is not provided",
429+
meta.fieldName,
430+
)
363431
}
364432

365433
if rawValue == nil && meta.isFieldValueZero() {
@@ -392,8 +460,8 @@ func parseValue(field reflect.Value, value, sep string, layout *string) error {
392460
}
393461

394462
valueType := field.Type()
395-
396463
switch valueType.Kind() {
464+
397465
// parse string value
398466
case reflect.String:
399467
field.SetString(value)
@@ -406,16 +474,22 @@ func parseValue(field reflect.Value, value, sep string, layout *string) error {
406474
}
407475
field.SetBool(b)
408476

409-
// parse integer (or time) value
410-
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
411-
if field.Kind() == reflect.Int64 && valueType.PkgPath() == "time" && valueType.Name() == "Duration" {
477+
// parse integer
478+
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32:
479+
number, err := strconv.ParseInt(value, 0, valueType.Bits())
480+
if err != nil {
481+
return err
482+
}
483+
field.SetInt(number)
484+
485+
case reflect.Int64:
486+
if valueType == reflect.TypeOf(time.Duration(0)) {
412487
// try to parse time
413488
d, err := time.ParseDuration(value)
414489
if err != nil {
415490
return err
416491
}
417492
field.SetInt(int64(d))
418-
419493
} else {
420494
// parse regular integer
421495
number, err := strconv.ParseInt(value, 0, valueType.Bits())
@@ -459,62 +533,18 @@ func parseValue(field reflect.Value, value, sep string, layout *string) error {
459533

460534
field.Set(*mapValue)
461535

462-
case reflect.Struct:
536+
default:
537+
// look for supported struct parser
463538
if structParser, found := validStructs[valueType]; found {
464539
return structParser(&field, value, layout)
465540
}
466541

467-
default:
468542
return fmt.Errorf("unsupported type %s.%s", valueType.PkgPath(), valueType.Name())
469543
}
470544

471545
return nil
472546
}
473547

474-
// parseSlice parses value into a slice of given type
475-
func parseSlice(valueType reflect.Type, value string, sep string, layout *string) (*reflect.Value, error) {
476-
sliceValue := reflect.MakeSlice(valueType, 0, 0)
477-
if valueType.Elem().Kind() == reflect.Uint8 {
478-
sliceValue = reflect.ValueOf([]byte(value))
479-
} else if len(strings.TrimSpace(value)) != 0 {
480-
values := strings.Split(value, sep)
481-
sliceValue = reflect.MakeSlice(valueType, len(values), len(values))
482-
483-
for i, val := range values {
484-
if err := parseValue(sliceValue.Index(i), val, sep, layout); err != nil {
485-
return nil, err
486-
}
487-
}
488-
}
489-
return &sliceValue, nil
490-
}
491-
492-
// parseMap parses value into a map of given type
493-
func parseMap(valueType reflect.Type, value string, sep string, layout *string) (*reflect.Value, error) {
494-
mapValue := reflect.MakeMap(valueType)
495-
if len(strings.TrimSpace(value)) != 0 {
496-
pairs := strings.Split(value, sep)
497-
for _, pair := range pairs {
498-
kvPair := strings.SplitN(pair, ":", 2)
499-
if len(kvPair) != 2 {
500-
return nil, fmt.Errorf("invalid map item: %q", pair)
501-
}
502-
k := reflect.New(valueType.Key()).Elem()
503-
err := parseValue(k, kvPair[0], sep, layout)
504-
if err != nil {
505-
return nil, err
506-
}
507-
v := reflect.New(valueType.Elem()).Elem()
508-
err = parseValue(v, kvPair[1], sep, layout)
509-
if err != nil {
510-
return nil, err
511-
}
512-
mapValue.SetMapIndex(k, v)
513-
}
514-
}
515-
return &mapValue, nil
516-
}
517-
518548
// GetDescription returns a description of environment variables.
519549
// You can provide a custom header text.
520550
func GetDescription(cfg interface{}, headerText *string) (string, error) {
@@ -583,42 +613,3 @@ func FUsage(w io.Writer, cfg interface{}, headerText *string, usageFuncs ...func
583613
fmt.Fprintln(w, text)
584614
}
585615
}
586-
587-
// isZero is a backport of reflect.Value.IsZero()
588-
func isZero(v reflect.Value) bool {
589-
switch v.Kind() {
590-
case reflect.Bool:
591-
return !v.Bool()
592-
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
593-
return v.Int() == 0
594-
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
595-
return v.Uint() == 0
596-
case reflect.Float32, reflect.Float64:
597-
return math.Float64bits(v.Float()) == 0
598-
case reflect.Complex64, reflect.Complex128:
599-
c := v.Complex()
600-
return math.Float64bits(real(c)) == 0 && math.Float64bits(imag(c)) == 0
601-
case reflect.Array:
602-
for i := 0; i < v.Len(); i++ {
603-
if !isZero(v.Index(i)) {
604-
return false
605-
}
606-
}
607-
return true
608-
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice, reflect.UnsafePointer:
609-
return v.IsNil()
610-
case reflect.String:
611-
return v.Len() == 0
612-
case reflect.Struct:
613-
for i := 0; i < v.NumField(); i++ {
614-
if !isZero(v.Field(i)) {
615-
return false
616-
}
617-
}
618-
return true
619-
default:
620-
// This should never happens, but will act as a safeguard for
621-
// later, as a default value doesn't makes sense here.
622-
panic(fmt.Sprintf("Value.IsZero: %v", v.Kind()))
623-
}
624-
}

0 commit comments

Comments
 (0)