Reject custom marshalers from direct CBOR Marshal and Unmarshal.

Types that implement any of the stdlib text and JSON marshaler and unmarshaler interfaces without
implementing the corresponding CBOR interfaces are currently rejected by the CBOR serializer. This
is a temporary measure for the initial alpha; such types will ultimately be handled via automatic
transcoding. The "cbor/direct" subpackage exports Marshal and Unmarshal functions to support the
implementation of custom CBOR marshalling and unmarshalling behaviors, but did not include the
safeguard against handling non-CBOR custom marshalers.
This commit is contained in:
Ben Luddy 2024-10-30 16:32:11 -04:00
parent dc1d7f41ef
commit 0b7b42cc10
No known key found for this signature in database
GPG Key ID: A6551E73A5974C30
2 changed files with 106 additions and 0 deletions

View File

@ -23,14 +23,39 @@ import (
"k8s.io/apimachinery/pkg/runtime/serializer/cbor/internal/modes"
)
// Marshal serializes a value to CBOR. If there is more than one way to encode the value, it will
// make the same choice as the CBOR implementation of runtime.Serializer.
//
// Note: Support for CBOR is at an alpha stage. If the value (or, for composite types, any of its
// nested values) implement any of the interfaces encoding.TextMarshaler, encoding.TextUnmarshaler,
// encoding/json.Marshaler, or encoding/json.Unmarshaler, a non-nil error will be returned unless
// the value also implements the corresponding CBOR interfaces. This limitation will ultimately be
// removed in favor of automatic transcoding to CBOR.
func Marshal(src interface{}) ([]byte, error) {
if err := modes.RejectCustomMarshalers(src); err != nil {
return nil, err
}
return modes.Encode.Marshal(src)
}
// Unmarshal deserializes from CBOR into an addressable value. If there is more than one way to
// unmarshal a value, it will make the same choice as the CBOR implementation of runtime.Serializer.
//
// Note: Support for CBOR is at an alpha stage. If the value (or, for composite types, any of its
// nested values) implement any of the interfaces encoding.TextMarshaler, encoding.TextUnmarshaler,
// encoding/json.Marshaler, or encoding/json.Unmarshaler, a non-nil error will be returned unless
// the value also implements the corresponding CBOR interfaces. This limitation will ultimately be
// removed in favor of automatic transcoding to CBOR.
func Unmarshal(src []byte, dst interface{}) error {
if err := modes.RejectCustomMarshalers(dst); err != nil {
return err
}
return modes.Decode.Unmarshal(src, dst)
}
// Diagnose accepts well-formed CBOR bytes and returns a string representing the same data item in
// human-readable diagnostic notation (RFC 8949 Section 8). The diagnostic notation is not meant to
// be parsed.
func Diagnose(src []byte) (string, error) {
return modes.Diagnostic.Diagnose(src)
}

View File

@ -0,0 +1,81 @@
/*
Copyright 2024 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package direct_test
import (
"encoding"
"encoding/json"
"fmt"
"reflect"
"testing"
"k8s.io/apimachinery/pkg/runtime/serializer/cbor/direct"
)
var _ json.Marshaler = CustomJSONMarshaler{}
type CustomJSONMarshaler struct{}
func (CustomJSONMarshaler) MarshalJSON() ([]byte, error) {
panic("unimplemented")
}
var _ json.Unmarshaler = CustomJSONUnmarshaler{}
type CustomJSONUnmarshaler struct{}
func (CustomJSONUnmarshaler) UnmarshalJSON([]byte) error {
panic("unimplemented")
}
var _ encoding.TextMarshaler = CustomTextMarshaler{}
type CustomTextMarshaler struct{}
func (CustomTextMarshaler) MarshalText() ([]byte, error) {
panic("unimplemented")
}
var _ encoding.TextUnmarshaler = CustomTextUnmarshaler{}
type CustomTextUnmarshaler struct{}
func (CustomTextUnmarshaler) UnmarshalText([]byte) error {
panic("unimplemented")
}
func TestRejectsCustom(t *testing.T) {
for _, tc := range []struct {
value interface{}
iface reflect.Type
}{
{value: CustomJSONMarshaler{}, iface: reflect.TypeFor[json.Marshaler]()},
{value: CustomJSONUnmarshaler{}, iface: reflect.TypeFor[json.Unmarshaler]()},
{value: CustomTextMarshaler{}, iface: reflect.TypeFor[encoding.TextMarshaler]()},
{value: CustomTextUnmarshaler{}, iface: reflect.TypeFor[encoding.TextUnmarshaler]()},
} {
t.Run(fmt.Sprintf("%T", tc.value), func(t *testing.T) {
want := fmt.Sprintf("unable to serialize %T: %T implements %s without corresponding cbor interface", tc.value, tc.value, tc.iface.String())
if _, err := direct.Marshal(tc.value); err == nil || err.Error() != want {
t.Errorf("want error: %q, got: %v", want, err)
}
if err := direct.Unmarshal(nil, tc.value); err == nil || err.Error() != want {
t.Errorf("want error: %q, got: %v", want, err)
}
})
}
}