Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposal for solving api plugability #1239

Merged
merged 5 commits into from
Sep 15, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 20 additions & 0 deletions pkg/api/serialization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,26 @@ var fuzzIters = flag.Int("fuzz_iters", 50, "How many fuzzing iterations to do.")

// apiObjectFuzzer can randomly populate api objects.
var apiObjectFuzzer = fuzz.New().NilChance(.5).NumElements(1, 1).Funcs(
func(j *runtime.PluginBase, c fuzz.Continue) {
// Do nothing; this struct has only a Kind field and it must stay blank in memory.
},
func(j *runtime.JSONBase, c fuzz.Continue) {
// We have to customize the randomization of JSONBases because their
// APIVersion and Kind must remain blank in memory.
j.APIVersion = ""
j.Kind = ""
j.ID = c.RandString()
// TODO: Fix JSON/YAML packages and/or write custom encoding
// for uint64's. Somehow the LS *byte* of this is lost, but
// only when all 8 bytes are set.
j.ResourceVersion = c.RandUint64() >> 8
j.SelfLink = c.RandString()

var sec, nsec int64
c.Fuzz(&sec)
c.Fuzz(&nsec)
j.CreationTimestamp = util.Unix(sec, nsec).Rfc3339Copy()
},
func(j *api.JSONBase, c fuzz.Continue) {
// We have to customize the randomization of JSONBases because their
// APIVersion and Kind must remain blank in memory.
Expand Down
133 changes: 118 additions & 15 deletions pkg/conversion/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,18 @@ type Converter struct {

// If non-nil, will be called to print helpful debugging info. Quite verbose.
Debug DebugLogger

// NameFunc is called to retrieve the name of a type; this name is used for the
// purpose of deciding whether two types match or not (i.e., will we attempt to
// do a conversion). The default returns the go type name.
NameFunc func(t reflect.Type) string
}

// NewConverter creates a new Converter object.
func NewConverter() *Converter {
return &Converter{
funcs: map[typePair]reflect.Value{},
funcs: map[typePair]reflect.Value{},
NameFunc: func(t reflect.Type) string { return t.Name() },
}
}

Expand All @@ -55,6 +61,80 @@ type Scope interface {
// Call Convert to convert sub-objects. Note that if you call it with your own exact
// parameters, you'll run out of stack space before anything useful happens.
Convert(src, dest interface{}, flags FieldMatchingFlags) error

// SrcTags and DestTags contain the struct tags that src and dest had, respectively.
// If the enclosing object was not a struct, then these will contain no tags, of course.
SrcTag() reflect.StructTag
DestTag() reflect.StructTag

// Flags returns the flags with which the conversion was started.
Flags() FieldMatchingFlags

// Meta returns any information originally passed to Convert.
Meta() *Meta
}

// Meta is supplied by Scheme, when it calls Convert.
type Meta struct {
SrcVersion string
DestVersion string

// TODO: If needed, add a user data field here.
}

// scope contains information about an ongoing conversion.
type scope struct {
converter *Converter
meta *Meta
flags FieldMatchingFlags
srcTagStack []reflect.StructTag
destTagStack []reflect.StructTag
}

// push adds a level to the src/dest tag stacks.
func (s *scope) push() {
s.srcTagStack = append(s.srcTagStack, "")
s.destTagStack = append(s.destTagStack, "")
}

// pop removes a level to the src/dest tag stacks.
func (s *scope) pop() {
n := len(s.srcTagStack)
s.srcTagStack = s.srcTagStack[:n-1]
s.destTagStack = s.destTagStack[:n-1]
}

func (s *scope) setSrcTag(tag reflect.StructTag) {
s.srcTagStack[len(s.srcTagStack)-1] = tag
}

func (s *scope) setDestTag(tag reflect.StructTag) {
s.destTagStack[len(s.destTagStack)-1] = tag
}

// Convert continues a conversion.
func (s *scope) Convert(src, dest interface{}, flags FieldMatchingFlags) error {
return s.converter.Convert(src, dest, flags, s.meta)
}

// SrcTag returns the tag of the struct containing the current source item, if any.
func (s *scope) SrcTag() reflect.StructTag {
return s.srcTagStack[len(s.srcTagStack)-1]
}

// DestTag returns the tag of the struct containing the current dest item, if any.
func (s *scope) DestTag() reflect.StructTag {
return s.destTagStack[len(s.destTagStack)-1]
}

// Flags returns the flags with which the current conversion was started.
func (s *scope) Flags() FieldMatchingFlags {
return s.flags
}

// Meta returns the meta object that was originally passed to Convert.
func (s *scope) Meta() *Meta {
return s.meta
}

// Register registers a conversion func with the Converter. conversionFunc must take
Expand Down Expand Up @@ -82,7 +162,7 @@ func (c *Converter) Register(conversionFunc interface{}) error {
if ft.In(1).Kind() != reflect.Ptr {
return fmt.Errorf("expected pointer arg for 'in' param 1, got: %v", ft)
}
scopeType := Scope(c)
scopeType := Scope(nil)
if e, a := reflect.TypeOf(&scopeType).Elem(), ft.In(2); e != a {
return fmt.Errorf("expected '%v' arg for 'in' param 2, got '%v' (%v)", e, a, ft)
}
Expand Down Expand Up @@ -127,8 +207,12 @@ func (f FieldMatchingFlags) IsSet(flag FieldMatchingFlags) bool {
// Convert will translate src to dest if it knows how. Both must be pointers.
// If no conversion func is registered and the default copying mechanism
// doesn't work on this type pair, an error will be returned.
// Read the comments on the various FieldMatchingFlags constants to understand
// what the 'flags' parameter does.
// 'meta' is given to allow you to pass information to conversion functions,
// it is not used by Convert() other than storing it in the scope.
// Not safe for objects with cyclic references!
func (c *Converter) Convert(src, dest interface{}, flags FieldMatchingFlags) error {
func (c *Converter) Convert(src, dest interface{}, flags FieldMatchingFlags, meta *Meta) error {
dv, sv := reflect.ValueOf(dest), reflect.ValueOf(src)
if dv.Kind() != reflect.Ptr {
return fmt.Errorf("Need pointer, but got %#v", dest)
Expand All @@ -141,18 +225,24 @@ func (c *Converter) Convert(src, dest interface{}, flags FieldMatchingFlags) err
if !dv.CanAddr() {
return fmt.Errorf("Can't write to dest")
}
return c.convert(sv, dv, flags)
s := &scope{
converter: c,
flags: flags,
meta: meta,
}
s.push() // Easy way to make SrcTag and DestTag never fail
return c.convert(sv, dv, s)
}

// convert recursively copies sv into dv, calling an appropriate conversion function if
// one is registered.
func (c *Converter) convert(sv, dv reflect.Value, flags FieldMatchingFlags) error {
func (c *Converter) convert(sv, dv reflect.Value, scope *scope) error {
dt, st := dv.Type(), sv.Type()
if fv, ok := c.funcs[typePair{st, dt}]; ok {
if c.Debug != nil {
c.Debug.Logf("Calling custom conversion of '%v' to '%v'", st, dt)
}
args := []reflect.Value{sv.Addr(), dv.Addr(), reflect.ValueOf(Scope(c))}
args := []reflect.Value{sv.Addr(), dv.Addr(), reflect.ValueOf(scope)}
ret := fv.Call(args)[0].Interface()
// This convolution is necssary because nil interfaces won't convert
// to errors.
Expand All @@ -162,7 +252,7 @@ func (c *Converter) convert(sv, dv reflect.Value, flags FieldMatchingFlags) erro
return ret.(error)
}

if !flags.IsSet(AllowDifferentFieldTypeNames) && dt.Name() != st.Name() {
if !scope.flags.IsSet(AllowDifferentFieldTypeNames) && c.NameFunc(dt) != c.NameFunc(st) {
return fmt.Errorf("Can't convert %v to %v because type names don't match.", st, dt)
}

Expand All @@ -180,28 +270,41 @@ func (c *Converter) convert(sv, dv reflect.Value, flags FieldMatchingFlags) erro
c.Debug.Logf("Trying to convert '%v' to '%v'", st, dt)
}

scope.push()
defer scope.pop()

switch dv.Kind() {
case reflect.Struct:
listType := dt
if flags.IsSet(SourceToDest) {
if scope.flags.IsSet(SourceToDest) {
listType = st
}
for i := 0; i < listType.NumField(); i++ {
f := listType.Field(i)
df := dv.FieldByName(f.Name)
sf := sv.FieldByName(f.Name)
if sf.IsValid() {
// No need to check error, since we know it's valid.
field, _ := st.FieldByName(f.Name)
scope.setSrcTag(field.Tag)
}
if df.IsValid() {
field, _ := dt.FieldByName(f.Name)
scope.setDestTag(field.Tag)
}
// TODO: set top level of scope.src/destTagStack with these field tags here.
if !df.IsValid() || !sf.IsValid() {
switch {
case flags.IsSet(IgnoreMissingFields):
case scope.flags.IsSet(IgnoreMissingFields):
// No error.
case flags.IsSet(SourceToDest):
case scope.flags.IsSet(SourceToDest):
return fmt.Errorf("%v not present in dest (%v to %v)", f.Name, st, dt)
default:
return fmt.Errorf("%v not present in src (%v to %v)", f.Name, st, dt)
}
continue
}
if err := c.convert(sf, df, flags); err != nil {
if err := c.convert(sf, df, scope); err != nil {
return err
}
}
Expand All @@ -213,7 +316,7 @@ func (c *Converter) convert(sv, dv reflect.Value, flags FieldMatchingFlags) erro
}
dv.Set(reflect.MakeSlice(dt, sv.Len(), sv.Cap()))
for i := 0; i < sv.Len(); i++ {
if err := c.convert(sv.Index(i), dv.Index(i), flags); err != nil {
if err := c.convert(sv.Index(i), dv.Index(i), scope); err != nil {
return err
}
}
Expand All @@ -224,7 +327,7 @@ func (c *Converter) convert(sv, dv reflect.Value, flags FieldMatchingFlags) erro
return nil
}
dv.Set(reflect.New(dt.Elem()))
return c.convert(sv.Elem(), dv.Elem(), flags)
return c.convert(sv.Elem(), dv.Elem(), scope)
case reflect.Map:
if sv.IsNil() {
// Don't copy a nil ptr!
Expand All @@ -234,11 +337,11 @@ func (c *Converter) convert(sv, dv reflect.Value, flags FieldMatchingFlags) erro
dv.Set(reflect.MakeMap(dt))
for _, sk := range sv.MapKeys() {
dk := reflect.New(dt.Key()).Elem()
if err := c.convert(sk, dk, flags); err != nil {
if err := c.convert(sk, dk, scope); err != nil {
return err
}
dkv := reflect.New(dt.Elem()).Elem()
if err := c.convert(sv.MapIndex(sk), dkv, flags); err != nil {
if err := c.convert(sv.MapIndex(sk), dkv, scope); err != nil {
return err
}
dv.SetMapIndex(dk, dkv)
Expand Down