github.com/billybanfield/evergreen@v0.0.0-20170525200750-eeee692790f7/util/csv.go (about) 1 package util 2 3 import ( 4 "encoding/csv" 5 "fmt" 6 "net/http" 7 "reflect" 8 9 "github.com/mongodb/grip" 10 "github.com/pkg/errors" 11 ) 12 13 // getCSVFields takes in a struct and retrieves the struct tag values for csv. 14 // If there is a substruct, then it will recurse and flatten out those fields. 15 func getCSVFields(t reflect.Type) []string { 16 fields := []string{} 17 numberFields := t.NumField() 18 for i := 0; i < numberFields; i++ { 19 fieldType := t.Field(i).Type 20 if fieldType.Kind() == reflect.Struct && fieldType.NumField() > 0 { 21 fields = append(fields, getCSVFields(fieldType)...) 22 continue 23 } 24 stringVal := t.Field(i).Tag.Get("csv") 25 if stringVal != "" { 26 fields = append(fields, stringVal) 27 } 28 } 29 return fields 30 } 31 32 // getCSVValues takes in a struct and returns a string of values based on struct tags 33 func getCSVValues(data interface{}) []string { 34 values := []string{} 35 v := reflect.ValueOf(data) 36 t := reflect.TypeOf(data) 37 numberFields := v.NumField() 38 for i := 0; i < numberFields; i++ { 39 fieldType := v.Field(i).Type() 40 if fieldType.Kind() == reflect.Struct && fieldType.NumField() > 0 { 41 values = append(values, getCSVValues(v.Field(i).Interface())...) 42 continue 43 } 44 stringVal := t.Field(i).Tag.Get("csv") 45 if stringVal != "" { 46 values = append(values, fmt.Sprintf("%v", v.Field(i).Interface())) 47 } 48 } 49 return values 50 } 51 52 // convertDataToCSVRecord takes in an interface that is a slice or an array and converts the struct to csv for 53 // fields that have the the csv struct tag. 54 func convertDataToCSVRecord(data interface{}) ([][]string, error) { 55 switch reflect.TypeOf(data).Kind() { 56 case reflect.Slice, reflect.Array: 57 s := reflect.ValueOf(data) 58 if s.Len() == 0 { 59 return nil, errors.New("no data to write to CSV") 60 } 61 // create the fields by passing in the type of a host utilization bucket 62 records := [][]string{getCSVFields(reflect.TypeOf(s.Index(0).Interface()))} 63 for i := 0; i < s.Len(); i++ { 64 records = append(records, getCSVValues(s.Index(i).Interface())) 65 } 66 return records, nil 67 default: 68 return nil, errors.New("data is not an array") 69 } 70 } 71 72 // WriteToCSVResponse takes in an interface that is a slice or an array and converts the struct to csv for 73 // fields that have the the csv struct tag. 74 func WriteCSVResponse(w http.ResponseWriter, status int, data interface{}) { 75 // if the status is not okay then don't convert the data to csv. 76 if status != http.StatusOK { 77 bytes := []byte(fmt.Sprintf("%v", data)) 78 w.WriteHeader(500) 79 _, err := w.Write(bytes) 80 grip.Debug(errors.Wrap(err, "problem writing cvs data to output")) 81 return 82 } 83 84 w.Header().Add("Content-Type", "application/csv") 85 w.Header().Add("Connection", "close") 86 87 csvRecords, err := convertDataToCSVRecord(data) 88 89 if err != nil { 90 stringBytes := []byte(err.Error()) 91 w.WriteHeader(500) 92 _, err = w.Write(stringBytes) 93 grip.Warning(errors.WithStack(err)) 94 return 95 } 96 w.WriteHeader(http.StatusOK) 97 csvWriter := csv.NewWriter(w) 98 grip.Warning(errors.WithStack(csvWriter.WriteAll(csvRecords))) 99 return 100 }