A community based topic aggregation platform built on atproto
at main 94 lines 2.8 kB view raw
1package actor 2 3import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "log" 8 "net/http" 9 10 "Coves/internal/core/posts" 11) 12 13// ErrorResponse represents an XRPC error response 14type ErrorResponse struct { 15 Error string `json:"error"` 16 Message string `json:"message"` 17} 18 19// writeError writes a JSON error response 20func writeError(w http.ResponseWriter, statusCode int, errorType, message string) { 21 w.Header().Set("Content-Type", "application/json") 22 w.WriteHeader(statusCode) 23 if err := json.NewEncoder(w).Encode(ErrorResponse{ 24 Error: errorType, 25 Message: message, 26 }); err != nil { 27 // Log encoding errors but can't send error response (headers already sent) 28 log.Printf("ERROR: Failed to encode error response: %v", err) 29 } 30} 31 32// handleServiceError maps service errors to HTTP responses 33func handleServiceError(w http.ResponseWriter, err error) { 34 // Check for handler-level errors first 35 var actorNotFound *actorNotFoundError 36 if errors.As(err, &actorNotFound) { 37 writeError(w, http.StatusNotFound, "ActorNotFound", "Actor not found") 38 return 39 } 40 41 // Check for service-level errors 42 switch { 43 case errors.Is(err, posts.ErrNotFound): 44 writeError(w, http.StatusNotFound, "ActorNotFound", "Actor not found") 45 46 case errors.Is(err, posts.ErrActorNotFound): 47 writeError(w, http.StatusNotFound, "ActorNotFound", "Actor not found") 48 49 case errors.Is(err, posts.ErrCommunityNotFound): 50 writeError(w, http.StatusNotFound, "CommunityNotFound", "Community not found") 51 52 case errors.Is(err, posts.ErrInvalidCursor): 53 writeError(w, http.StatusBadRequest, "InvalidCursor", "Invalid pagination cursor") 54 55 case posts.IsValidationError(err): 56 // Extract message from ValidationError for cleaner response 57 var valErr *posts.ValidationError 58 if errors.As(err, &valErr) { 59 writeError(w, http.StatusBadRequest, "InvalidRequest", valErr.Message) 60 } else { 61 writeError(w, http.StatusBadRequest, "InvalidRequest", err.Error()) 62 } 63 64 default: 65 // Internal server error - don't leak details 66 log.Printf("ERROR: Actor posts service error: %v", err) 67 writeError(w, http.StatusInternalServerError, "InternalServerError", "An internal error occurred") 68 } 69} 70 71// actorNotFoundError represents an actor not found error 72type actorNotFoundError struct { 73 actor string 74} 75 76func (e *actorNotFoundError) Error() string { 77 return fmt.Sprintf("actor not found: %s", e.actor) 78} 79 80// resolutionFailedError represents an infrastructure failure during resolution 81// (database down, DNS failures, TLS errors, etc.) 82// This is distinct from actorNotFoundError to avoid masking real problems as "not found" 83type resolutionFailedError struct { 84 actor string 85 cause error 86} 87 88func (e *resolutionFailedError) Error() string { 89 return fmt.Sprintf("failed to resolve actor %s: %v", e.actor, e.cause) 90} 91 92func (e *resolutionFailedError) Unwrap() error { 93 return e.cause 94}