From 1f3ff5323bc74095e489a1b7403b7a8790da08fc Mon Sep 17 00:00:00 2001 From: Mike Schinkel Date: Wed, 29 Jan 2025 00:28:32 -0500 Subject: [PATCH 1/2] Allow `*interface{}` --- decode.go | 15 ++--- decode_test.go | 164 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+), 7 deletions(-) diff --git a/decode.go b/decode.go index a365a2e..98d0640 100644 --- a/decode.go +++ b/decode.go @@ -91,6 +91,14 @@ func (d *Decoder) Decode(v interface{}) error { } func (d *Decoder) unmarshal(pval *plistValue, v reflect.Value) error { + + if v.Kind() == reflect.Ptr { + if v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) + } + v = v.Elem() + } + // check for empty interface v type if v.Kind() == reflect.Interface && v.NumMethod() == 0 { val := reflect.ValueOf(d.valueInterface(pval)) @@ -101,13 +109,6 @@ func (d *Decoder) unmarshal(pval *plistValue, v reflect.Value) error { return nil } - if v.Kind() == reflect.Ptr { - if v.IsNil() { - v.Set(reflect.New(v.Type().Elem())) - } - v = v.Elem() - } - unmarshalerType := reflect.TypeOf((*Unmarshaler)(nil)).Elem() if v.CanInterface() && v.Type().Implements(unmarshalerType) { diff --git a/decode_test.go b/decode_test.go index e0b9d75..30936fb 100644 --- a/decode_test.go +++ b/decode_test.go @@ -555,3 +555,167 @@ func TestXMLPlutilParity(t *testing.T) { } } } + +// TestDecodeWithDict decodes with a dictionary into a field with a custom type +// that requires a UnmarshalPlist() method and using a subset of a plist file +// from github.com/ProfileManifests/ProfileManifests +func TestDecodeWithDict(t *testing.T) { + r := bytes.NewBuffer([]byte(yamlForTestDecodeWithDict)) + decoder := NewXMLDecoder(r) + pm := ProfileManifestTestType{} + err := decoder.Decode(&pm) + if err != nil { + t.Errorf("Failed to decode profile manifest: %s", err.Error()) + } + if have, want := pm.Domain, "com.apple.dock"; have != want { + t.Errorf("have %s, want %s", have, want) + return + } + if have, want := len(pm.Subkeys), 1; have != want { + t.Errorf("have %d, want %d", have, want) + return + } + if have, want := pm.Subkeys[0].Name, "static-apps"; have != want { + t.Errorf("have %s, want %s", have, want) + return + } + if have, want := len(pm.Subkeys[0].Subkeys), 1; have != want { + t.Errorf("have %d, want %d", have, want) + return + } + if have, want := len(pm.Subkeys[0].Subkeys[0].Subkeys), 2; have != want { + t.Errorf("have %d, want %d", have, want) + return + } + if have, want := pm.Subkeys[0].Subkeys[0].Subkeys[0].Default.Value, "file-tile"; have != want { + t.Errorf("have %s, want %s", have, want) + return + } + if have, want := len(pm.Subkeys[0].Subkeys[0].Subkeys[1].Subkeys), 7; have != want { + t.Errorf("have %d, want %d", have, want) + return + } + if have, want := pm.Subkeys[0].Subkeys[0].Subkeys[1].Subkeys[1].Default.Value, uint64(100); have != want { + t.Errorf("have %d, want %d", have, want) + return + } + if have, want := pm.Subkeys[0].Subkeys[0].Subkeys[1].Subkeys[2].Default.Value, 1.234; have != want { + t.Errorf("have %f, want %f", have, want) + return + } +} + +type TestValueForDecodeWithDict struct { + Value interface{} +} + +// UnmarshalPlist only captures the value and assigns to Value.value which is of +func (v *TestValueForDecodeWithDict) UnmarshalPlist(f func(interface{}) error) (err error) { + var value interface{} + err = f(&value) + if err != nil { + return err + } + v.Value = value + return nil +} + +type ManifestKeyTestType struct { + Name string `plist:"pfm_name"` + Default *TestValueForDecodeWithDict `plist:"pfm_default,omitempty"` + Subkeys []ManifestKeyTestType `plist:"pfm_subkeys,omitempty"` +} + +type ProfileManifestTestType struct { + Domain string `plist:"pfm_domain"` + Subkeys []ManifestKeyTestType `plist:"pfm_subkeys"` +} + +// yamlForTestDecodeWithDict is a subset of this: +// — https://github.com/ProfileManifests/ProfileManifests/tree/master/Manifests/ManifestsApple/com.apple.dock.plist +// With a few modifications to give data to test for +const yamlForTestDecodeWithDict = ` + + + + pfm_domain + com.apple.dock + pfm_subkeys + + + pfm_name + static-apps + pfm_subkeys + + + pfm_name + StaticItem + pfm_subkeys + + + pfm_default + file-tile + pfm_name + tile-type + + + pfm_name + tile-data + pfm_subkeys + + + pfm_name + label + + + pfm_default + 100 + pfm_name + Amount + + + pfm_default + 1.234 + pfm_name + Fraction + + + pfm_name + file-label + + + pfm_name + file-type + + + pfm_name + file-data + pfm_subkeys + + + pfm_default + example.com + pfm_name + _CFURLString + + + pfm_default + whatever + pfm_name + _CFURLStringType + + + + + pfm_name + url + + + + + + + + + +` From 2b4a694e5d4144dc96d165d72202510a5df68083 Mon Sep 17 00:00:00 2001 From: Kory Prince Date: Sat, 15 Mar 2025 01:33:48 -0500 Subject: [PATCH 2/2] use a minimal test case --- decode.go | 1 - decode_test.go | 203 ++++++++++++------------------------------------- 2 files changed, 50 insertions(+), 154 deletions(-) diff --git a/decode.go b/decode.go index 98d0640..f16f4fb 100644 --- a/decode.go +++ b/decode.go @@ -91,7 +91,6 @@ func (d *Decoder) Decode(v interface{}) error { } func (d *Decoder) unmarshal(pval *plistValue, v reflect.Value) error { - if v.Kind() == reflect.Ptr { if v.IsNil() { v.Set(reflect.New(v.Type().Elem())) diff --git a/decode_test.go b/decode_test.go index 30936fb..59c7faa 100644 --- a/decode_test.go +++ b/decode_test.go @@ -556,166 +556,63 @@ func TestXMLPlutilParity(t *testing.T) { } } -// TestDecodeWithDict decodes with a dictionary into a field with a custom type -// that requires a UnmarshalPlist() method and using a subset of a plist file -// from github.com/ProfileManifests/ProfileManifests -func TestDecodeWithDict(t *testing.T) { - r := bytes.NewBuffer([]byte(yamlForTestDecodeWithDict)) - decoder := NewXMLDecoder(r) - pm := ProfileManifestTestType{} - err := decoder.Decode(&pm) - if err != nil { - t.Errorf("Failed to decode profile manifest: %s", err.Error()) - } - if have, want := pm.Domain, "com.apple.dock"; have != want { - t.Errorf("have %s, want %s", have, want) - return - } - if have, want := len(pm.Subkeys), 1; have != want { - t.Errorf("have %d, want %d", have, want) - return - } - if have, want := pm.Subkeys[0].Name, "static-apps"; have != want { - t.Errorf("have %s, want %s", have, want) - return - } - if have, want := len(pm.Subkeys[0].Subkeys), 1; have != want { - t.Errorf("have %d, want %d", have, want) - return - } - if have, want := len(pm.Subkeys[0].Subkeys[0].Subkeys), 2; have != want { - t.Errorf("have %d, want %d", have, want) - return - } - if have, want := pm.Subkeys[0].Subkeys[0].Subkeys[0].Default.Value, "file-tile"; have != want { - t.Errorf("have %s, want %s", have, want) - return - } - if have, want := len(pm.Subkeys[0].Subkeys[0].Subkeys[1].Subkeys), 7; have != want { - t.Errorf("have %d, want %d", have, want) - return - } - if have, want := pm.Subkeys[0].Subkeys[0].Subkeys[1].Subkeys[1].Default.Value, uint64(100); have != want { - t.Errorf("have %d, want %d", have, want) - return - } - if have, want := pm.Subkeys[0].Subkeys[0].Subkeys[1].Subkeys[2].Default.Value, 1.234; have != want { - t.Errorf("have %f, want %f", have, want) - return - } +type testVal struct { + s string + b bool } -type TestValueForDecodeWithDict struct { - Value interface{} -} - -// UnmarshalPlist only captures the value and assigns to Value.value which is of -func (v *TestValueForDecodeWithDict) UnmarshalPlist(f func(interface{}) error) (err error) { - var value interface{} - err = f(&value) +func (v *testVal) UnmarshalPlist(f func(interface{}) error) (err error) { + var val interface{} + err = f(&val) if err != nil { return err } - v.Value = value + switch value := val.(type) { + case string: + v.s = value + case bool: + v.b = value + } return nil } -type ManifestKeyTestType struct { - Name string `plist:"pfm_name"` - Default *TestValueForDecodeWithDict `plist:"pfm_default,omitempty"` - Subkeys []ManifestKeyTestType `plist:"pfm_subkeys,omitempty"` -} +type nestedType struct { + Val *testVal `plist:"val"` + Val2 *testVal `plist:"val2"` +} + +// TestDecodeCustomType tests decoding a type that decodes into multiple types +// based on the underlying plist type +func TestDecodeCustomType(t *testing.T) { + p := ` + + + + val + val + val2 + + + ` + r := bytes.NewBuffer([]byte(p)) + decoder := NewXMLDecoder(r) + typ := new(nestedType) + err := decoder.Decode(typ) + if err != nil { + t.Fatalf("could not read profile: %v", err) + } -type ProfileManifestTestType struct { - Domain string `plist:"pfm_domain"` - Subkeys []ManifestKeyTestType `plist:"pfm_subkeys"` -} + if typ.Val == nil { + t.Fatal("unexpected nil for typ.Val") + } + if have, want := typ.Val.s, "val"; have != want { + t.Errorf("typ.Val: have %v, want %v", have, want) + } -// yamlForTestDecodeWithDict is a subset of this: -// — https://github.com/ProfileManifests/ProfileManifests/tree/master/Manifests/ManifestsApple/com.apple.dock.plist -// With a few modifications to give data to test for -const yamlForTestDecodeWithDict = ` - - - - pfm_domain - com.apple.dock - pfm_subkeys - - - pfm_name - static-apps - pfm_subkeys - - - pfm_name - StaticItem - pfm_subkeys - - - pfm_default - file-tile - pfm_name - tile-type - - - pfm_name - tile-data - pfm_subkeys - - - pfm_name - label - - - pfm_default - 100 - pfm_name - Amount - - - pfm_default - 1.234 - pfm_name - Fraction - - - pfm_name - file-label - - - pfm_name - file-type - - - pfm_name - file-data - pfm_subkeys - - - pfm_default - example.com - pfm_name - _CFURLString - - - pfm_default - whatever - pfm_name - _CFURLStringType - - - - - pfm_name - url - - - - - - - - - -` + if typ.Val2 == nil { + t.Fatal("unexpected nil for typ.Val2") + } + if have, want := typ.Val2.b, true; have != want { + t.Errorf("typ.Val2: have %v, want %v", have, want) + } +}