From 2431b2bb5977457dfdc634534e6e53d82a9677d6 Mon Sep 17 00:00:00 2001 From: Daniel Fireman Date: Mon, 9 Oct 2017 20:30:41 -0300 Subject: [PATCH] Fix a panic caused when the number of fields in the struct is smaller than the number of fields in the schema. Also adding more tests and making sure we respect the schema declaration order of the cells. #61 --- schema/schema.go | 28 +++++++++++++++++++++++----- schema/schema_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/schema/schema.go b/schema/schema.go index c6f5b20..178e48a 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -7,6 +7,7 @@ import ( "net/http" "os" "reflect" + "sort" "strings" "sync" "time" @@ -18,7 +19,7 @@ import ( // it refers to a field that does not exist in the schema. const InvalidPosition = -1 -// Unexportet tagname for the tableheader +// Unexported tagname for the tableheader const tableheaderTag = "tableheader" // Read reads and parses a descriptor to create a schema. @@ -225,15 +226,27 @@ func (s *Schema) Decode(row []string, out interface{}) error { return nil } +type encodedCell struct { + pos int + val string +} + +type encodedRow []encodedCell + +func (r encodedRow) Len() int { return len(r) } +func (r encodedRow) Swap(i, j int) { r[i], r[j] = r[j], r[i] } +func (r encodedRow) Less(i, j int) bool { return r[i].pos < r[j].pos } + // Encode encodes struct into a row. This method can only encode structs (or pointer to structs) and // will error out if nil is passed. +// The order of the cells in the returned row is the schema declaration order. func (s *Schema) Encode(in interface{}) ([]string, error) { inValue := reflect.Indirect(reflect.ValueOf(in)) if inValue.Kind() != reflect.Struct { return nil, fmt.Errorf("can only encode structs and does not support nil pointers") } inType := inValue.Type() - row := make([]string, inType.NumField()) + var row encodedRow for i := 0; i < inType.NumField(); i++ { structFieldValue := inValue.Field(i) fieldName, ok := inType.Field(i).Tag.Lookup(tableheaderTag) @@ -242,14 +255,19 @@ func (s *Schema) Encode(in interface{}) ([]string, error) { } f, fieldIndex := s.GetField(fieldName) if fieldIndex != InvalidPosition { - r, err := f.Encode(structFieldValue.Interface()) + cell, err := f.Encode(structFieldValue.Interface()) if err != nil { return nil, err } - row[fieldIndex] = r + row = append(row, encodedCell{fieldIndex, cell}) } } - return row, nil + sort.Sort(row) + ret := make([]string, len(row)) + for i := range row { + ret[i] = row[i].val + } + return ret, nil } func (s *Schema) isMissingValue(value string) bool { diff --git a/schema/schema_test.go b/schema/schema_test.go index d465ccb..e84c935 100644 --- a/schema/schema_test.go +++ b/schema/schema_test.go @@ -565,6 +565,36 @@ func TestSchema_Encode(t *testing.T) { t.Fatalf("val want:%v got:%v", want, got) } }) + t.Run("SuccessSchemaMoreFieldsThanStruct", func(t *testing.T) { + s := Schema{Fields: Fields{{Name: "Age", Type: IntegerType}, {Name: "Name", Type: StringType}}} + in := csvRow{Name: "Foo"} + got, err := s.Encode(&in) + if err != nil { + t.Fatalf("err want:nil got:%q", err) + } + want := []string{"Foo"} + if !reflect.DeepEqual(want, got) { + t.Fatalf("val want:%v got:%v", want, got) + } + }) + t.Run("SuccessStructHasMoreFieldsThanSchema", func(t *testing.T) { + // Note: deliberately changed the order to make stuff more interesting. + type rowType struct { + Age int + Name string + Bar float64 + Bez string + } + s := Schema{Fields: []Field{{Name: "Name", Type: StringType}, {Name: "Bez", Type: StringType}}} + got, err := s.Encode(rowType{Age: 42, Bez: "Bez", Name: "Foo"}) + if err != nil { + t.Fatalf("err want:nil got:%q", err) + } + want := []string{"Foo", "Bez"} + if !reflect.DeepEqual(want, got) { + t.Fatalf("val want:%v got:%v", want, got) + } + }) t.Run("Error_Encoding", func(t *testing.T) { type rowType struct { Age string