Implement cbor.Marshaler and cbor.Unmarshaler for metav1.MicroTime.

This commit is contained in:
Ben Luddy 2024-05-17 11:52:30 -04:00
parent 7b3129e015
commit 14367eee5a
No known key found for this signature in database
GPG Key ID: A6551E73A5974C30
2 changed files with 110 additions and 0 deletions

View File

@ -19,6 +19,8 @@ package v1
import (
"encoding/json"
"time"
cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct"
)
const RFC3339Micro = "2006-01-02T15:04:05.000000Z07:00"
@ -129,6 +131,25 @@ func (t *MicroTime) UnmarshalJSON(b []byte) error {
return nil
}
func (t *MicroTime) UnmarshalCBOR(b []byte) error {
var s *string
if err := cbor.Unmarshal(b, &s); err != nil {
return err
}
if s == nil {
t.Time = time.Time{}
return nil
}
parsed, err := time.Parse(RFC3339Micro, *s)
if err != nil {
return err
}
t.Time = parsed.Local()
return nil
}
// UnmarshalQueryParameter converts from a URL query parameter value to an object
func (t *MicroTime) UnmarshalQueryParameter(str string) error {
if len(str) == 0 {
@ -160,6 +181,13 @@ func (t MicroTime) MarshalJSON() ([]byte, error) {
return json.Marshal(t.UTC().Format(RFC3339Micro))
}
func (t MicroTime) MarshalCBOR() ([]byte, error) {
if t.IsZero() {
return cbor.Marshal(nil)
}
return cbor.Marshal(t.UTC().Format(RFC3339Micro))
}
// OpenAPISchemaType is used by the kube-openapi generator when constructing
// the OpenAPI spec of this type.
//

View File

@ -18,11 +18,16 @@ package v1
import (
"encoding/json"
"fmt"
"reflect"
"testing"
"time"
cbor "k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct"
"sigs.k8s.io/yaml"
"github.com/google/go-cmp/cmp"
fuzz "github.com/google/gofuzz"
)
type MicroTimeHolder struct {
@ -113,6 +118,59 @@ func TestMicroTimeUnmarshalJSON(t *testing.T) {
}
}
func TestMicroTimeMarshalCBOR(t *testing.T) {
for _, tc := range []struct {
name string
in MicroTime
out []byte
}{
{name: "zero value", in: MicroTime{}, out: []byte{0xf6}}, // null
{name: "no fractional seconds", in: DateMicro(1998, time.May, 5, 5, 5, 5, 0, time.UTC), out: []byte("\x58\x1b1998-05-05T05:05:05.000000Z")}, // '1998-05-05T05:05:05.000000Z'
{name: "nanoseconds truncated", in: DateMicro(1998, time.May, 5, 5, 5, 5, 5050, time.UTC), out: []byte("\x58\x1b1998-05-05T05:05:05.000005Z")}, // '1998-05-05T05:05:05.000005Z'
} {
t.Run(fmt.Sprintf("%+v", tc.in), func(t *testing.T) {
got, err := tc.in.MarshalCBOR()
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(tc.out, got); diff != "" {
t.Errorf("unexpected output:\n%s", diff)
}
})
}
}
func TestMicroTimeUnmarshalCBOR(t *testing.T) {
for _, tc := range []struct {
name string
in []byte
out MicroTime
errMessage string
}{
{name: "null", in: []byte{0xf6}, out: MicroTime{}}, // null
{name: "valid", in: []byte("\x58\x1b1998-05-05T05:05:05.000000Z"), out: MicroTime{Time: Date(1998, time.May, 5, 5, 5, 5, 0, time.UTC).Local()}}, // '1998-05-05T05:05:05.000000Z'
{name: "invalid cbor type", in: []byte{0x07}, out: MicroTime{}, errMessage: "cbor: cannot unmarshal positive integer into Go value of type string"}, // 7
{name: "malformed timestamp", in: []byte("\x45hello"), out: MicroTime{}, errMessage: `parsing time "hello" as "2006-01-02T15:04:05.000000Z07:00": cannot parse "hello" as "2006"`}, // 'hello'
} {
t.Run(tc.name, func(t *testing.T) {
var got MicroTime
err := got.UnmarshalCBOR(tc.in)
if err != nil {
if tc.errMessage == "" {
t.Fatalf("want nil error, got: %v", err)
} else if gotMessage := err.Error(); tc.errMessage != gotMessage {
t.Fatalf("want error: %q, got: %q", tc.errMessage, gotMessage)
}
} else if tc.errMessage != "" {
t.Fatalf("got nil error, want: %s", tc.errMessage)
}
if diff := cmp.Diff(tc.out, got); diff != "" {
t.Errorf("unexpected output:\n%s", diff)
}
})
}
}
func TestMicroTimeProto(t *testing.T) {
cases := []struct {
input MicroTime
@ -318,3 +376,27 @@ func TestMicroTimeProtoUnmarshalRaw(t *testing.T) {
}
}
func TestMicroTimeRoundtripCBOR(t *testing.T) {
fuzzer := fuzz.New()
for i := 0; i < 500; i++ {
var initial, final MicroTime
fuzzer.Fuzz(&initial)
b, err := cbor.Marshal(initial)
if err != nil {
t.Errorf("error encoding %v: %v", initial, err)
continue
}
err = cbor.Unmarshal(b, &final)
if err != nil {
t.Errorf("%v: error decoding %v: %v", initial, string(b), err)
}
if !final.Equal(&initial) {
diag, err := cbor.Diagnose(b)
if err != nil {
t.Logf("failed to produce diagnostic encoding of 0x%x: %v", b, err)
}
t.Errorf("expected equal: %v, %v (cbor was '%s')", initial, final, diag)
}
}
}