-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathresponse.go
More file actions
144 lines (126 loc) · 3.59 KB
/
Copy pathresponse.go
File metadata and controls
144 lines (126 loc) · 3.59 KB
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
package httpassert
import (
"bytes"
"compress/flate"
"compress/gzip"
"compress/zlib"
"encoding/json"
"fmt"
"io"
"maps"
"net/http"
"slices"
"strings"
"github.com/andybalholm/brotli"
"github.com/klauspost/compress/zstd"
)
// Response is the response assertions inspect. Response.Body has already been
// consumed and closed. After a successful Client.Do, BodyBytes contains the
// decoded payload when DecodeErr is nil. If Client.Do returns a body-read
// error, BodyBytes contains only the bytes read before that error and no
// decoding was attempted. The original headers, including Content-Encoding and
// Content-Length, remain unchanged.
type Response struct {
*http.Response
// BodyBytes is the complete decoded response payload after a successful
// Client.Do. On a body-read error it is the partial encoded payload received
// before the error; callers must check Client.Do's top-level error first.
BodyBytes []byte
// Encoding is the response's Content-Encoding value, with surrounding
// whitespace removed. An empty value means no encoding was declared.
Encoding string
// DecodeErr explains why BodyBytes could not be decoded. When non-nil,
// BodyBytes contains the encoded bytes exactly as received.
DecodeErr error
jsonBody any
jsonErr error
jsonParsed bool
}
func (r *Response) decodeJSON() (any, error) {
if r.jsonParsed {
return r.jsonBody, r.jsonErr
}
r.jsonParsed = true
body, err := bodyOf(r)
if err != nil {
r.jsonErr = err
return nil, r.jsonErr
}
if err := json.Unmarshal(body, &r.jsonBody); err != nil {
r.jsonErr = &EvaluationError{
Code: EvaluationJSON,
Kind: KindBody,
Cause: err,
}
return nil, r.jsonErr
}
return r.jsonBody, nil
}
var decoders = map[string]func([]byte) ([]byte, error){
"gzip": decodeGzip,
"deflate": decodeDeflate,
"br": decodeBrotli,
"zstd": decodeZstd,
}
func supportedCodings() string {
return strings.Join(slices.Sorted(maps.Keys(decoders)), ", ")
}
func (r *Response) decodeBody() {
r.Encoding = strings.TrimSpace(r.Header.Get("Content-Encoding"))
if len(r.BodyBytes) == 0 {
return
}
switch enc := strings.ToLower(r.Encoding); enc {
case "", "identity":
return
default:
decode, ok := decoders[enc]
if !ok {
r.DecodeErr = fmt.Errorf("no decoder for %q; %s are supported", r.Encoding, supportedCodings())
return
}
body, err := decode(r.BodyBytes)
if err != nil {
r.DecodeErr = err
return
}
r.BodyBytes = body
}
}
func decodeBrotli(body []byte) ([]byte, error) {
return io.ReadAll(brotli.NewReader(bytes.NewReader(body)))
}
func decodeZstd(body []byte) ([]byte, error) {
// A response body is consumed synchronously before assertions run. One
// decoder worker avoids starting a background decode pipeline and excess
// per-response workers.
reader, err := zstd.NewReader(bytes.NewReader(body), zstd.WithDecoderConcurrency(1))
if err != nil {
return nil, err
}
defer reader.Close()
return io.ReadAll(reader)
}
func decodeGzip(body []byte) ([]byte, error) {
reader, err := gzip.NewReader(bytes.NewReader(body))
if err != nil {
return nil, err
}
defer func() { _ = reader.Close() }()
return io.ReadAll(reader)
}
func decodeDeflate(body []byte) ([]byte, error) {
if reader, err := zlib.NewReader(bytes.NewReader(body)); err == nil {
defer func() { _ = reader.Close() }()
if out, err := io.ReadAll(reader); err == nil {
return out, nil
}
}
reader := flate.NewReader(bytes.NewReader(body))
defer func() { _ = reader.Close() }()
out, err := io.ReadAll(reader)
if err != nil {
return nil, fmt.Errorf("not valid zlib or raw DEFLATE: %w", err)
}
return out, nil
}