-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathjson.go
128 lines (110 loc) · 4.26 KB
/
json.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
package toolkit
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
)
type JSONResponse struct {
Error bool `json:"error"`
Message string `json:"message"`
Data interface{} `json:"data,omitempty"` // Do not include if empty with omitempty
}
// ReadJSON reads and decodes JSON data from an HTTP request body into the provided 'data' object.
// It ensures the JSON is properly formatted, validates its size, and handles various error scenarios.
func (t *Tools) ReadJSON(w http.ResponseWriter, r *http.Request, data interface{}) error {
// Check if the payload is of permitted size
maxBytes := 1024 * 1024 // 1 Mg
if t.MaxJSONSize != 0 {
maxBytes = t.MaxJSONSize
}
// Read request of the body
r.Body = http.MaxBytesReader(w, r.Body, int64(maxBytes))
// Decode the body
decodedBody := json.NewDecoder(r.Body)
// Check if we should process JSON with unknown fields
if !t.AllowUnknownFields {
decodedBody.DisallowUnknownFields()
}
// Decode data
err := decodedBody.Decode(data)
if err != nil {
var syntaxError *json.SyntaxError
var unmarshalTypeError *json.UnmarshalTypeError
var invalidUnmarshalError *json.InvalidUnmarshalError
switch {
case errors.As(err, &syntaxError):
// If there's a syntax error in the JSON, report the position of the error
return fmt.Errorf("body contains badly-formed JSON (at character %d)", syntaxError.Offset)
case errors.Is(err, io.ErrUnexpectedEOF):
// If the body is incomplete, return a malformed JSON error
return errors.New("body contains badly-formed JSON")
case errors.As(err, &unmarshalTypeError):
// If there's a type mismatch, report which field is problematic
if unmarshalTypeError.Field != "" {
return fmt.Errorf("body contains incorrect JSON type for field %q", unmarshalTypeError.Field)
}
// If there's no specific field, report the character offset where the type error occurred
return fmt.Errorf("body contains incorrect JSON type (at character %d)", unmarshalTypeError.Offset)
case errors.Is(err, io.EOF):
// If the body is empty, return an error indicating that the body must not be empty
return errors.New("body must not be empty")
case strings.HasPrefix(err.Error(), "json: unknown field"):
// If there is an unknown field in the JSON, return an error indicating which field is unknown
fieldName := strings.TrimPrefix(err.Error(), "json: unknown field")
return fmt.Errorf("body contains unknown key %s", fieldName)
case err.Error() == "http: request body too large":
// If the body exceeds the allowed size, return an error with the size limit
return fmt.Errorf("body must not be larger %d bytes", maxBytes)
case errors.As(err, &invalidUnmarshalError):
// If unmarshalling fails for any reason, return the error message
return fmt.Errorf("error unmarshalling JSON %s", err.Error())
default:
return err
}
}
// Ensure that only one JSON file is received
// Attempt to decode an empty struct after the initial JSON decoding
// If more JSON data is found, return an error indicating multiple JSON objects
err = decodedBody.Decode(&struct{}{})
if err != io.EOF {
return errors.New("body must contain only one JSON value")
}
return nil
}
// WriteJSON() writes a JSON response with provided status, data and an optional custom header
func (t *Tools) WriteJSON(w http.ResponseWriter, status int, data interface{}, headers ...http.Header) error {
// Attempt to marshal the data into a pretty-printed JSON format
jsonData, err := json.MarshalIndent(data, "", " ")
if err != nil {
return err
}
// Check if a custom header should be set
if len(headers) > 0 {
for indx, hdr := range headers[0] {
w.Header()[indx] = hdr
}
}
// Set Content-Type and provided status
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_, err = w.Write(jsonData)
if err != nil {
return err
}
return nil
}
// ErrorJSON() takes in an error and an optional status code, and sends a JSON error message
func (t *Tools) ErrorJSON(w http.ResponseWriter, err error, status ...int) error {
// Set a default status
statusCode := http.StatusBadRequest
if len(status) > 0 {
statusCode = status[0]
}
var JSONPayload JSONResponse
JSONPayload.Error = true
JSONPayload.Message = err.Error()
return t.WriteJSON(w, statusCode, JSONPayload)
}