Files
browser/platform/api/json.go
T
2026-07-11 14:56:10 +08:00

82 lines
2.6 KiB
Go

package api
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"browser.local/platform/dto"
"browser.local/platform/repo"
"browser.local/platform/service"
"browser.local/platform/validator"
)
const (
errorCodeForbidden = "forbidden"
errorCodeBadRequest = "bad_request"
errorCodeDuplicate = "duplicate_resource"
errorCodeInternal = "internal_error"
errorCodeMethodNotAllowed = "method_not_allowed"
errorCodeNotFound = "not_found"
errorCodeUnauthorized = "unauthorized"
errorCodeValidation = "validation_failed"
)
func decodeJSON[T any](r *http.Request) (T, error) {
var value T
decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields()
if err := decoder.Decode(&value); err != nil {
return value, fmt.Errorf("decode json: %w", err)
}
var extra struct{}
if err := decoder.Decode(&extra); err != io.EOF {
return value, errors.New("decode json: multiple JSON values are not allowed")
}
return value, nil
}
func writeJSON(w http.ResponseWriter, status int, value any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(value)
}
func writeAPIError(w http.ResponseWriter, status int, code string, message string, details []string) {
writeJSON(w, status, dto.ErrorResponse{
Code: code,
Message: message,
Details: details,
})
}
func writeMethodNotAllowed(w http.ResponseWriter, allow string) {
w.Header().Set("Allow", allow)
writeAPIError(w, http.StatusMethodNotAllowed, errorCodeMethodNotAllowed, "method not allowed", nil)
}
func writeServiceError(w http.ResponseWriter, err error) {
var validationErr validator.ValidationError
switch {
case errors.As(err, &validationErr):
writeAPIError(w, http.StatusBadRequest, errorCodeValidation, "validation failed", validationErr.Violations)
case errors.Is(err, service.ErrUnauthorized):
writeAPIError(w, http.StatusUnauthorized, errorCodeUnauthorized, "authentication required", nil)
case errors.Is(err, service.ErrForbidden):
writeAPIError(w, http.StatusForbidden, errorCodeForbidden, "account is not allowed to access this resource", nil)
case errors.Is(err, repo.ErrDuplicate):
writeAPIError(w, http.StatusConflict, errorCodeDuplicate, "resource already exists", nil)
case errors.Is(err, repo.ErrNotFound):
writeAPIError(w, http.StatusNotFound, errorCodeNotFound, "resource not found", nil)
default:
writeAPIError(w, http.StatusInternalServerError, errorCodeInternal, "internal server error", nil)
}
}
func writeDecodeError(w http.ResponseWriter, err error) {
writeAPIError(w, http.StatusBadRequest, errorCodeBadRequest, "invalid JSON request body", []string{err.Error()})
}