115 lines
3.3 KiB
Go
115 lines
3.3 KiB
Go
// utils/json.go
|
|
package utils
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// DecodeJSON декодирует JSON из тела запроса с валидацией
|
|
func DecodeJSON(r *http.Request, v interface{}) error {
|
|
// Ограничиваем размер тела запроса (например, 1MB)
|
|
maxBytes := int64(1_048_576) // 1MB
|
|
r.Body = http.MaxBytesReader(nil, r.Body, maxBytes)
|
|
|
|
decoder := json.NewDecoder(r.Body)
|
|
decoder.DisallowUnknownFields() // Запрещаем неизвестные поля
|
|
|
|
err := decoder.Decode(v)
|
|
if err != nil {
|
|
var syntaxError *json.SyntaxError
|
|
var unmarshalTypeError *json.UnmarshalTypeError
|
|
var invalidUnmarshalError *json.InvalidUnmarshalError
|
|
|
|
switch {
|
|
case err == io.EOF:
|
|
return &APIError{
|
|
Code: http.StatusBadRequest,
|
|
Message: "Request body is empty",
|
|
}
|
|
case err.Error() == "http: request body too large":
|
|
return &APIError{
|
|
Code: http.StatusRequestEntityTooLarge,
|
|
Message: fmt.Sprintf("Request body must not be larger than %d bytes", maxBytes),
|
|
}
|
|
case strings.HasPrefix(err.Error(), "json: unknown field"):
|
|
fieldName := strings.TrimPrefix(err.Error(), "json: unknown field ")
|
|
return &APIError{
|
|
Code: http.StatusBadRequest,
|
|
Message: fmt.Sprintf("Unknown field in JSON: %s", fieldName),
|
|
}
|
|
case errors.As(err, &syntaxError):
|
|
return &APIError{
|
|
Code: http.StatusBadRequest,
|
|
Message: fmt.Sprintf("Malformed JSON at position %d", syntaxError.Offset),
|
|
}
|
|
case errors.As(err, &unmarshalTypeError):
|
|
return &APIError{
|
|
Code: http.StatusBadRequest,
|
|
Message: fmt.Sprintf("Invalid value for field '%s'. Expected type %s", unmarshalTypeError.Field, unmarshalTypeError.Type),
|
|
}
|
|
case errors.As(err, &invalidUnmarshalError):
|
|
return &APIError{
|
|
Code: http.StatusInternalServerError,
|
|
Message: "Internal server error",
|
|
}
|
|
default:
|
|
return &APIError{
|
|
Code: http.StatusBadRequest,
|
|
Message: "Invalid JSON",
|
|
}
|
|
}
|
|
}
|
|
|
|
// Проверяем, что нет лишних данных после JSON
|
|
if err = decoder.Decode(&struct{}{}); err != io.EOF {
|
|
return &APIError{
|
|
Code: http.StatusBadRequest,
|
|
Message: "Request body must contain only single JSON object",
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// WriteJSON записывает JSON ответ
|
|
func WriteJSON(w http.ResponseWriter, status int, data interface{}) error {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(status)
|
|
|
|
if data == nil {
|
|
return nil
|
|
}
|
|
|
|
encoder := json.NewEncoder(w)
|
|
encoder.SetEscapeHTML(true) // Экранируем HTML для безопасности
|
|
|
|
return encoder.Encode(data)
|
|
}
|
|
|
|
// WriteError записывает ошибку в формате JSON
|
|
func WriteError(w http.ResponseWriter, status int, message string) {
|
|
errorResponse := ErrorResponse{
|
|
Error: true,
|
|
Message: message,
|
|
Code: status,
|
|
}
|
|
|
|
WriteJSON(w, status, errorResponse)
|
|
}
|
|
|
|
// WriteValidationError записывает ошибки валидации
|
|
func WriteValidationError(w http.ResponseWriter, errors map[string]string) {
|
|
errorResponse := ValidationErrorResponse{
|
|
Error: true,
|
|
Message: "Validation failed",
|
|
Code: http.StatusBadRequest,
|
|
Errors: errors,
|
|
}
|
|
|
|
WriteJSON(w, http.StatusBadRequest, errorResponse)
|
|
} |