From d325ed4fb9b4fc705358e551d59983ca841a6b10 Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Tue, 5 Jan 2021 10:35:32 +0100 Subject: [PATCH] Add support for time.Duration. Fixes #2 --- conf/decoder_test.go | 16 ++++++++++++++++ conf/spec.go | 5 +++++ conf/spec_test.go | 6 ++++++ conf/type.go | 20 +++++++++++++------- conf/utils.go | 31 +++++++++++++++++++++++++------ conf/utils_test.go | 10 ++++++++++ conf/validation_errors.go | 1 + 7 files changed, 76 insertions(+), 13 deletions(-) diff --git a/conf/decoder_test.go b/conf/decoder_test.go index 25ce477..638f67a 100644 --- a/conf/decoder_test.go +++ b/conf/decoder_test.go @@ -2,6 +2,7 @@ package conf_test import ( "testing" + "time" "github.com/ppacher/system-conf/conf" "github.com/stretchr/testify/assert" @@ -28,6 +29,10 @@ func TestFileSpecDecode(t *testing.T) { Name: "Rotate", Type: conf.BoolType, }, + { + Name: "MaxAge", + Type: conf.DurationType, + }, }, } @@ -39,6 +44,7 @@ func TestFileSpecDecode(t *testing.T) { type TestLogFile struct { Path string RotateFile bool `option:"Rotate"` + MaxAge time.Duration } type Test struct { @@ -76,6 +82,10 @@ func TestFileSpecDecode(t *testing.T) { Name: "Rotate", Value: "yes", }, + { + Name: "MaxAge", + Value: "10h", + }, }, }, { @@ -89,6 +99,10 @@ func TestFileSpecDecode(t *testing.T) { Name: "Rotate", Value: "no", }, + { + Name: "MaxAge", + Value: "24h", + }, }, }, }, @@ -107,10 +121,12 @@ func TestFileSpecDecode(t *testing.T) { { Path: "/var/log/path1", RotateFile: true, + MaxAge: time.Hour * 10, }, { Path: "/var/log/path2", RotateFile: false, + MaxAge: time.Hour * 24, }, }, }, target) diff --git a/conf/spec.go b/conf/spec.go index fc8a8ab..435ef42 100644 --- a/conf/spec.go +++ b/conf/spec.go @@ -8,6 +8,7 @@ import ( "reflect" "strconv" "strings" + "time" ) // OptionSpec describes an option @@ -351,6 +352,10 @@ func checkValue(val string, optType OptionType) error { if _, err := strconv.ParseInt(val, 0, 64); err != nil { return ErrInvalidNumber } + case DurationType, DurationSliceType: + if _, err := time.ParseDuration(val); err != nil { + return ErrInvalidDuration + } } return nil diff --git a/conf/spec_test.go b/conf/spec_test.go index 5765599..246319c 100644 --- a/conf/spec_test.go +++ b/conf/spec_test.go @@ -30,6 +30,12 @@ func TestCheckValue(t *testing.T) { {FloatSliceType, "0.1e10", nil}, {FloatType, ".INVALID", ErrInvalidFloat}, {FloatSliceType, "0.1eINVALID", ErrInvalidFloat}, + {DurationType, "5m", nil}, + {DurationType, "10h6s", nil}, + {DurationType, "asdf", ErrInvalidDuration}, + {DurationSliceType, "5m", nil}, + {DurationSliceType, "10h6s", nil}, + {DurationSliceType, "asdf", ErrInvalidDuration}, {StringType, "", nil}, // empty strings ARE VALID } diff --git a/conf/type.go b/conf/type.go index 966b3d8..9b4040d 100644 --- a/conf/type.go +++ b/conf/type.go @@ -19,13 +19,15 @@ type OptionType interface { // All supported option types. var ( - StringType = option("string ", false) - StringSliceType = option("[]string ", true) - BoolType = option("bool ", false) - IntType = option("int ", false) - IntSliceType = option("[]int ", true) - FloatType = option("float ", false) - FloatSliceType = option("[]float ", true) + StringType = option("string ", false) + StringSliceType = option("[]string ", true) + BoolType = option("bool ", false) + IntType = option("int ", false) + IntSliceType = option("[]int ", true) + FloatType = option("float ", false) + FloatSliceType = option("[]float ", true) + DurationType = option("duration", false) + DurationSliceType = option("[]duration", true) ) type optionType struct { @@ -68,6 +70,10 @@ func TypeFromString(str string) *OptionType { return &FloatType case "[]float": return &FloatSliceType + case "duration": + return &DurationType + case "[]duration": + return &DurationSliceType } return nil diff --git a/conf/utils.go b/conf/utils.go index 6c3c54f..e8440d5 100644 --- a/conf/utils.go +++ b/conf/utils.go @@ -6,6 +6,7 @@ import ( "reflect" "strconv" "strings" + "time" "unicode" ) @@ -297,6 +298,10 @@ func decodeBasic(data []string, specType OptionType, outVal reflect.Value) error decodeType = reflect.TypeOf([]float64{}) case BoolType: decodeType = reflect.TypeOf(bool(true)) + case DurationType: + decodeType = reflect.TypeOf(time.Duration(0)) + case DurationSliceType: + decodeType = reflect.TypeOf([]time.Duration{}) default: return fmt.Errorf("unsupported type: %s", specType.String()) } @@ -327,13 +332,27 @@ func decodeBool(data string, specType OptionType, outVal reflect.Value) error { } func decodeInt(data string, specType OptionType, outVal reflect.Value) error { - if specType != IntType && specType != IntSliceType { - return errors.New("invalid type") - } + var ( + val int64 + err error + ) - val, err := strconv.ParseInt(data, 0, 64) - if err != nil { - return err + switch specType { + case DurationType, DurationSliceType: + d, err := time.ParseDuration(data) + if err != nil { + return err + } + val = int64(d) + + case IntType, IntSliceType: + val, err = strconv.ParseInt(data, 0, 64) + if err != nil { + return err + } + + default: + return errors.New("invalid type") } outVal.SetInt(val) diff --git a/conf/utils_test.go b/conf/utils_test.go index de6d7e7..f78f469 100644 --- a/conf/utils_test.go +++ b/conf/utils_test.go @@ -2,6 +2,7 @@ package conf_test import ( "testing" + "time" "github.com/ppacher/system-conf/conf" "github.com/stretchr/testify/assert" @@ -14,6 +15,10 @@ func TestDecode(t *testing.T) { err := conf.DecodeValues([]string{"10"}, conf.IntType, &x) assert.NoError(t, err) assert.Equal(t, 10, x) + + err = conf.DecodeValues([]string{"10m"}, conf.DurationType, &x) + assert.NoError(t, err) + assert.Equal(t, int(time.Minute*10), x) }) t.Run("float", func(t *testing.T) { @@ -48,5 +53,10 @@ func TestDecode(t *testing.T) { err = conf.DecodeValues([]string{"1.0", "2.1", "3.2"}, conf.FloatSliceType, &x) assert.NoError(t, err) assert.Equal(t, []float64{1.0, 2.1, 3.2}, x) + + x = nil + err = conf.DecodeValues([]string{"10m5s"}, conf.DurationType, &x) + assert.NoError(t, err) + assert.Equal(t, time.Minute*10+time.Second*5, x) }) } diff --git a/conf/validation_errors.go b/conf/validation_errors.go index eae7872..95a417b 100644 --- a/conf/validation_errors.go +++ b/conf/validation_errors.go @@ -10,6 +10,7 @@ var ( ErrInvalidBoolean = errors.New("invalid boolean value") ErrInvalidFloat = errors.New("invalid floating point number)") ErrInvalidNumber = errors.New("invalid number") + ErrInvalidDuration = errors.New("invalid duration") ErrNoSections = errors.New("task does not contain any sections") ErrUnknownSection = errors.New("unknown section") ErrDropInSectionNotExists = errors.New("section defined in drop-in does not exist")