/
t3
/
iam-server
Обзор
Документация
Войти
/
t3
/
iam-server
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
master
internal/api/decode.go
95 строк
3 KB
Ivan Shibkikh
fix P1
20 июл 2026, 18:23
20 июл 2026, 18:23
01d4f4b
Код
Авторство
О чём код?
package api import ( "encoding/json" "errors" "fmt" "io" "net/http" "strings" ) // DecodeError wraps a decode failure with a kind so handlers can pick the // right HTTP status (400 vs 413) without parsing error strings. type DecodeError struct { Kind string // "validation", "too_large", "bad_json" Message string } func (e *DecodeError) Error() string { return e.Message } // Is enables errors.Is checks against DecodeError by kind. func (e *DecodeError) Is(target error) bool { t, ok := target.(*DecodeError) if !ok { return false } return t.Kind == "" || t.Kind == e.Kind } // decodeJSON reads the request body into dst with strict settings: // - unknown fields are rejected // - empty bodies are rejected // - trailing garbage is rejected // // It distinguishes a body that exceeded the upstream MaxBytesReader (413) from // malformed/invalid JSON (400) by returning a *DecodeError with Kind="too_large" // when the error wraps http.MaxBytesError. func decodeJSON(r *http.Request, dst any) error { dec := json.NewDecoder(r.Body) dec.DisallowUnknownFields() if err := dec.Decode(dst); err != nil { // Body exceeded the MaxBytesReader limit set by BodyLimitMiddleware. var maxErr *http.MaxBytesError if errors.As(err, &maxErr) { return &DecodeError{Kind: "too_large", Message: "request body exceeds the maximum allowed size"} } // An empty body (or EOF before the first token) is a special case. if err == io.EOF || strings.Contains(err.Error(), "EOF") { return &DecodeError{Kind: "validation", Message: "request body must not be empty"} } // Distinguish between syntax errors (invalid JSON) and type errors // (wrong field type) vs unknown fields. switch e := err.(type) { case *json.UnmarshalTypeError: return &DecodeError{Kind: "validation", Message: fmt.Sprintf("field %q has wrong type (expected %s)", e.Field, e.Type.String())} case *json.SyntaxError: return &DecodeError{Kind: "bad_json", Message: fmt.Sprintf("invalid JSON at byte %d: %s", e.Offset, e.Error())} default: if strings.Contains(err.Error(), "unknown field") { return &DecodeError{Kind: "validation", Message: err.Error()} } return &DecodeError{Kind: "bad_json", Message: fmt.Sprintf("cannot parse request body: %s", err.Error())} } } // Guard against trailing data (another JSON value after the first). if dec.More() { return &DecodeError{Kind: "validation", Message: "request body must contain a single JSON object"} } return nil } // writeDecodeError maps a decodeJSON error to the appropriate HTTP response. // If err is not a *DecodeError, it is treated as a generic bad_request. func writeDecodeError(w http.ResponseWriter, err error) { var de *DecodeError if errors.As(err, &de) { switch de.Kind { case "too_large": writeRequestEntityTooLarge(w, de.Message) return case "bad_json": writeBadRequest(w, de.Message) return default: writeValidationError(w, de.Message, nil) return } } writeBadRequest(w, err.Error()) }