From 3feaff3f6c63dcc72008bcc623ab33ace07390ca Mon Sep 17 00:00:00 2001 From: Joshua Thomas Date: Mon, 29 Jun 2026 10:46:42 -0700 Subject: [PATCH] making time.Duration values supported and parseable when provided as ENV or --arg --- .github/workflows/test.yml | 2 +- go.mod | 2 +- settings.go | 30 +++++++++++++++++++++++++----- settings_test.go | 17 +++++++++++------ 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ce9d215..984443c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -5,7 +5,7 @@ jobs: test: runs-on: ubuntu-latest container: - image: golang:1.24 + image: golang:1.26 steps: - uses: actions/checkout@v6 with: diff --git a/go.mod b/go.mod index 5d9de8d..03d97cb 100644 --- a/go.mod +++ b/go.mod @@ -1,5 +1,5 @@ module go.jtlabs.io/settings -go 1.24 +go 1.26 require gopkg.in/yaml.v2 v2.4.0 diff --git a/settings.go b/settings.go index f31cf2a..f6ae4ad 100644 --- a/settings.go +++ b/settings.go @@ -17,10 +17,11 @@ import ( ) var ( - commaRE = regexp.MustCompile(`\,\s?`) - dotRE = regexp.MustCompile(`\.`) - settingsExt = []string{".yml", ".yaml", ".json", ""} - timeType = reflect.TypeOf(time.Now()) + commaRE = regexp.MustCompile(`\,\s?`) + dotRE = regexp.MustCompile(`\.`) + settingsExt = []string{".yml", ".yaml", ".json", ""} + timeType = reflect.TypeOf(time.Now()) + durationType = reflect.TypeOf(time.Duration(0)) ) type settings struct { @@ -552,7 +553,8 @@ func (s *settings) setFieldValue(fieldPath string, sVal string, override string) case reflect.Array, reflect.Slice: sVals := commaRE.Split(sVal, -1) ov := s.findOutFieldValue(fieldPath) - st := ov.Type().Elem().Kind() + elemType := ov.Type().Elem() + st := elemType.Kind() pv := reflect.MakeSlice(reflect.Indirect(ov).Type(), len(sVals), cap(sVals)) for i, sv := range sVals { @@ -592,6 +594,15 @@ func (s *settings) setFieldValue(fieldPath string, sVal string, override string) iv := int32(v) pv.Index(i).Set(reflect.ValueOf(iv)) case reflect.Int64: + if elemType == durationType { + dv, err := time.ParseDuration(sv) + if err != nil { + return SettingsFieldSetError(fieldPath, t.Kind(), err) + } + pv.Index(i).Set(reflect.ValueOf(dv)) + continue + } + v, err := strconv.ParseInt(sv, 0, ov.Type().Elem().Bits()) if err != nil { return SettingsFieldSetError(fieldPath, t.Kind(), err) @@ -669,6 +680,15 @@ func (s *settings) setFieldValue(fieldPath string, sVal string, override string) } val = int(pv) case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + if t == durationType { + dv, err := time.ParseDuration(sVal) + if err != nil { + return SettingsFieldSetError(fieldPath, t.Kind(), err) + } + val = dv + break + } + pv, err := strconv.ParseInt(sVal, 0, t.Bits()) if err != nil { return SettingsFieldSetError(fieldPath, t.Kind(), err) diff --git a/settings_test.go b/settings_test.go index 02b018c..37be0e8 100644 --- a/settings_test.go +++ b/settings_test.go @@ -638,9 +638,10 @@ func Test_settings_applyDefaultsMap(t *testing.T) { func Test_settings_applyVars(t *testing.T) { type testConfig struct { - Bool bool - Name string - Nested struct { + Bool bool + Duration time.Duration + Name string + Nested struct { Count int } } @@ -664,6 +665,7 @@ func Test_settings_applyVars(t *testing.T) { fields{ fieldTypeMap: map[string]reflect.Type{ "Bool": reflect.TypeOf(true), + "Duration": reflect.TypeOf(time.Duration(0)), "Name": reflect.TypeOf(""), "Nested.Count": reflect.TypeOf(1), }, @@ -672,19 +674,22 @@ func Test_settings_applyVars(t *testing.T) { args{ v: map[string]string{ "BOOL": "Bool", + "DURATION": "Duration", "NAME": "Name", "NESTED_COUNT": "Nested.Count", }, }, map[string]string{ "BOOL": "true", + "DURATION": "5s", "NAME": "testing name assignment", "NESTED_COUNT": "10", }, &testConfig{ - Bool: true, - Name: "testing name assignment", - Nested: struct{ Count int }{10}, + Bool: true, + Duration: time.Duration(5) * time.Second, + Name: "testing name assignment", + Nested: struct{ Count int }{10}, }, false, },