-
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathrequest_decode.go
More file actions
115 lines (97 loc) · 2.55 KB
/
request_decode.go
File metadata and controls
115 lines (97 loc) · 2.55 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
package httpsuite
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
)
// BodyDecodeErrorKind identifies the decode failure category.
type BodyDecodeErrorKind string
const (
BodyDecodeErrorInvalidJSON BodyDecodeErrorKind = "invalid_json"
BodyDecodeErrorBodyTooLarge BodyDecodeErrorKind = "body_too_large"
BodyDecodeErrorMultipleDocuments BodyDecodeErrorKind = "multiple_documents"
)
// BodyDecodeError represents a request body parsing error.
type BodyDecodeError struct {
Kind BodyDecodeErrorKind
Err error
Limit int64
}
func (e *BodyDecodeError) Error() string {
switch e.Kind {
case BodyDecodeErrorBodyTooLarge:
return fmt.Sprintf("request body exceeds the limit of %d bytes", e.Limit)
case BodyDecodeErrorMultipleDocuments:
return "request body must contain a single JSON document"
default:
if e.Err != nil {
return e.Err.Error()
}
return "invalid request body"
}
}
func (e *BodyDecodeError) Unwrap() error {
return e.Err
}
// DecodeRequestBody decodes a JSON request body into T without writing HTTP responses.
func DecodeRequestBody[T any](r *http.Request, maxBodyBytes int64) (T, error) {
var request T
if r == nil {
return request, errNilHTTPRequest
}
if r.Body == nil {
return request, errNilRequestBody
}
if r.Body == http.NoBody {
return request, nil
}
limit := maxBodyBytes
if limit <= 0 {
limit = defaultMaxBodyBytes
}
body := http.MaxBytesReader(nilResponseWriter{}, r.Body, limit)
decoder := json.NewDecoder(body)
if err := decoder.Decode(&request); err != nil {
var maxBytesErr *http.MaxBytesError
if errors.As(err, &maxBytesErr) {
return request, &BodyDecodeError{
Kind: BodyDecodeErrorBodyTooLarge,
Err: err,
Limit: maxBytesErr.Limit,
}
}
return request, &BodyDecodeError{
Kind: BodyDecodeErrorInvalidJSON,
Err: err,
}
}
var trailing json.RawMessage
if err := decoder.Decode(&trailing); err != nil {
if errors.Is(err, io.EOF) {
return request, nil
}
var maxBytesErr *http.MaxBytesError
if errors.As(err, &maxBytesErr) {
return request, &BodyDecodeError{
Kind: BodyDecodeErrorBodyTooLarge,
Err: err,
Limit: maxBytesErr.Limit,
}
}
return request, &BodyDecodeError{
Kind: BodyDecodeErrorMultipleDocuments,
Err: err,
}
}
return request, &BodyDecodeError{Kind: BodyDecodeErrorMultipleDocuments}
}
type nilResponseWriter struct{}
func (nilResponseWriter) Header() http.Header {
return make(http.Header)
}
func (nilResponseWriter) Write([]byte) (int, error) {
return 0, nil
}
func (nilResponseWriter) WriteHeader(int) {}