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  }