diff --git a/README.md b/README.md index 23bf7a9..ef06240 100644 --- a/README.md +++ b/README.md @@ -467,39 +467,6 @@ func main() { - Call `SetDefaults()` on any field whose pointer type implements it - Recurse into nested structs, maps, and slices -### `types` - Custom Types - -#### `Duration` - -A duration type that works seamlessly with TOML, YAML, JSON, and plain integer values (interpreted as seconds). - -```go -package main - -import ( - "fmt" - - "github.com/tinyauthapp/paerser/types" -) - -func main() { - var d types.Duration - - d.Set("30s") // 30 seconds - d.Set("5m30s") // 5 minutes and 30 seconds - d.Set("120") // 120 seconds (suffix-less integers are treated as seconds) - - fmt.Println(d.String()) // "2m0s" -} -``` - -It implements `encoding.TextMarshaler`, `encoding.TextUnmarshaler`, `json.Marshaler`, and `json.Unmarshaler`, so it works out of the box in configuration files: - -```yaml -timeout: 30s -interval: 120 -``` - ### `parser` - Low-Level Parsing Engine The `parser` package is the foundation that all other packages build on. It provides a tree-based intermediate representation (`Node`) for configuration data, along with encoding/decoding utilities. diff --git a/flag/flag_test.go b/flag/flag_test.go index b9a0099..eed9755 100644 --- a/flag/flag_test.go +++ b/flag/flag_test.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/require" "github.com/tinyauthapp/paerser/generator" "github.com/tinyauthapp/paerser/parser" - "github.com/tinyauthapp/paerser/types" ) func TestDecode(t *testing.T) { @@ -23,18 +22,6 @@ func TestDecode(t *testing.T) { args: nil, expected: nil, }, - { - desc: "types.Duration value", - args: []string{"--foo=1"}, - element: &struct { - Foo types.Duration - }{}, - expected: &struct { - Foo types.Duration - }{ - Foo: types.Duration(1 * time.Second), - }, - }, { desc: "time.Duration value", args: []string{"--foo=1"}, @@ -905,19 +892,6 @@ func TestEncode(t *testing.T) { Default: "1s", }}, }, - { - desc: "time duration field", - element: &struct { - Field types.Duration `description:"field description"` - }{ - Field: types.Duration(180 * time.Second), - }, - expected: []parser.Flat{{ - Name: "field", - Description: "field description", - Default: "180", - }}, - }, { desc: "slice of struct", element: &struct { diff --git a/parser/element_fill.go b/parser/element_fill.go index 9263b59..0cbb817 100644 --- a/parser/element_fill.go +++ b/parser/element_fill.go @@ -8,8 +8,6 @@ import ( "strconv" "strings" "time" - - "github.com/tinyauthapp/paerser/types" ) const defaultRawSliceSeparator = "," @@ -352,8 +350,6 @@ func (f filler) setMap(field reflect.Value, node *Node) error { func setInt(field reflect.Value, value string, bitSize int) error { switch field.Type() { - case reflect.TypeOf(types.Duration(0)): - return setDuration(field, value, bitSize, time.Second) case reflect.TypeOf(time.Duration(0)): return setDuration(field, value, bitSize, time.Nanosecond) default: diff --git a/parser/element_fill_test.go b/parser/element_fill_test.go index fde14cd..530b8f7 100644 --- a/parser/element_fill_test.go +++ b/parser/element_fill_test.go @@ -7,7 +7,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tinyauthapp/paerser/types" ) func TestFill(t *testing.T) { @@ -390,30 +389,6 @@ func TestFill(t *testing.T) { element: &struct{ Foo time.Duration }{}, expected: expected{element: &struct{ Foo time.Duration }{Foo: 4 * time.Nanosecond}}, }, - { - desc: "types.Duration with unit", - node: &Node{ - Name: "traefik", - Kind: reflect.Struct, - Children: []*Node{ - {Name: "Foo", FieldName: "Foo", Value: "4s", Kind: reflect.Int64}, - }, - }, - element: &struct{ Foo types.Duration }{}, - expected: expected{element: &struct{ Foo types.Duration }{Foo: types.Duration(4 * time.Second)}}, - }, - { - desc: "types.Duration without unit", - node: &Node{ - Name: "traefik", - Kind: reflect.Struct, - Children: []*Node{ - {Name: "Foo", FieldName: "Foo", Value: "4", Kind: reflect.Int64}, - }, - }, - element: &struct{ Foo types.Duration }{}, - expected: expected{element: &struct{ Foo types.Duration }{Foo: types.Duration(4 * time.Second)}}, - }, { desc: "bool", node: &Node{ diff --git a/parser/flat_encode.go b/parser/flat_encode.go index 8d20621..52cebca 100644 --- a/parser/flat_encode.go +++ b/parser/flat_encode.go @@ -8,8 +8,6 @@ import ( "strconv" "strings" "time" - - "github.com/tinyauthapp/paerser/types" ) const defaultPtrValue = "false" @@ -143,8 +141,6 @@ func (e encoderToFlat) getNodeValue(field reflect.Value, node *Node) string { i, _ := strconv.ParseInt(node.Value, 10, 64) switch field.Type() { - case reflect.TypeOf(types.Duration(time.Second)): - return strconv.Itoa(int(i) / int(time.Second)) case reflect.TypeOf(time.Second): return time.Duration(i).String() } diff --git a/parser/flat_encode_test.go b/parser/flat_encode_test.go index 51ecb41..78550cd 100644 --- a/parser/flat_encode_test.go +++ b/parser/flat_encode_test.go @@ -7,7 +7,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tinyauthapp/paerser/types" ) func TestEncodeToFlat(t *testing.T) { @@ -1121,34 +1120,6 @@ func TestEncodeToFlat(t *testing.T) { Default: "1s", }}, }, - { - desc: "time duration field", - element: &struct { - Field types.Duration `description:"field description"` - }{ - Field: types.Duration(180 * time.Second), - }, - node: &Node{ - Name: "traefik", - FieldName: "", - Kind: reflect.Struct, - Children: []*Node{ - { - Name: "Field", - Description: "field description", - FieldName: "Field", - Value: "180000000000", - Kind: reflect.Int64, - Tag: `description:"field description"`, - }, - }, - }, - expected: []Flat{{ - Name: "field", - Description: "field description", - Default: "180", - }}, - }, { desc: "slice of struct", element: &struct { diff --git a/types/duration.go b/types/duration.go deleted file mode 100644 index df6a1ad..0000000 --- a/types/duration.go +++ /dev/null @@ -1,62 +0,0 @@ -package types - -import ( - "encoding/json" - "strconv" - "time" -) - -// Duration is a custom type suitable for parsing duration values. -// It supports `time.ParseDuration`-compatible values and suffix-less digits; in -// the latter case, seconds are assumed. -type Duration time.Duration - -// Set sets the duration from the given string value. -func (d *Duration) Set(s string) error { - if v, err := strconv.ParseInt(s, 10, 64); err == nil { - *d = Duration(time.Duration(v) * time.Second) - return nil - } - - v, err := time.ParseDuration(s) - *d = Duration(v) - return err -} - -// String returns a string representation of the duration value. -func (d Duration) String() string { return (time.Duration)(d).String() } - -// MarshalText serialize the given duration value into a text. -func (d Duration) MarshalText() ([]byte, error) { - return []byte(d.String()), nil -} - -// UnmarshalText deserializes the given text into a duration value. -// It is meant to support TOML decoding of durations. -func (d *Duration) UnmarshalText(text []byte) error { - return d.Set(string(text)) -} - -// MarshalJSON serializes the given duration value. -func (d Duration) MarshalJSON() ([]byte, error) { - return []byte(`"` + time.Duration(d).String() + `"`), nil -} - -// UnmarshalJSON deserializes the given text into a duration value. -func (d *Duration) UnmarshalJSON(text []byte) error { - if v, err := strconv.ParseInt(string(text), 10, 64); err == nil { - *d = Duration(time.Duration(v) * time.Second) - return nil - } - - // We use json unmarshal on value because we have the quoted version - var value string - err := json.Unmarshal(text, &value) - if err != nil { - return err - } - - v, err := time.ParseDuration(value) - *d = Duration(v) - return err -} diff --git a/types/duration_test.go b/types/duration_test.go deleted file mode 100644 index c5502f6..0000000 --- a/types/duration_test.go +++ /dev/null @@ -1,160 +0,0 @@ -package types - -import ( - "testing" - "time" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func TestDuration_Set(t *testing.T) { - testCases := []struct { - desc string - value string - assert require.ErrorAssertionFunc - expected Duration - }{ - { - desc: "empty", - value: "", - assert: require.Error, - }, - { - desc: "duration", - value: "2m", - assert: require.NoError, - expected: Duration(2 * time.Minute), - }, - { - desc: "integer", - value: "2", - assert: require.NoError, - expected: Duration(2 * time.Second), - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - var d Duration - - err := d.Set(test.value) - test.assert(t, err) - - assert.Equal(t, test.expected, d) - }) - } -} - -func TestDuration_MarshalJSON(t *testing.T) { - testCases := []struct { - desc string - dur Duration - expected []byte - }{ - { - desc: "1 second", - dur: Duration(time.Second), - expected: []byte(`"1s"`), - }, - { - desc: "1 millisecond", - dur: Duration(time.Millisecond), - expected: []byte(`"1ms"`), - }, - { - desc: "1 nanosecond", - dur: Duration(time.Nanosecond), - expected: []byte(`"1ns"`), - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - b, err := test.dur.MarshalJSON() - require.NoError(t, err) - - assert.Equal(t, test.expected, b) - }) - } -} - -func TestDuration_JSON_bijection(t *testing.T) { - testCases := []struct { - desc string - dur Duration - }{ - { - desc: "1 second", - dur: Duration(time.Second), - }, - { - desc: "1 millisecond", - dur: Duration(time.Millisecond), - }, - { - desc: "1 nanosecond", - dur: Duration(time.Nanosecond), - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - t.Parallel() - - b, err := test.dur.MarshalJSON() - require.NoError(t, err) - - var ud Duration - err = ud.UnmarshalJSON(b) - require.NoError(t, err) - - assert.Equal(t, test.dur, ud) - }) - } -} - -func TestDuration_UnmarshalJSON(t *testing.T) { - testCases := []struct { - desc string - text []byte - assert require.ErrorAssertionFunc - expected Duration - }{ - { - desc: "empty", - text: []byte(""), - assert: require.Error, - }, - { - desc: "duration", - text: []byte(`"2m"`), - assert: require.NoError, - expected: Duration(2 * time.Minute), - }, - { - desc: "integer", - text: []byte(`2`), - assert: require.NoError, - expected: Duration(2 * time.Second), - }, - { - desc: "bad format", - text: []byte(`"2"`), - assert: require.Error, - }, - } - - for _, test := range testCases { - t.Run(test.desc, func(t *testing.T) { - var d Duration - - err := d.UnmarshalJSON(test.text) - test.assert(t, err) - - assert.Equal(t, test.expected, d) - }) - } -}