A community based topic aggregation platform built on atproto
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}