Add documentation and tests to conversion.

pull/6/head
Daniel Smith 2014-08-01 14:24:12 -07:00
parent 5c0f5e85e2
commit 1cc7fce523
8 changed files with 331 additions and 145 deletions

View File

@ -31,6 +31,7 @@ func init() {
conversionScheme = conversion.NewScheme()
conversionScheme.InternalVersion = ""
conversionScheme.ExternalVersion = "v1beta1"
conversionScheme.MetaInsertionFactory = metaInsertion{}
AddKnownTypes("",
PodList{},
Pod{},
@ -248,3 +249,28 @@ func Decode(data []byte) (interface{}, error) {
func DecodeInto(data []byte, obj interface{}) error {
return conversionScheme.DecodeInto(data, obj)
}
// metaInsertion implements conversion.MetaInsertionFactory, which lets the conversion
// package figure out how to encode our object's types and versions. These fields are
// located in our JSONBase.
type metaInsertion struct {
JSONBase struct {
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
} `json:",inline" yaml:",inline"`
}
// Create returns a new metaInsertion with the version and kind fields set.
func (metaInsertion) Create(version, kind string) interface{} {
m := metaInsertion{}
m.JSONBase.APIVersion = version
m.JSONBase.Kind = kind
return &m
}
// Interpret returns the version and kind information from in, which must be
// a metaInsertion pointer object.
func (metaInsertion) Interpret(in interface{}) (version, kind string) {
m := in.(*metaInsertion)
return m.JSONBase.APIVersion, m.JSONBase.Kind
}

View File

@ -88,21 +88,21 @@ func (c *Converter) Register(conversionFunc interface{}) error {
type FieldMatchingFlags int
const (
// Loop through source fields, search for matching dest field
// to copy it into. Destination fields with no corresponding
// source field will be ignored.
SourceToDest FieldMatchingFlags = 1 << iota
// Loop through destiation fields, search for matching source
// field to copy it from. Source fields with no corresponding
// destination field will be ignored. If SourceToDest is
// specified, this flag is ignored. If niether is specified,
// this flag is the default.
DestFromSource
// or no flags are passed, this flag is the default.
DestFromSource FieldMatchingFlags = 0
// Loop through source fields, search for matching dest field
// to copy it into. Destination fields with no corresponding
// source field will be ignored.
SourceToDest FieldMatchingFlags = 1 << iota
// Don't treat it as an error if the corresponding source or
// dest field can't be found.
IgnoreMissingFields
// Don't require type names to match.
AllowDifferentFieldNames
AllowDifferentFieldTypeNames
)
// Returns true if the given flag or combination of flags is set.
@ -147,7 +147,7 @@ func (c *Converter) convert(sv, dv reflect.Value, flags FieldMatchingFlags) erro
return ret.(error)
}
if !flags.IsSet(AllowDifferentFieldNames) && dt.Name() != st.Name() {
if !flags.IsSet(AllowDifferentFieldTypeNames) && dt.Name() != st.Name() {
return fmt.Errorf("Can't convert %v to %v because type names don't match.", st, dt)
}

View File

@ -18,10 +18,13 @@ package conversion
import (
"fmt"
"reflect"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
)
func TestConverter(t *testing.T) {
func TestConverter_CallsRegisteredFunctions(t *testing.T) {
type A struct {
Foo string
}
@ -79,3 +82,128 @@ func TestConverter(t *testing.T) {
t.Errorf("unexpected non-error")
}
}
func TestConverter_fuzz(t *testing.T) {
newAnonType := func() interface{} {
return reflect.New(reflect.TypeOf(externalTypeReturn())).Interface()
}
// Use the same types from the scheme test.
table := []struct {
from, to, check interface{}
}{
{&TestType1{}, newAnonType(), &TestType1{}},
{newAnonType(), &TestType1{}, newAnonType()},
}
f := util.NewFuzzer()
c := NewConverter()
for i, item := range table {
for j := 0; j < *fuzzIters; j++ {
f.Fuzz(item.from)
err := c.Convert(item.from, item.to, 0)
if err != nil {
t.Errorf("(%v, %v): unexpected error: %v", i, j, err)
continue
}
err = c.Convert(item.to, item.check, 0)
if err != nil {
t.Errorf("(%v, %v): unexpected error: %v", i, j, err)
continue
}
if e, a := item.from, item.check; !reflect.DeepEqual(e, a) {
t.Errorf("(%v, %v): unexpected diff: %v", i, j, objDiff(e, a))
}
}
}
}
func TestConverter_flags(t *testing.T) {
type Foo struct{ A string }
type Bar struct{ A string }
table := []struct {
from, to interface{}
flags FieldMatchingFlags
shouldSucceed bool
}{
// Check that DestFromSource allows extra fields only in source.
{
from: &struct{ A string }{},
to: &struct{ A, B string }{},
flags: DestFromSource,
shouldSucceed: false,
}, {
from: &struct{ A, B string }{},
to: &struct{ A string }{},
flags: DestFromSource,
shouldSucceed: true,
},
// Check that SourceToDest allows for extra fields only in dest.
{
from: &struct{ A string }{},
to: &struct{ A, B string }{},
flags: SourceToDest,
shouldSucceed: true,
}, {
from: &struct{ A, B string }{},
to: &struct{ A string }{},
flags: SourceToDest,
shouldSucceed: false,
},
// Check that IgnoreMissingFields makes the above failure cases pass.
{
from: &struct{ A string }{},
to: &struct{ A, B string }{},
flags: DestFromSource | IgnoreMissingFields,
shouldSucceed: true,
}, {
from: &struct{ A, B string }{},
to: &struct{ A string }{},
flags: SourceToDest | IgnoreMissingFields,
shouldSucceed: true,
},
// Check that the field type name must match unless
// AllowDifferentFieldTypeNames is specified.
{
from: &struct{ A, B Foo }{},
to: &struct{ A Bar }{},
flags: DestFromSource,
shouldSucceed: false,
}, {
from: &struct{ A Foo }{},
to: &struct{ A, B Bar }{},
flags: SourceToDest,
shouldSucceed: false,
}, {
from: &struct{ A, B Foo }{},
to: &struct{ A Bar }{},
flags: DestFromSource | AllowDifferentFieldTypeNames,
shouldSucceed: true,
}, {
from: &struct{ A Foo }{},
to: &struct{ A, B Bar }{},
flags: SourceToDest | AllowDifferentFieldTypeNames,
shouldSucceed: true,
},
}
f := util.NewFuzzer()
c := NewConverter()
for i, item := range table {
for j := 0; j < *fuzzIters; j++ {
f.Fuzz(item.from)
err := c.Convert(item.from, item.to, item.flags)
if item.shouldSucceed && err != nil {
t.Errorf("(%v, %v): unexpected error: %v", i, j, err)
continue
}
if !item.shouldSucceed && err == nil {
t.Errorf("(%v, %v): unexpected non-error", i, j)
continue
}
}
}
}

View File

@ -28,12 +28,12 @@ import (
// s.InternalVersion type before being returned. Decode will refuse to decode
// objects without a version, because that's probably an error.
func (s *Scheme) Decode(data []byte) (interface{}, error) {
version, kind, err := s.DataAPIVersionAndKind(data)
version, kind, err := s.DataVersionAndKind(data)
if err != nil {
return nil, err
}
if version == "" {
return nil, fmt.Errorf("API Version not set in '%s'", string(data))
return nil, fmt.Errorf("version not set in '%s'", string(data))
}
obj, err := s.NewObject(version, kind)
if err != nil {
@ -47,8 +47,7 @@ func (s *Scheme) Decode(data []byte) (interface{}, error) {
}
// Version and Kind should be blank in memory.
blankVersionAndKind := s.MetaInsertionFactory.Create("", "")
err = s.converter.Convert(blankVersionAndKind, obj, SourceToDest|IgnoreMissingFields|AllowDifferentFieldNames)
err = s.SetVersionAndKind("", "", obj)
if err != nil {
return nil, err
}
@ -71,14 +70,14 @@ func (s *Scheme) Decode(data []byte) (interface{}, error) {
// DecodeInto parses a YAML or JSON string and stores it in obj. Returns an error
// if data.Kind is set and doesn't match the type of obj. Obj should be a
// pointer to an api type.
// If obj's APIVersion doesn't match that in data, an attempt will be made to convert
// If obj's version doesn't match that in data, an attempt will be made to convert
// data into obj's version.
func (s *Scheme) DecodeInto(data []byte, obj interface{}) error {
dataVersion, dataKind, err := s.DataAPIVersionAndKind(data)
dataVersion, dataKind, err := s.DataVersionAndKind(data)
if err != nil {
return err
}
objVersion, objKind, err := s.ObjectAPIVersionAndKind(obj)
objVersion, objKind, err := s.ObjectVersionAndKind(obj)
if err != nil {
return err
}
@ -120,10 +119,5 @@ func (s *Scheme) DecodeInto(data []byte, obj interface{}) error {
}
// Version and Kind should be blank in memory.
blankVersionAndKind := s.MetaInsertionFactory.Create("", "")
err = s.converter.Convert(blankVersionAndKind, obj, SourceToDest|IgnoreMissingFields|AllowDifferentFieldNames)
if err != nil {
return err
}
return nil
return s.SetVersionAndKind("", "", obj)
}

34
pkg/conversion/doc.go Normal file
View File

@ -0,0 +1,34 @@
/*
Copyright 2014 Google Inc. All rights reserved.
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 conversion provides go object versioning and encoding/decoding
// mechanisms.
//
// Specifically, conversion provides a way for you to define multiple versions
// of the same object. You may write functions which implement conversion logic,
// but for the fields which did not change, copying is automated. This makes it
// easy to modify the structures you use in memory without affecting the format
// you store on disk or respond to in your external API calls.
//
// The second offering of this package is automated encoding/decoding. The version
// and type of the object is recorded in the output, so it can be recreated upon
// reading. Currently, conversion writes JSON output, and interprets both JSON
// and YAML input.
//
// In the future, we plan to more explicitly separate the above two mechanisms, and
// add more serialization options, such as gob.
//
package conversion

View File

@ -17,7 +17,6 @@ limitations under the License.
package conversion
import (
"bytes"
"encoding/json"
"fmt"
)
@ -37,17 +36,17 @@ func (s *Scheme) EncodeOrDie(obj interface{}) string {
// struct. The type must have been registered.
//
// Memory/wire format differences:
// * Having to keep track of the Kind and APIVersion fields makes tests
// * Having to keep track of the Kind and Version fields makes tests
// very annoying, so the rule is that they are set only in wire format
// (json), not when in native (memory) format. This is possible because
// both pieces of information are implicit in the go typed object.
// * An exception: note that, if there are embedded API objects of known
// type, for example, PodList{... Items []Pod ...}, these embedded
// objects must be of the same version of the object they are embedded
// within, and their APIVersion and Kind must both be empty.
// * Note that the exception does not apply to the APIObject type, which
// recursively does Encode()/Decode(), and is capable of expressing any
// API object.
// within, and their Version and Kind must both be empty.
// * Note that the exception does not apply to a generic APIObject type
// which recursively does Encode()/Decode(), and is capable of
// expressing any API object.
// * Only versioned objects should be encoded. This means that, if you pass
// a native object, Encode will convert it to a versioned object. For
// example, an api.Pod will get converted to a v1beta1.Pod. However, if
@ -71,7 +70,7 @@ func (s *Scheme) EncodeToVersion(obj interface{}, destVersion string) (data []by
return nil, fmt.Errorf("type %v is not registered and it will be impossible to Decode it, therefore Encode will refuse to encode it.", v.Type())
}
objVersion, objKind, err := s.ObjectAPIVersionAndKind(obj)
objVersion, objKind, err := s.ObjectVersionAndKind(obj)
if err != nil {
return nil, err
}
@ -90,8 +89,7 @@ func (s *Scheme) EncodeToVersion(obj interface{}, destVersion string) (data []by
}
// Version and Kind should be set on the wire.
setVersionAndKind := s.MetaInsertionFactory.Create(destVersion, objKind)
err = s.converter.Convert(setVersionAndKind, obj, SourceToDest|IgnoreMissingFields|AllowDifferentFieldNames)
err = s.SetVersionAndKind(destVersion, objKind, obj)
if err != nil {
return nil, err
}
@ -102,35 +100,12 @@ func (s *Scheme) EncodeToVersion(obj interface{}, destVersion string) (data []by
return nil, err
}
// Version and Kind should be blank in memory.
blankVersionAndKind := s.MetaInsertionFactory.Create("", "")
err = s.converter.Convert(blankVersionAndKind, obj, SourceToDest|IgnoreMissingFields|AllowDifferentFieldNames)
// Version and Kind should be blank in memory. Reset them, since it's
// possible that we modified a user object and not a copy above.
err = s.SetVersionAndKind("", "", obj)
if err != nil {
return nil, err
}
return data, nil
meta, err := json.Marshal(s.MetaInsertionFactory.Create(destVersion, objKind))
if err != nil {
return nil, err
}
// Stick these together, omitting the last } from meta and the first { from
// data. Add a comma to meta if necessary.
metaN := len(meta)
if len(data) > 2 {
meta[metaN-1] = ',' // Add comma
} else {
meta = meta[:metaN-1] // Just remove }
}
together := append(meta, data[1:]...)
if s.Indent {
var out bytes.Buffer
err := json.Indent(&out, together, "", " ")
if err != nil {
return nil, err
}
return out.Bytes(), nil
}
return together, nil
}

View File

@ -25,7 +25,7 @@ import (
// MetaInsertionFactory is used to create an object to store and retrieve
// the version and kind information for all objects. The default uses the
// keys "apiVersion" and "kind" respectively. The object produced by this
// keys "version" and "kind" respectively. The object produced by this
// factory is used to clear the version and kind fields in memory, so it
// must match the layout of your actual api structs. (E.g., if you have your
// version and kind field inside an inlined struct, this must produce an
@ -34,15 +34,12 @@ type MetaInsertionFactory interface {
// Create should make a new object with two fields.
// This object will be used to encode this metadata along with your
// API objects, so the tags on the fields you use shouldn't conflict.
Create(apiVersion, kind string) interface{}
Create(version, kind string) interface{}
// Interpret should take the same type of object that Create creates.
// It should return the version and kind information from this object.
Interpret(interface{}) (apiVersion, kind string)
Interpret(interface{}) (version, kind string)
}
// Default is a global scheme.
var Default = NewScheme()
// Scheme defines an entire encoding and decoding scheme.
type Scheme struct {
// versionMap allows one to figure out the go type of an object with
@ -70,7 +67,7 @@ type Scheme struct {
// MetaInsertionFactory is used to create an object to store and retrieve
// the version and kind information for all objects. The default uses the
// keys "apiVersion" and "kind" respectively.
// keys "version" and "kind" respectively.
MetaInsertionFactory MetaInsertionFactory
}
@ -86,8 +83,10 @@ func NewScheme() *Scheme {
}
}
// AddKnownTypes registers the types of the arguments to the marshaller of the package api.
// Encode() refuses the object unless its type is registered with AddKnownTypes.
// AddKnownTypes registers all types passed in 'types' as being members of version 'version.
// Encode() will refuse objects unless their type has been registered with AddKnownTypes.
// All objects passed to types should be structs, not pointers to structs. The name that go
// reports for the struct becomes the "kind" field when encoding.
func (s *Scheme) AddKnownTypes(version string, types ...interface{}) {
knownTypes, found := s.versionMap[version]
if !found {
@ -117,17 +116,18 @@ func (s *Scheme) NewObject(versionName, typeName string) (interface{}, error) {
}
// AddConversionFuncs adds functions to the list of conversion functions. The given
// functions should know how to convert between two API objects. We deduce how to call
// it from the types of its two parameters; see the comment for Converter.Register.
// functions should know how to convert between two of your API objects, or their
// sub-objects. We deduce how to call these functions from the types of their two
// parameters; see the comment for Converter.Register.
//
// Note that, if you need to copy sub-objects that didn't change, it's safe to call
// Convert() inside your conversionFuncs, as long as you don't start a conversion
// s.Convert() inside your conversionFuncs, as long as you don't start a conversion
// chain that's infinitely recursive.
//
// Also note that the default behavior, if you don't add a conversion function, is to
// sanely copy fields that have the same names. It's OK if the destination type has
// extra fields, but it must not remove any. So you only need to add a conversion
// function for things with changed/removed fields.
// sanely copy fields that have the same names and same type names. It's OK if the
// destination type has extra fields, but it must not remove any. So you only need to
// add conversion functions for things with changed/removed fields.
func (s *Scheme) AddConversionFuncs(conversionFuncs ...interface{}) error {
for _, f := range conversionFuncs {
err := s.converter.Register(f)
@ -138,8 +138,8 @@ func (s *Scheme) AddConversionFuncs(conversionFuncs ...interface{}) error {
return nil
}
// Convert will attempt to convert in into out. Both must be pointers.
// For easy testing of conversion functions. Returns an error if the conversion isn't
// Convert will attempt to convert in into out. Both must be pointers. For easy
// testing of conversion functions. Returns an error if the conversion isn't
// possible.
func (s *Scheme) Convert(in, out interface{}) error {
return s.converter.Convert(in, out, 0)
@ -147,32 +147,30 @@ func (s *Scheme) Convert(in, out interface{}) error {
// metaInsertion provides a default implementation of MetaInsertionFactory.
type metaInsertion struct {
JSONBase struct {
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
} `json:",inline" yaml:",inline"`
Version string `json:"version,omitempty" yaml:"version,omitempty"`
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
}
// Create should make a new object with two fields.
// This object will be used to encode this metadata along with your
// API objects, so the tags on the fields you use shouldn't conflict.
func (metaInsertion) Create(apiVersion, kind string) interface{} {
func (metaInsertion) Create(version, kind string) interface{} {
m := metaInsertion{}
m.JSONBase.APIVersion = apiVersion
m.JSONBase.Kind = kind
m.Version = version
m.Kind = kind
return &m
}
// Interpret should take the same type of object that Create creates.
// It should return the version and kind information from this object.
func (metaInsertion) Interpret(in interface{}) (apiVersion, kind string) {
func (metaInsertion) Interpret(in interface{}) (version, kind string) {
m := in.(*metaInsertion)
return m.JSONBase.APIVersion, m.JSONBase.Kind
return m.Version, m.Kind
}
// DataAPIVersionAndKind will return the APIVersion and Kind of the given wire-format
// enconding of an API Object, or an error.
func (s *Scheme) DataAPIVersionAndKind(data []byte) (apiVersion, kind string, err error) {
func (s *Scheme) DataVersionAndKind(data []byte) (version, kind string, err error) {
findKind := s.MetaInsertionFactory.Create("", "")
// yaml is a superset of json, so we use it to decode here. That way,
// we understand both.
@ -180,13 +178,13 @@ func (s *Scheme) DataAPIVersionAndKind(data []byte) (apiVersion, kind string, er
if err != nil {
return "", "", fmt.Errorf("couldn't get version/kind: %v", err)
}
apiVersion, kind = s.MetaInsertionFactory.Interpret(findKind)
return apiVersion, kind, nil
version, kind = s.MetaInsertionFactory.Interpret(findKind)
return version, kind, nil
}
// ObjectVersionAndKind returns the API version and kind of the go object,
// or an error if it's not a pointer or is unregistered.
func (s *Scheme) ObjectAPIVersionAndKind(obj interface{}) (apiVersion, kind string, err error) {
func (s *Scheme) ObjectVersionAndKind(obj interface{}) (apiVersion, kind string, err error) {
v, err := enforcePtr(obj)
if err != nil {
return "", "", err
@ -199,6 +197,14 @@ func (s *Scheme) ObjectAPIVersionAndKind(obj interface{}) (apiVersion, kind stri
}
}
// SetVersionAndKind sets the version and kind fields (with help from
// MetaInsertionFactory). Returns an error if this isn't possible. obj
// must be a pointer.
func (s *Scheme) SetVersionAndKind(version, kind string, obj interface{}) error {
versionAndKind := s.MetaInsertionFactory.Create(version, kind)
return s.converter.Convert(versionAndKind, obj, SourceToDest|IgnoreMissingFields|AllowDifferentFieldTypeNames)
}
// maybeCopy copies obj if it is not a pointer, to get a settable/addressable
// object. Guaranteed to return a pointer.
func maybeCopy(obj interface{}) interface{} {

View File

@ -28,31 +28,33 @@ import (
var fuzzIters = flag.Int("fuzz_iters", 10, "How many fuzzing iterations to do.")
// Intended to be compatible with the default implementation of MetaInsertionFactory
type JSONBase struct {
// Test a weird version/kind embedding format.
type MyWeirdCustomEmbeddedVersionKindField struct {
ID string `yaml:"ID,omitempty" json:"ID,omitempty"`
APIVersion string `json:"apiVersion,omitempty" yaml:"apiVersion,omitempty"`
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
APIVersion string `json:"myVersionKey,omitempty" yaml:"myVersionKey,omitempty"`
ObjectKind string `json:"myKindKey,omitempty" yaml:"myKindKey,omitempty"`
Z string `yaml:"Z,omitempty" json:"Z,omitempty"`
Y uint64 `yaml:"Y,omitempty" json:"Y,omitempty"`
}
type TestType1 struct {
JSONBase `json:",inline" yaml:",inline"`
A string `yaml:"A,omitempty" json:"A,omitempty"`
B int `yaml:"B,omitempty" json:"B,omitempty"`
C int8 `yaml:"C,omitempty" json:"C,omitempty"`
D int16 `yaml:"D,omitempty" json:"D,omitempty"`
E int32 `yaml:"E,omitempty" json:"E,omitempty"`
F int64 `yaml:"F,omitempty" json:"F,omitempty"`
G uint `yaml:"G,omitempty" json:"G,omitempty"`
H uint8 `yaml:"H,omitempty" json:"H,omitempty"`
I uint16 `yaml:"I,omitempty" json:"I,omitempty"`
J uint32 `yaml:"J,omitempty" json:"J,omitempty"`
K uint64 `yaml:"K,omitempty" json:"K,omitempty"`
L bool `yaml:"L,omitempty" json:"L,omitempty"`
M map[string]int `yaml:"M,omitempty" json:"M,omitempty"`
N map[string]TestType2 `yaml:"N,omitempty" json:"N,omitempty"`
O *TestType2 `yaml:"O,omitempty" json:"O,omitempty"`
P []TestType2 `yaml:"Q,omitempty" json:"Q,omitempty"`
MyWeirdCustomEmbeddedVersionKindField `json:",inline" yaml:",inline"`
A string `yaml:"A,omitempty" json:"A,omitempty"`
B int `yaml:"B,omitempty" json:"B,omitempty"`
C int8 `yaml:"C,omitempty" json:"C,omitempty"`
D int16 `yaml:"D,omitempty" json:"D,omitempty"`
E int32 `yaml:"E,omitempty" json:"E,omitempty"`
F int64 `yaml:"F,omitempty" json:"F,omitempty"`
G uint `yaml:"G,omitempty" json:"G,omitempty"`
H uint8 `yaml:"H,omitempty" json:"H,omitempty"`
I uint16 `yaml:"I,omitempty" json:"I,omitempty"`
J uint32 `yaml:"J,omitempty" json:"J,omitempty"`
K uint64 `yaml:"K,omitempty" json:"K,omitempty"`
L bool `yaml:"L,omitempty" json:"L,omitempty"`
M map[string]int `yaml:"M,omitempty" json:"M,omitempty"`
N map[string]TestType2 `yaml:"N,omitempty" json:"N,omitempty"`
O *TestType2 `yaml:"O,omitempty" json:"O,omitempty"`
P []TestType2 `yaml:"Q,omitempty" json:"Q,omitempty"`
}
type TestType2 struct {
@ -69,39 +71,39 @@ func externalTypeReturn() interface{} {
B int `yaml:"B,omitempty" json:"B,omitempty"`
}
type TestType1 struct {
JSONBase `json:",inline" yaml:",inline"`
A string `yaml:"A,omitempty" json:"A,omitempty"`
B int `yaml:"B,omitempty" json:"B,omitempty"`
C int8 `yaml:"C,omitempty" json:"C,omitempty"`
D int16 `yaml:"D,omitempty" json:"D,omitempty"`
E int32 `yaml:"E,omitempty" json:"E,omitempty"`
F int64 `yaml:"F,omitempty" json:"F,omitempty"`
G uint `yaml:"G,omitempty" json:"G,omitempty"`
H uint8 `yaml:"H,omitempty" json:"H,omitempty"`
I uint16 `yaml:"I,omitempty" json:"I,omitempty"`
J uint32 `yaml:"J,omitempty" json:"J,omitempty"`
K uint64 `yaml:"K,omitempty" json:"K,omitempty"`
L bool `yaml:"L,omitempty" json:"L,omitempty"`
M map[string]int `yaml:"M,omitempty" json:"M,omitempty"`
N map[string]TestType2 `yaml:"N,omitempty" json:"N,omitempty"`
O *TestType2 `yaml:"O,omitempty" json:"O,omitempty"`
P []TestType2 `yaml:"Q,omitempty" json:"Q,omitempty"`
MyWeirdCustomEmbeddedVersionKindField `json:",inline" yaml:",inline"`
A string `yaml:"A,omitempty" json:"A,omitempty"`
B int `yaml:"B,omitempty" json:"B,omitempty"`
C int8 `yaml:"C,omitempty" json:"C,omitempty"`
D int16 `yaml:"D,omitempty" json:"D,omitempty"`
E int32 `yaml:"E,omitempty" json:"E,omitempty"`
F int64 `yaml:"F,omitempty" json:"F,omitempty"`
G uint `yaml:"G,omitempty" json:"G,omitempty"`
H uint8 `yaml:"H,omitempty" json:"H,omitempty"`
I uint16 `yaml:"I,omitempty" json:"I,omitempty"`
J uint32 `yaml:"J,omitempty" json:"J,omitempty"`
K uint64 `yaml:"K,omitempty" json:"K,omitempty"`
L bool `yaml:"L,omitempty" json:"L,omitempty"`
M map[string]int `yaml:"M,omitempty" json:"M,omitempty"`
N map[string]TestType2 `yaml:"N,omitempty" json:"N,omitempty"`
O *TestType2 `yaml:"O,omitempty" json:"O,omitempty"`
P []TestType2 `yaml:"Q,omitempty" json:"Q,omitempty"`
}
return TestType1{}
}
type ExternalInternalSame struct {
JSONBase `json:",inline" yaml:",inline"`
A TestType2 `yaml:"A,omitempty" json:"A,omitempty"`
MyWeirdCustomEmbeddedVersionKindField `json:",inline" yaml:",inline"`
A TestType2 `yaml:"A,omitempty" json:"A,omitempty"`
}
// TestObjectFuzzer can randomly populate all the above objects.
var TestObjectFuzzer = util.NewFuzzer(
func(j *JSONBase) {
// We have to customize the randomization of JSONBases because their
func(j *MyWeirdCustomEmbeddedVersionKindField) {
// We have to customize the randomization of MyWeirdCustomEmbeddedVersionKindFields because their
// APIVersion and Kind must remain blank in memory.
j.APIVersion = ""
j.Kind = ""
j.ObjectKind = ""
j.ID = util.RandString()
},
func(u *uint64) {
@ -125,9 +127,32 @@ func GetTestScheme() *Scheme {
s.AddKnownTypes("v1", externalTypeReturn(), ExternalInternalSame{})
s.ExternalVersion = "v1"
s.InternalVersion = ""
s.MetaInsertionFactory = testMetaInsertionFactory{}
return s
}
type testMetaInsertionFactory struct {
MyWeirdCustomEmbeddedVersionKindField struct {
APIVersion string `json:"myVersionKey,omitempty" yaml:"myVersionKey,omitempty"`
ObjectKind string `json:"myKindKey,omitempty" yaml:"myKindKey,omitempty"`
} `json:",inline" yaml:",inline"`
}
// Create returns a new testMetaInsertionFactory with the version and kind fields set.
func (testMetaInsertionFactory) Create(version, kind string) interface{} {
m := testMetaInsertionFactory{}
m.MyWeirdCustomEmbeddedVersionKindField.APIVersion = version
m.MyWeirdCustomEmbeddedVersionKindField.ObjectKind = kind
return &m
}
// Interpret returns the version and kind information from in, which must be
// a testMetaInsertionFactory pointer object.
func (testMetaInsertionFactory) Interpret(in interface{}) (version, kind string) {
m := in.(*testMetaInsertionFactory)
return m.MyWeirdCustomEmbeddedVersionKindField.APIVersion, m.MyWeirdCustomEmbeddedVersionKindField.ObjectKind
}
func objDiff(a, b interface{}) string {
ab, err := json.Marshal(a)
if err != nil {
@ -162,22 +187,20 @@ func runTest(t *testing.T, source interface{}) {
if err != nil {
t.Errorf("%v: %v (%v)", name, err, string(data))
return
} else {
if !reflect.DeepEqual(source, obj2) {
t.Errorf("1: %v: diff: %v", name, objDiff(source, obj2))
return
}
}
if !reflect.DeepEqual(source, obj2) {
t.Errorf("1: %v: diff: %v", name, objDiff(source, obj2))
return
}
obj3 := reflect.New(reflect.TypeOf(source).Elem()).Interface()
err = s.DecodeInto(data, obj3)
if err != nil {
t.Errorf("2: %v: %v", name, err)
return
} else {
if !reflect.DeepEqual(source, obj3) {
t.Errorf("3: %v: diff: %v", name, objDiff(source, obj3))
return
}
}
if !reflect.DeepEqual(source, obj3) {
t.Errorf("3: %v: diff: %v", name, objDiff(source, obj3))
return
}
}
@ -231,17 +254,17 @@ func TestEncode_Ptr(t *testing.T) {
func TestBadJSONRejection(t *testing.T) {
s := GetTestScheme()
badJSONs := [][]byte{
[]byte(`{"apiVersion":"v1"}`), // Missing kind
[]byte(`{"kind":"TestType1"}`), // Missing version
[]byte(`{"apiVersion":"v1","kind":"bar"}`), // Unknown kind
[]byte(`{"apiVersion":"bar","kind":"TestType1"}`), // Unknown version
[]byte(`{"myVersionKey":"v1"}`), // Missing kind
[]byte(`{"myKindKey":"TestType1"}`), // Missing version
[]byte(`{"myVersionKey":"v1","myKindKey":"bar"}`), // Unknown kind
[]byte(`{"myVersionKey":"bar","myKindKey":"TestType1"}`), // Unknown version
}
for _, b := range badJSONs {
if _, err := s.Decode(b); err == nil {
t.Errorf("Did not reject bad json: %s", string(b))
}
}
badJSONKindMismatch := []byte(`{"apiVersion":"v1","kind": "ExternalInternalSame"}`)
badJSONKindMismatch := []byte(`{"myVersionKey":"v1","myKindKey":"ExternalInternalSame"}`)
if err := s.DecodeInto(badJSONKindMismatch, &TestType1{}); err == nil {
t.Errorf("Kind is set but doesn't match the object type: %s", badJSONKindMismatch)
}