rc-1
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type APIError struct {
|
||||
Status int `json:"-"`
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
var (
|
||||
ERROR_GENERIC_SERVER = APIError{Status: 500, Code: 10000, Message: "Server Error"}
|
||||
ERROR_GENERIC_NOT_FOUND = APIError{Status: 404, Code: 10001, Message: "Endpoint Not Found"}
|
||||
ERROR_GENERIC_RATELIMIT = APIError{Status: 429, Code: 10002, Message: "Too Many Requests"}
|
||||
ERROR_GENERIC_UNAUTHORIZED = APIError{Status: 401, Code: 10003, Message: "Unauthorized"}
|
||||
ERROR_GENERIC_FORBIDDEN = APIError{Status: 403, Code: 10004, Message: "Forbidden"}
|
||||
ERROR_GENERIC_METHOD_NOT_ALLOWED = APIError{Status: 405, Code: 10005, Message: "Method Not Allowed"}
|
||||
ERROR_SERVER_RESOURCES_EXHAUSTED = APIError{Status: 507, Code: 11006, Message: "Resources Exhausted"}
|
||||
ERROR_BODY_EMPTY = APIError{Status: 411, Code: 12000, Message: "Request Body is Empty"}
|
||||
ERROR_BODY_TOO_LARGE = APIError{Status: 413, Code: 12001, Message: "Request Body is Too Large"}
|
||||
ERROR_BODY_INVALID_CONTENT_TYPE = APIError{Status: 400, Code: 12002, Message: "Invalid 'Content-Type' Header"}
|
||||
ERROR_BODY_INVALID_CHALLENGE = APIError{Status: 400, Code: 12003, Message: "Invalid 'X-Challenge-*' Header"}
|
||||
ERROR_BODY_INVALID_FIELD = APIError{Status: 400, Code: 12004, Message: "Invalid Body Field"}
|
||||
ERROR_BODY_INVALID_DATA = APIError{Status: 422, Code: 12005, Message: "Invalid Body"}
|
||||
ERROR_UNKNOWN_ENDPOINT = APIError{Status: 404, Code: 13000, Message: "Unknown Endpoint"}
|
||||
ERROR_UNKNOWN_FUNCTION = APIError{Status: 404, Code: 13001, Message: "Unknown Function"}
|
||||
ERROR_UNKNOWN_ANIMATION = APIError{Status: 404, Code: 13002, Message: "Unknown Animation"}
|
||||
ERROR_UNKNOWN_TASK = APIError{Status: 404, Code: 13003, Message: "Unknown Task"}
|
||||
ERROR_UNKNOWN_CHALLENGE = APIError{Status: 404, Code: 13004, Message: "Unknown Challenge"}
|
||||
ERROR_CHALLENGE_INVALID = APIError{Status: 400, Code: 14000, Message: "Invalid Challenge Result"}
|
||||
ERROR_CHALLENGE_TOO_EASY = APIError{Status: 400, Code: 14001, Message: "Challenge Difficulty Too Low"}
|
||||
ERROR_CHALLENGE_EXPIRED = APIError{Status: 400, Code: 14002, Message: "Challenge Expired"}
|
||||
ERROR_MEDIA_INVALID = APIError{Status: 400, Code: 15000, Message: "Media Invalid"}
|
||||
ERROR_MEDIA_INAPPROPRIATE = APIError{Status: 400, Code: 15001, Message: "Media Inappropriate"}
|
||||
)
|
||||
|
||||
// Reject request due to a Client Mistake
|
||||
func SendClientError(w http.ResponseWriter, r *http.Request, err APIError) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(err.Status)
|
||||
fmt.Fprintf(w, `{"code":%d,"message":%q}`, err.Code, err.Message)
|
||||
}
|
||||
|
||||
// Reject request due to a Server Error
|
||||
// Additionally collects debug information and logs it to the console
|
||||
func SendServerError(w http.ResponseWriter, r *http.Request, err error) {
|
||||
|
||||
debugStack := strings.Split(string(debug.Stack()), "\n")
|
||||
for i, item := range debugStack {
|
||||
debugStack[i] = strings.ReplaceAll(item, "\t", " ")
|
||||
}
|
||||
if len(debugStack) > 5 {
|
||||
debugStack = debugStack[5:] // skip header
|
||||
}
|
||||
|
||||
reqHeader := make(map[string]string, len(r.Header))
|
||||
for key, header := range r.Header {
|
||||
reqHeader[key] = strings.Join(header, ", ")
|
||||
}
|
||||
|
||||
LoggerHTTP.Data(ERROR, err.Error(), map[string]any{
|
||||
"request": map[string]any{
|
||||
"method": r.Method,
|
||||
"url": r.URL.String(),
|
||||
"headers": reqHeader,
|
||||
},
|
||||
"error": map[string]any{
|
||||
"raw": err,
|
||||
"message": err.Error(),
|
||||
"stack": debugStack,
|
||||
},
|
||||
})
|
||||
|
||||
if w != nil {
|
||||
SendClientError(w, r, ERROR_GENERIC_SERVER)
|
||||
}
|
||||
}
|
||||
|
||||
// Respond to the request with a JSON object
|
||||
func SendJSON(w http.ResponseWriter, r *http.Request, statusCode int, responseObject any) (int, error) {
|
||||
|
||||
// Check Compression
|
||||
var g io.Writer
|
||||
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
z := gzip.NewWriter(w)
|
||||
defer z.Close()
|
||||
g = z
|
||||
} else {
|
||||
g = w
|
||||
}
|
||||
|
||||
// Stream Object
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(statusCode)
|
||||
if b, ok := responseObject.([]byte); ok {
|
||||
return g.Write(b)
|
||||
} else {
|
||||
j := json.NewEncoder(g)
|
||||
return 0, j.Encode(responseObject)
|
||||
}
|
||||
}
|
||||
|
||||
// Encode Object as JSON and gzipped version
|
||||
func PrepareStaticJSON(responseObject any) ([]byte, []byte, error) {
|
||||
|
||||
// Encode Object
|
||||
buf, err := json.Marshal(responseObject)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Compress Object
|
||||
cmp := bytes.Buffer{}
|
||||
zip := gzip.NewWriter(&cmp)
|
||||
|
||||
if _, err := zip.Write(buf); err != nil {
|
||||
zip.Close()
|
||||
return nil, nil, err
|
||||
}
|
||||
if err := zip.Close(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return buf, cmp.Bytes(), nil
|
||||
}
|
||||
|
||||
// Respond to the request with a Static JSON Object
|
||||
func SendStaticJSON(w http.ResponseWriter, r *http.Request, statusCode int, content []byte, gzipped []byte) (int, error) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if gzipped != nil && strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
|
||||
w.Header().Set("Content-Encoding", "gzip")
|
||||
return w.Write(gzipped)
|
||||
}
|
||||
return w.Write(content)
|
||||
}
|
||||
Reference in New Issue
Block a user