From de88a871c6f5a9e387a3c7a93a90a91b9625247d Mon Sep 17 00:00:00 2001 From: Jim Date: Thu, 18 May 2023 09:55:34 -0400 Subject: [PATCH] feat: (api/cli): add custom type for duration --- api/types.go | 34 +++++++++++++++++++ api/types_test.go | 63 ++++++++++++++++++++++++++++++++++++ internal/api/genapi/parse.go | 2 +- 3 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 api/types.go create mode 100644 api/types_test.go diff --git a/api/types.go b/api/types.go new file mode 100644 index 000000000..fda25dc47 --- /dev/null +++ b/api/types.go @@ -0,0 +1,34 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package api + +import ( + "encoding/json" + "fmt" + "time" +) + +// Duration represents a time.Duration and supports marshaling/unmarshaling from +// a json string +type Duration struct { + time.Duration +} + +func (d Duration) MarshalJSON() ([]byte, error) { + return json.Marshal(d.Duration.String()) +} + +func (d *Duration) UnmarshalJSON(b []byte) error { + const op = "api.(Duration).UnmarshalJSON" + var str string + if err := json.Unmarshal(b, &str); err != nil { + return fmt.Errorf("%s: %w", op, err) + } + val, err := time.ParseDuration(str) + if err != nil { + return fmt.Errorf("%s: %w", op, err) + } + d.Duration = val + return nil +} diff --git a/api/types_test.go b/api/types_test.go new file mode 100644 index 000000000..408e3cce5 --- /dev/null +++ b/api/types_test.go @@ -0,0 +1,63 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package api + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTypes(t *testing.T) { + t.Parallel() + tests := []struct { + name string + json string + want any + wantErrContains string + }{ + { + name: "Duration-valid", + json: `"12345s"`, + want: Duration{Duration: 12345000000000}, + }, + { + name: "Duration-valid", + json: `"1h"`, + want: Duration{Duration: 3600000000000}, + }, + { + name: "UInt64String-InvalidNumber", + json: `"abcd"`, + want: Duration{Duration: 0}, + wantErrContains: "invalid duration \"abcd\"", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + assert, require := assert.New(t), require.New(t) + var got any + var err error + switch tc.want.(type) { + case Duration: + var i Duration + err = json.Unmarshal([]byte(tc.json), &i) + got = i + default: + t.Fatalf("invalid type for test: %s", tc.name) + } + if tc.wantErrContains != "" { + require.Error(err) + assert.Equal(tc.want, got) + assert.ErrorContains(err, tc.wantErrContains) + return + } + require.NoError(err) + assert.Equal(tc.want, got) + }) + } +} diff --git a/internal/api/genapi/parse.go b/internal/api/genapi/parse.go index a654343b6..90bad4cb4 100644 --- a/internal/api/genapi/parse.go +++ b/internal/api/genapi/parse.go @@ -126,7 +126,7 @@ func messageKind(fd protoreflect.FieldDescriptor) (ptr, pkg, name string) { case timestampName: return "", "time", "Time" case durationName: - return "", "time", "Duration" + return "", "api", "Duration" default: return "*", packageFromFullName(fd.Message().FullName()), string(fd.Message().Name()) }