# Error Handling Flow Diagram ## Error Handling Architecture ``` ┌──────────────────────────────────────────────────────────────┐ │ Error Handling Architecture │ └──────────────────────────────────────────────────────────────┘ Error Types: ├── Domain Errors Application-level business logic errors ├── Validation Errors Input validation failures ├── System Errors Infrastructure/system failures └── Panic Recovery Runtime panic handling ``` ## Domain Error Structure ``` ┌──────────────────────────────────────────────────────────────┐ │ DomainError (internal/handlers/errors.go) │ ├──────────────────────────────────────────────────────────────┤ │ type DomainError struct { │ │ Code ErrorCode // Enum error code │ │ Message string // Human-readable message │ │ Err error // Underlying error (if any) │ │ StatusCode int // HTTP status code │ │ Field string // Field that caused error │ │ } │ │ │ │ func (e *DomainError) Error() string │ │ func (e *DomainError) Unwrap() error │ │ func (e *DomainError) WithField(field string) *DomainError │ └──────────────────────────────────────────────────────────────┘ ┌──────────────────────────────────────────────────────────────┐ │ Error Codes │ ├──────────────────────────────────────────────────────────────┤ │ type ErrorCode string │ │ │ │ const ( │ │ // Input Validation (400) │ │ ErrCodeInvalidLanguage = "INVALID_LANGUAGE" │ │ ErrCodeInvalidLength = "INVALID_LENGTH" │ │ ErrCodeInvalidIcons = "INVALID_ICONS" │ │ ErrCodeInvalidTheme = "INVALID_THEME" │ │ ErrCodeInvalidVersion = "INVALID_VERSION" │ │ ErrCodeValidationFailed = "VALIDATION_FAILED" │ │ │ │ // Resource Errors (404, 500) │ │ ErrCodeDataNotFound = "DATA_NOT_FOUND" │ │ ErrCodeTemplateNotFound = "TEMPLATE_NOT_FOUND" │ │ ErrCodeTemplateError = "TEMPLATE_ERROR" │ │ │ │ // Processing Errors (500) │ │ ErrCodePDFGeneration = "PDF_GENERATION" │ │ ErrCodeInternalError = "INTERNAL_ERROR" │ │ │ │ // Rate Limiting (429) │ │ ErrCodeRateLimitExceeded = "RATE_LIMIT_EXCEEDED" │ │ │ │ // Security (403) │ │ ErrCodeOriginMismatch = "ORIGIN_MISMATCH" │ │ ) │ └──────────────────────────────────────────────────────────────┘ ``` ## Error Flow Patterns ### Pattern 1: Validation Error ``` ┌────────────────────────────────────────────────────────────┐ │ Validation Error Flow │ └────────────────────────────────────────────────────────────┘ Request: GET /?lang=xx │ ▼ ┌─────────────────────────┐ │ Handler.Home() │ │ (cv_pages.go) │ └─────────────────────────┘ │ ├─→ lang := r.URL.Query().Get("lang") │ // lang = "xx" │ ├─→ err := validateLanguage(lang) │ // "xx" not in ["en", "es"] │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ validateLanguage(lang) │ │ (cv_helpers.go) │ │ │ │ if lang != "en" && lang != "es" { │ │ return InvalidLanguageError(lang) │ │ } │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ InvalidLanguageError(lang) │ │ (errors.go) │ │ │ │ return NewDomainError( │ │ ErrCodeInvalidLanguage, │ │ fmt.Sprintf("Unsupported language: %s", lang), │ │ http.StatusBadRequest, │ │ ).WithField("lang") │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Handler receives error │ │ │ │ if err != nil { │ │ h.HandleError(w, r, err) │ │ return │ │ } │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ HandleError(w, r, err) │ │ (errors.go) │ │ │ │ 1. Cast to DomainError │ │ domErr, ok := err.(*DomainError) │ │ │ │ 2. Log error │ │ log.Printf("[ERROR] %s: %s", domErr.Code, domErr.Message) │ │ │ │ 3. Build response │ │ response := NewErrorResponse( │ │ string(domErr.Code), │ │ domErr.Message, │ │ ) │ │ response.Error.Field = domErr.Field │ │ │ │ 4. Send JSON error │ │ w.Header().Set("Content-Type", "application/json") │ │ w.WriteHeader(domErr.StatusCode) │ │ json.NewEncoder(w).Encode(response) │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Client Response │ │ │ │ HTTP/1.1 400 Bad Request │ │ Content-Type: application/json │ │ │ │ { │ │ "success": false, │ │ "error": { │ │ "code": "INVALID_LANGUAGE", │ │ "message": "Unsupported language: xx (use 'en' or 'es')", │ │ "field": "lang" │ │ } │ │ } │ └─────────────────────────────────────────────────────────────┘ ``` ### Pattern 2: Data Loading Error ``` ┌────────────────────────────────────────────────────────────┐ │ Data Loading Error Flow │ └────────────────────────────────────────────────────────────┘ Handler calls LoadCV("es") │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ cvmodel.LoadCV(lang) │ │ (internal/models/cv/cv.go) │ │ │ │ 1. Build file path │ │ filePath := fmt.Sprintf("data/cv-%s.json", lang) │ │ │ │ 2. Read file │ │ data, err := os.ReadFile(filePath) │ │ if err != nil { │ │ return nil, fmt.Errorf("failed to read CV: %w", err) │ │ } │ │ │ │ 3. Parse JSON │ │ var cv CV │ │ err = json.Unmarshal(data, &cv) │ │ if err != nil { │ │ return nil, fmt.Errorf("failed to parse CV: %w", err) │ │ } │ │ │ │ 4. Validate │ │ if err := cv.Validate(); err != nil { │ │ return nil, fmt.Errorf("invalid CV data: %w", err) │ │ } │ │ │ │ 5. Return │ │ return &cv, nil │ └─────────────────────────────────────────────────────────────┘ │ │ Error Case: File not found ▼ ┌─────────────────────────────────────────────────────────────┐ │ Handler receives error │ │ │ │ cv, err := cvmodel.LoadCV(lang) │ │ if err != nil { │ │ // Wrap in DomainError │ │ domErr := DataNotFoundError("CV", lang) │ │ domErr.Err = err │ │ h.HandleError(w, r, domErr) │ │ return │ │ } │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Client Response │ │ │ │ HTTP/1.1 500 Internal Server Error │ │ Content-Type: application/json │ │ │ │ { │ │ "success": false, │ │ "error": { │ │ "code": "DATA_NOT_FOUND", │ │ "message": "CV data not found for language: es" │ │ } │ │ } │ └─────────────────────────────────────────────────────────────┘ ``` ### Pattern 3: PDF Generation Error ``` ┌────────────────────────────────────────────────────────────┐ │ PDF Generation Error Flow │ └────────────────────────────────────────────────────────────┘ Handler calls GeneratePDF() │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ pdf.GeneratePDF(htmlContent, options) │ │ (internal/pdf/generator.go) │ │ │ │ 1. Create context │ │ ctx, cancel := chromedp.NewContext(...) │ │ defer cancel() │ │ │ │ 2. Launch Chrome │ │ if err := chromedp.Run(ctx, ...); err != nil { │ │ return nil, fmt.Errorf("chrome launch: %w", err) │ │ } │ │ │ │ 3. Navigate and render │ │ err := chromedp.Run(ctx, │ │ chromedp.Navigate(dataURL), │ │ chromedp.WaitReady("body"), │ │ chromedp.PrintToPDF(&pdfBytes), │ │ ) │ │ if err != nil { │ │ return nil, fmt.Errorf("PDF generation: %w", err) │ │ } │ │ │ │ 4. Return PDF bytes │ │ return pdfBytes, nil │ └─────────────────────────────────────────────────────────────┘ │ │ Error Case: Chrome failed ▼ ┌─────────────────────────────────────────────────────────────┐ │ Handler.ExportPDF receives error │ │ (cv_pdf.go) │ │ │ │ pdfBytes, err := pdf.GeneratePDF(htmlContent, opts) │ │ if err != nil { │ │ domErr := PDFGenerationError(err) │ │ h.HandleError(w, r, domErr) │ │ return │ │ } │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Client Response │ │ │ │ HTTP/1.1 500 Internal Server Error │ │ Content-Type: application/json │ │ │ │ { │ │ "success": false, │ │ "error": { │ │ "code": "PDF_GENERATION", │ │ "message": "Failed to generate PDF. Please try again." │ │ } │ │ } │ └─────────────────────────────────────────────────────────────┘ ``` ### Pattern 4: Panic Recovery ``` ┌────────────────────────────────────────────────────────────┐ │ Panic Recovery Flow │ └────────────────────────────────────────────────────────────┘ Request enters system │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ Recovery Middleware │ │ (internal/middleware/recovery.go) │ │ │ │ func Recovery(next http.Handler) http.Handler { │ │ return http.HandlerFunc(func(w, r) { │ │ defer func() { │ │ if err := recover(); err != nil { │ │ // Capture panic │ │ stack := debug.Stack() │ │ │ │ // Log with stack trace │ │ log.Printf("PANIC: %v\n%s", err, stack) │ │ │ │ // Send error response │ │ http.Error(w, │ │ "Internal Server Error", │ │ http.StatusInternalServerError) │ │ } │ │ }() │ │ │ │ // Continue to next handler │ │ next.ServeHTTP(w, r) │ │ }) │ │ } │ └─────────────────────────────────────────────────────────────┘ │ │ Normal flow: no panic ├──────────────────────────────────┐ ▼ │ Panic occurs Handler executes ▼ │ ┌─────────────────────────────────┐ │ │ panic("something went wrong") │ │ └─────────────────────────────────┘ │ │ │ ▼ │ ┌─────────────────────────────────┐ │ │ defer recover() catches it │ │ │ ├─ Get stack trace │ │ │ ├─ Log error + stack │ │ │ └─ Send 500 response │ │ └─────────────────────────────────┘ ▼ │ Response sent ▼ ┌─────────────────────────────────┐ │ Client receives 500 │ └─────────────────────────────────┘ ``` ## Error Response Formats ``` ┌────────────────────────────────────────────────────────────┐ │ Error Response Formats │ └────────────────────────────────────────────────────────────┘ Standard API Error (JSON): { "success": false, "error": { "code": "INVALID_LANGUAGE", "message": "Unsupported language: xx (use 'en' or 'es')", "field": "lang" } } Validation Error with Multiple Fields: { "success": false, "error": { "code": "VALIDATION_FAILED", "message": "Request validation failed", "fields": { "lang": "Invalid language", "length": "Invalid length" } } } Internal Error (Generic): { "success": false, "error": { "code": "INTERNAL_ERROR", "message": "An unexpected error occurred. Please try again." } } HTML Error Page (for page requests):
We're sorry, but we couldn't process your request.
Error: INVALID_LANGUAGE
Go back home ``` ## Error Logging ``` ┌────────────────────────────────────────────────────────────┐ │ Error Logging │ └────────────────────────────────────────────────────────────┘ Log Format: [ERROR]