Skip to content

Commit

Permalink
Support marshalling/unmarshalling mapped interface (#123)
Browse files Browse the repository at this point in the history
  • Loading branch information
at-wat authored Mar 14, 2020
1 parent 4a63b76 commit 938207e
Show file tree
Hide file tree
Showing 4 changed files with 221 additions and 92 deletions.
50 changes: 39 additions & 11 deletions marshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import (
// ErrUnsupportedElement means that a element name is known but unsupported in this version of ebml-go.
var ErrUnsupportedElement = errors.New("unsupported element")

// ErrNonStringMapKey is returned if input is map and key is not a string.
var ErrNonStringMapKey = errors.New("non-string map key")

// Marshal struct to EBML bytes.
//
// Examples of struct field tags:
Expand Down Expand Up @@ -108,21 +111,46 @@ func deepIsZero(v reflect.Value) bool {
}

func marshalImpl(vo reflect.Value, w io.Writer, pos uint64, parent *Element, options *MarshalOptions) (uint64, error) {
l := vo.NumField()
for i := 0; i < l; i++ {
vn := vo.Field(i)
tn := vo.Type().Field(i)
var l int
var tagFieldFunc func(int) (*structTag, reflect.Value, error)

tag := &structTag{}
if n, ok := tn.Tag.Lookup("ebml"); ok {
var err error
if tag, err = parseTag(n); err != nil {
return pos, err
switch vo.Kind() {
case reflect.Struct:
l = vo.NumField()
tagFieldFunc = func(i int) (*structTag, reflect.Value, error) {
tag := &structTag{}
tn := vo.Type().Field(i)
if n, ok := tn.Tag.Lookup("ebml"); ok {
var err error
if tag, err = parseTag(n); err != nil {
return nil, reflect.Value{}, err
}
}
if tag.name == "" {
tag.name = tn.Name
}
return tag, vo.Field(i), nil
}
case reflect.Map:
l = vo.Len()
keys := vo.MapKeys()
tagFieldFunc = func(i int) (*structTag, reflect.Value, error) {
name := keys[i]
if name.Kind() != reflect.String {
return nil, reflect.Value{}, ErrNonStringMapKey
}
return &structTag{name: name.String()}, vo.MapIndex(name), nil
}
if tag.name == "" {
tag.name = tn.Name
default:
return pos, ErrIncompatibleType
}

for i := 0; i < l; i++ {
tag, vn, err := tagFieldFunc(i)
if err != nil {
return pos, err
}

t, err := ElementTypeFromString(tag.name)
if err != nil {
return pos, err
Expand Down
143 changes: 91 additions & 52 deletions marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,97 +69,125 @@ func TestMarshal(t *testing.T) {

testCases := map[string]struct {
input interface{}
expected []byte
expected [][]byte // one of
}{
"Omitempty": {
&struct{ EBML TestOmitempty }{},
[]byte{
0x1a, 0x45, 0xDF, 0xA3, 0x80,
},
[][]byte{{0x1a, 0x45, 0xDF, 0xA3, 0x80}},
},
"NoOmitempty": {
&struct{ EBML TestNoOmitempty }{},
[]byte{
0x1A, 0x45, 0xDF, 0xA3, 0x8B,
0x42, 0x82, 0x81, 0x00,
0x42, 0x87, 0x81, 0x00,
0x53, 0xAB, 0x80,
[][]byte{
{
0x1A, 0x45, 0xDF, 0xA3, 0x8B,
0x42, 0x82, 0x81, 0x00,
0x42, 0x87, 0x81, 0x00,
0x53, 0xAB, 0x80,
},
},
},
"SliceOmitempty": {
&struct {
EBML TestSliceOmitempty
}{TestSliceOmitempty{make([]uint64, 0)}},
[]byte{
0x1a, 0x45, 0xDF, 0xA3, 0x80,
},
[][]byte{{0x1a, 0x45, 0xDF, 0xA3, 0x80}},
},
"SliceOmitemptyNested": {
&struct {
EBML []TestSliceOmitempty `ebml:"EBML,omitempty"`
}{make([]TestSliceOmitempty, 3)},
[]byte{},
[][]byte{{}},
},
"SliceNoOmitempty": {
&struct {
EBML TestSliceNoOmitempty
}{TestSliceNoOmitempty{make([]uint64, 2)}},
[]byte{
0x1a, 0x45, 0xDF, 0xA3, 0x88,
0x42, 0x87, 0x81, 0x00,
0x42, 0x87, 0x81, 0x00,
[][]byte{
{
0x1a, 0x45, 0xDF, 0xA3, 0x88,
0x42, 0x87, 0x81, 0x00,
0x42, 0x87, 0x81, 0x00,
},
},
},
"Sized": {
&struct{ EBML TestSized }{TestSized{"a", 1, 0.0, 0.0, []byte{0x01}}},
[]byte{
0x1A, 0x45, 0xDF, 0xA3, 0xA2,
0x42, 0x82, 0x83, 0x61, 0x00, 0x00,
0x42, 0x87, 0x82, 0x00, 0x01,
0x44, 0x89, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x44, 0x89, 0x84, 0x00, 0x00, 0x00, 0x00,
0x53, 0xAB, 0x82, 0x01, 0x00,
[][]byte{
{
0x1A, 0x45, 0xDF, 0xA3, 0xA2,
0x42, 0x82, 0x83, 0x61, 0x00, 0x00,
0x42, 0x87, 0x82, 0x00, 0x01,
0x44, 0x89, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x44, 0x89, 0x84, 0x00, 0x00, 0x00, 0x00,
0x53, 0xAB, 0x82, 0x01, 0x00,
},
},
},
"SizedAndOverflow": {
&struct{ EBML TestSized }{TestSized{"abc", 0x012345, 0.0, 0.0, []byte{0x01, 0x02, 0x03}}},
[]byte{
0x1A, 0x45, 0xDF, 0xA3, 0xA5,
0x42, 0x82, 0x84, 0x61, 0x62, 0x63, 0x00,
0x42, 0x87, 0x83, 0x01, 0x23, 0x45,
0x44, 0x89, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x44, 0x89, 0x84, 0x00, 0x00, 0x00, 0x00,
0x53, 0xAB, 0x83, 0x01, 0x02, 0x03,
[][]byte{
{
0x1A, 0x45, 0xDF, 0xA3, 0xA5,
0x42, 0x82, 0x84, 0x61, 0x62, 0x63, 0x00,
0x42, 0x87, 0x83, 0x01, 0x23, 0x45,
0x44, 0x89, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x44, 0x89, 0x84, 0x00, 0x00, 0x00, 0x00,
0x53, 0xAB, 0x83, 0x01, 0x02, 0x03,
},
},
},
"Ptr": {
&struct{ EBML TestPtr }{TestPtr{&str, &uinteger}},
[]byte{
0x1A, 0x45, 0xDF, 0xA3, 0x88,
0x42, 0x82, 0x81, 0x00,
0x42, 0x87, 0x81, 0x00,
[][]byte{
{
0x1A, 0x45, 0xDF, 0xA3, 0x88,
0x42, 0x82, 0x81, 0x00,
0x42, 0x87, 0x81, 0x00,
},
},
},
"PtrOmitempty": {
&struct{ EBML TestPtrOmitempty }{TestPtrOmitempty{&str, &uinteger}},
[]byte{
0x1A, 0x45, 0xDF, 0xA3, 0x80,
},
[][]byte{{0x1A, 0x45, 0xDF, 0xA3, 0x80}},
},
"Interface": {
&struct{ EBML TestInterface }{TestInterface{str, uinteger}},
[]byte{
0x1A, 0x45, 0xDF, 0xA3, 0x88,
0x42, 0x82, 0x81, 0x00,
0x42, 0x87, 0x81, 0x00,
[][]byte{
{
0x1A, 0x45, 0xDF, 0xA3, 0x88,
0x42, 0x82, 0x81, 0x00,
0x42, 0x87, 0x81, 0x00,
},
},
},
"InterfacePtr": {
&struct{ EBML TestInterface }{TestInterface{&str, &uinteger}},
[]byte{
0x1A, 0x45, 0xDF, 0xA3, 0x88,
0x42, 0x82, 0x81, 0x00,
0x42, 0x87, 0x81, 0x00,
[][]byte{
{
0x1A, 0x45, 0xDF, 0xA3, 0x88,
0x42, 0x82, 0x81, 0x00,
0x42, 0x87, 0x81, 0x00,
},
},
},
"Map": {
&map[string]interface{}{
"Info": map[string]interface{}{
"MuxingApp": "test",
"WritingApp": "abcd",
},
},
[][]byte{
{
0x15, 0x49, 0xA9, 0x66, 0x90,
0x4D, 0x80, 0x85, 0x74, 0x65, 0x73, 0x74, 0x00,
0x57, 0x41, 0x85, 0x61, 0x62, 0x63, 0x64, 0x00,
},
{ // Go map element order is unstable
0x15, 0x49, 0xA9, 0x66, 0x90,
0x57, 0x41, 0x85, 0x61, 0x62, 0x63, 0x64, 0x00,
0x4D, 0x80, 0x85, 0x74, 0x65, 0x73, 0x74, 0x00,
},
},
},
"Block": {
Expand All @@ -168,31 +196,31 @@ func TestMarshal(t *testing.T) {
TrackNumber: 0x01, Timecode: 0x0123, Lacing: LacingNo, Data: [][]byte{{0x01}},
},
},
[]byte{0xA3, 0x85, 0x81, 0x01, 0x23, 0x00, 0x01},
[][]byte{{0xA3, 0x85, 0x81, 0x01, 0x23, 0x00, 0x01}},
},
"BlockXiph": {
&TestBlocks{
Block: Block{
TrackNumber: 0x01, Timecode: 0x0123, Lacing: LacingXiph, Data: [][]byte{{0x01}, {0x02}},
},
},
[]byte{0xA3, 0x88, 0x81, 0x01, 0x23, 0x02, 0x01, 0x01, 0x01, 0x02},
[][]byte{{0xA3, 0x88, 0x81, 0x01, 0x23, 0x02, 0x01, 0x01, 0x01, 0x02}},
},
"BlockFixed": {
&TestBlocks{
Block: Block{
TrackNumber: 0x01, Timecode: 0x0123, Lacing: LacingFixed, Data: [][]byte{{0x01}, {0x02}},
},
},
[]byte{0xA3, 0x87, 0x81, 0x01, 0x23, 0x04, 0x01, 0x01, 0x02},
[][]byte{{0xA3, 0x87, 0x81, 0x01, 0x23, 0x04, 0x01, 0x01, 0x02}},
},
"BlockEBML": {
&TestBlocks{
Block: Block{
TrackNumber: 0x01, Timecode: 0x0123, Lacing: LacingEBML, Data: [][]byte{{0x01}, {0x02}},
},
},
[]byte{0xA3, 0x88, 0x81, 0x01, 0x23, 0x06, 0x01, 0x81, 0x01, 0x02},
[][]byte{{0xA3, 0x88, 0x81, 0x01, 0x23, 0x06, 0x01, 0x81, 0x01, 0x02}},
},
}

Expand All @@ -202,9 +230,12 @@ func TestMarshal(t *testing.T) {
if err := Marshal(c.input, &b); err != nil {
t.Fatalf("Unexpected error: '%v'", err)
}
if !bytes.Equal(c.expected, b.Bytes()) {
t.Errorf("Marshaled binary doesn't match:\n expected: %v,\n got: %v", c.expected, b.Bytes())
for _, expected := range c.expected {
if bytes.Equal(expected, b.Bytes()) {
return
}
}
t.Errorf("Marshaled binary doesn't match:\n expected one of:\n%v,\ngot:\n%v", c.expected, b.Bytes())
})
}
}
Expand All @@ -224,6 +255,14 @@ func TestMarshal_Error(t *testing.T) {
}{},
ErrUnknownElementName,
},
"InvalidMapKey": {
&map[int]interface{}{1: "test"},
ErrNonStringMapKey,
},
"InvalidType": {
&[]int{},
ErrIncompatibleType,
},
}
for n, c := range testCases {
t.Run(n, func(t *testing.T) {
Expand Down
Loading

0 comments on commit 938207e

Please sign in to comment.