feat(values): Add methods to get bool and time values from proto
This commit is contained in:
1 parent
09cece64bc
commit
2c2bd3429b
2 files changed
+177
No files matched your search
@@ -5,6 +5,7 @@ package values
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
@@ -37,6 +38,52 @@ func GetStringValue(in *structpb.Struct, k string, required bool) (string, error
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// GetBoolValue returns a boolean value and no error if the given key is found
|
||||
// in the provided proto struct input. An error is returned if the key is not
|
||||
// found and required is true, or the value type is not a boolean.
|
||||
func GetBoolValue(in *structpb.Struct, k string, required bool) (bool, error) {
|
||||
mv := in.GetFields()
|
||||
v, ok := mv[k]
|
||||
if !ok {
|
||||
if required {
|
||||
return false, fmt.Errorf("missing required value %q", k)
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
b, ok := v.AsInterface().(bool)
|
||||
if !ok {
|
||||
return false, fmt.Errorf("unexpected type for value %q: want bool, got %T", k, v.AsInterface())
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// GetTimeValue returns a time.Time object and no error if the given key is
|
||||
// found in the provided proto struct input. An error is returned if the value
|
||||
// in the struct isn't a string or the value is not parsable by time.Parse. The
|
||||
// expected time layout is RFC3339Nano.
|
||||
func GetTimeValue(in *structpb.Struct, k string) (time.Time, error) {
|
||||
mv := in.GetFields()
|
||||
v, ok := mv[k]
|
||||
if !ok {
|
||||
return time.Time{}, nil
|
||||
}
|
||||
|
||||
tRaw, ok := v.AsInterface().(string)
|
||||
if !ok {
|
||||
return time.Time{}, fmt.Errorf("unexpected type for value %q: want string, got %T", k, v.AsInterface())
|
||||
}
|
||||
|
||||
t, err := time.Parse(time.RFC3339Nano, tRaw)
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("could not parse time in value %q: %w", k, err)
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// StructFields returns a map[string]struct{} of the proto struct input.
|
||||
func StructFields(s *structpb.Struct) map[string]struct{} {
|
||||
m := make(map[string]struct{}, len(s.GetFields()))
|
||||
|
||||
@@ -5,6 +5,7 @@ package values
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
@@ -78,3 +79,132 @@ func TestGetStringValue(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetBoolValue(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
in map[string]any
|
||||
key string
|
||||
required bool
|
||||
expected bool
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "required missing",
|
||||
in: map[string]any{},
|
||||
key: "foo",
|
||||
required: true,
|
||||
expectedErr: "missing required value \"foo\"",
|
||||
},
|
||||
{
|
||||
name: "optional missing",
|
||||
in: map[string]any{},
|
||||
key: "foo",
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "non-bool value",
|
||||
in: map[string]any{
|
||||
"foo": "bar",
|
||||
},
|
||||
key: "foo",
|
||||
expectedErr: "unexpected type for value \"foo\": want bool, got string",
|
||||
},
|
||||
{
|
||||
name: "good",
|
||||
in: map[string]any{
|
||||
"foo": true,
|
||||
},
|
||||
key: "foo",
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
input, err := structpb.NewStruct(tc.in)
|
||||
require.NoError(err)
|
||||
|
||||
actual, err := GetBoolValue(input, tc.key, tc.required)
|
||||
if tc.expectedErr != "" {
|
||||
require.EqualError(err, tc.expectedErr)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(err)
|
||||
require.Equal(tc.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTimeValue(t *testing.T) {
|
||||
staticTime := time.Now()
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
in map[string]any
|
||||
key string
|
||||
expected time.Time
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "missing",
|
||||
in: map[string]any{},
|
||||
key: "foo",
|
||||
expected: time.Time{},
|
||||
},
|
||||
{
|
||||
name: "non-time value",
|
||||
in: map[string]any{
|
||||
"foo": 1,
|
||||
},
|
||||
key: "foo",
|
||||
expectedErr: "unexpected type for value \"foo\": want string, got float64",
|
||||
},
|
||||
{
|
||||
name: "bad parse",
|
||||
in: map[string]any{
|
||||
"foo": "bar",
|
||||
},
|
||||
key: "foo",
|
||||
expectedErr: "could not parse time in value \"foo\": parsing time \"bar\" as \"2006-01-02T15:04:05.999999999Z07:00\": cannot parse \"bar\" as \"2006\"",
|
||||
},
|
||||
{
|
||||
name: "good",
|
||||
in: map[string]any{
|
||||
"foo": staticTime.Format(time.RFC3339Nano),
|
||||
},
|
||||
key: "foo",
|
||||
expected: func() time.Time {
|
||||
u, err := time.Parse(time.RFC3339Nano, staticTime.Format(time.RFC3339Nano))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return u
|
||||
}(),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
tc := tc
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
input, err := structpb.NewStruct(tc.in)
|
||||
require.NoError(err)
|
||||
|
||||
actual, err := GetTimeValue(input, tc.key)
|
||||
if tc.expectedErr != "" {
|
||||
require.EqualError(err, tc.expectedErr)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(err)
|
||||
require.Equal(tc.expected, actual)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user