// Package httpapi implementiert die öffentliche REST-Schnittstelle laut SYNCOVA_API.md. package httpapi import ( "fmt" "net/http" ) // ErrorCode ist ein stabiler, maschinenlesbarer Fehlercode der API. // // Codes sind Teil des API-Vertrags: Clients dürfen auf sie reagieren, während // die Fehlermeldung sich ändern darf. Sie beschreiben die Ursache verständlich, // ohne interne Details preiszugeben (SYNCOVA_API.md §26). type ErrorCode string const ( // ErrorCodeBadRequest meldet einen syntaktisch fehlerhaften Request. ErrorCodeBadRequest ErrorCode = "BAD_REQUEST" // ErrorCodeValidationFailed meldet fachlich ungültige Eingabewerte. ErrorCodeValidationFailed ErrorCode = "VALIDATION_FAILED" // ErrorCodeUnauthenticated meldet einen fehlenden oder ungültigen Nachweis. ErrorCodeUnauthenticated ErrorCode = "UNAUTHENTICATED" // ErrorCodePermissionDenied meldet eine fehlende Berechtigung. ErrorCodePermissionDenied ErrorCode = "PERMISSION_DENIED" // ErrorCodeNotFound meldet eine nicht vorhandene Ressource. ErrorCodeNotFound ErrorCode = "NOT_FOUND" // ErrorCodeMethodNotAllowed meldet eine für diese Route unzulässige HTTP-Methode. ErrorCodeMethodNotAllowed ErrorCode = "METHOD_NOT_ALLOWED" // ErrorCodeConflict meldet einen Konflikt mit dem aktuellen Zustand. ErrorCodeConflict ErrorCode = "CONFLICT" // ErrorCodePayloadTooLarge meldet eine Überschreitung des Body-Limits. ErrorCodePayloadTooLarge ErrorCode = "PAYLOAD_TOO_LARGE" // ErrorCodeRateLimited meldet zu viele Anfragen. ErrorCodeRateLimited ErrorCode = "RATE_LIMITED" // ErrorCodeInternal meldet einen unerwarteten Serverfehler. ErrorCodeInternal ErrorCode = "INTERNAL_ERROR" // ErrorCodeServiceUnavailable meldet einen vorübergehend nicht verfügbaren Dienst. ErrorCodeServiceUnavailable ErrorCode = "SERVICE_UNAVAILABLE" // ErrorCodeNotImplemented meldet eine bewusst noch nicht implementierte Funktion. // // PROMPT.md §138 verlangt diese ehrliche Antwort statt einer vorgetäuschten // Funktion oder erfundener Daten. ErrorCodeNotImplemented ErrorCode = "NOT_IMPLEMENTED" ) // APIError ist ein Fehler, der sich unmittelbar in eine API-Antwort übersetzen lässt. // // Er trennt die für den Aufrufer bestimmte Darstellung von der internen Ursache: // Letztere wird geloggt, aber niemals ausgeliefert (SYNCOVA_API.md §26). type APIError struct { // StatusCode ist der auszuliefernde HTTP-Statuscode. StatusCode int // Code ist der stabile maschinenlesbare Fehlercode. Code ErrorCode // Message ist die für Menschen bestimmte Erklärung ohne interne Details. Message string // Details trägt optionale, unbedenkliche Zusatzinformationen (z. B. Feldnamen). Details map[string]any // cause ist die interne Ursache. Sie wird ausschließlich geloggt. cause error } // Error erfüllt das error-Interface. func (apiError *APIError) Error() string { if apiError.cause != nil { return fmt.Sprintf("%s: %s: %v", apiError.Code, apiError.Message, apiError.cause) } return fmt.Sprintf("%s: %s", apiError.Code, apiError.Message) } // Unwrap gibt die interne Ursache für errors.Is/errors.As frei. func (apiError *APIError) Unwrap() error { return apiError.cause } // WithCause hinterlegt die interne Ursache eines Fehlers. // Die Ursache erscheint im Log, niemals in der Antwort an den Aufrufer. func (apiError *APIError) WithCause(causeError error) *APIError { apiError.cause = causeError return apiError } // WithDetails ergänzt unbedenkliche Zusatzinformationen für den Aufrufer. func (apiError *APIError) WithDetails(errorDetails map[string]any) *APIError { apiError.Details = errorDetails return apiError } // NewBadRequestError meldet einen syntaktisch fehlerhaften Request. func NewBadRequestError(errorMessage string) *APIError { return &APIError{StatusCode: http.StatusBadRequest, Code: ErrorCodeBadRequest, Message: errorMessage} } // NewValidationError meldet fachlich ungültige Eingabewerte. func NewValidationError(errorMessage string) *APIError { return &APIError{StatusCode: http.StatusUnprocessableEntity, Code: ErrorCodeValidationFailed, Message: errorMessage} } // NewNotFoundError meldet eine nicht vorhandene Ressource. func NewNotFoundError(errorMessage string) *APIError { return &APIError{StatusCode: http.StatusNotFound, Code: ErrorCodeNotFound, Message: errorMessage} } // NewInternalError meldet einen unerwarteten Serverfehler. // // Die Nachricht ist bewusst generisch: interne Details könnten Angreifern die // Struktur des Systems verraten. func NewInternalError(causeError error) *APIError { return &APIError{ StatusCode: http.StatusInternalServerError, Code: ErrorCodeInternal, Message: "Bei der Verarbeitung der Anfrage ist ein interner Fehler aufgetreten.", cause: causeError, } } // NewServiceUnavailableError meldet einen vorübergehend nicht verfügbaren Dienst. func NewServiceUnavailableError(errorMessage string) *APIError { return &APIError{StatusCode: http.StatusServiceUnavailable, Code: ErrorCodeServiceUnavailable, Message: errorMessage} } // NewNotImplementedError meldet eine noch nicht implementierte Funktion. // // Sie ist der vorgeschriebene Weg für geplante, aber unfertige Endpunkte // (PROMPT.md §138). func NewNotImplementedError(featureName string) *APIError { return &APIError{ StatusCode: http.StatusNotImplemented, Code: ErrorCodeNotImplemented, Message: fmt.Sprintf("%s ist in dieser Version noch nicht implementiert.", featureName), } }