This commit is contained in:
2026-05-23 17:17:56 -07:00
commit 448f2e33ef
135 changed files with 11817 additions and 0 deletions
+145
View File
@@ -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)
}