-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathcsv.go
147 lines (123 loc) · 2.84 KB
/
csv.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package api2
import (
"bytes"
"context"
"encoding/csv"
"errors"
"io"
"net/http"
)
type CsvResponse struct {
HttpCode int
HttpHeaders http.Header
CsvHeader []string
// The channel is used to pass rows of CSV file.
//
// In ResponseEncoder (server side) the field should be set by the handler
// and results written from a goroutine. The transport drains the channel.
//
// In ResponseDecoder (client side) the channel should be passed in response
// object. The transport downloads the file and writes rows to the channel
// and closes it before returning.
Rows chan []string
}
func csvEncodeResponse(ctx context.Context, w http.ResponseWriter, res0 interface{}) error {
res := res0.(*CsvResponse)
defer func() {
// Drain the channel.
for range res.Rows {
}
}()
w.Header().Set("Content-Type", "text/csv")
// Copy HTTP headers.
for k, v := range res.HttpHeaders {
w.Header()[k] = v
}
w.WriteHeader(res.HttpCode)
httpFlusher, hasFlusher := w.(http.Flusher)
csvWriter := csv.NewWriter(w)
csvWriter.UseCRLF = true
if err := csvWriter.Write(res.CsvHeader); err != nil {
return err
}
csvWriter.Flush()
if hasFlusher {
httpFlusher.Flush()
}
if err := csvWriter.Error(); err != nil {
return err
}
for record := range res.Rows {
if err := ctx.Err(); err != nil {
return err
}
if err := csvWriter.Write(record); err != nil {
return err
}
csvWriter.Flush()
if hasFlusher {
httpFlusher.Flush()
}
if err := csvWriter.Error(); err != nil {
return err
}
}
return nil
}
func csvDecodeResponse(ctx context.Context, r *http.Response, res0 interface{}) error {
res := res0.(*CsvResponse)
if res.Rows == nil {
panic("provide a channel in res.Rows")
}
defer close(res.Rows)
res.HttpCode = r.StatusCode
// Copy HTTP headers.
res.HttpHeaders = make(http.Header)
for k, v := range r.Header {
res.HttpHeaders[k] = v
}
csvReader := csv.NewReader(r.Body)
csvHeader, err := csvReader.Read()
if err != nil {
return err
}
res.CsvHeader = csvHeader
for {
if err := ctx.Err(); err != nil {
return err
}
record, err := csvReader.Read()
if err != nil {
if err == io.EOF {
return nil
}
return err
}
res.Rows <- record
}
}
func csvEncodeError(ctx context.Context, w http.ResponseWriter, err error) error {
code := http.StatusBadRequest
var httpErr HttpError
if errors.As(err, &httpErr) {
code = httpErr.HttpCode()
}
http.Error(w, err.Error(), code)
return nil
}
func csvDecodeError(ctx context.Context, res *http.Response) error {
message, err := io.ReadAll(res.Body)
if err != nil {
return err
}
return httpError{
Code: res.StatusCode,
Message: string(bytes.TrimSpace(message)),
}
}
var CsvTransport = &JsonTransport{
ResponseEncoder: csvEncodeResponse,
ResponseDecoder: csvDecodeResponse,
ErrorEncoder: csvEncodeError,
ErrorDecoder: csvDecodeError,
}