feat: Complete all remaining Future Improvements (#4-8)
Implemented 5 additional architectural improvements: 1. Response Types (types.go) - APIResponse with Success, Data, Error, Meta fields - ErrorInfo with Code, Message, Field, Details - MetaInfo with Timestamp, Version, RequestID - SuccessResponse() and NewErrorResponse() helpers - HealthCheckResponse for health endpoint - Consistent JSON API responses 2. Validation Tags (types.go) - Added struct tags to LanguageRequest - Added struct tags to PDFExportRequest - Declarative validation rules (oneof, required) - Self-documenting validation constraints - Ready for go-playground/validator integration 3. Context Helper Functions (middleware/preferences.go) - GetLanguage(), GetCVLength(), GetCVIcons(), GetCVTheme(), GetColorTheme() - IsLongCV(), IsShortCV() boolean helpers - ShowIcons(), HideIcons() boolean helpers - IsCleanTheme(), IsDefaultTheme() boolean helpers - IsDarkMode(), IsLightMode() boolean helpers - 13 new convenience functions for cleaner code 4. Typed Errors (errors.go) - ErrorCode constants for all error types - DomainError with Code, Message, Err, StatusCode, Field - Unwrap() support for error chains - WithError() and WithField() fluent builders - InvalidLanguageError(), InvalidLengthError(), etc. - PDFGenerationError(), MethodNotAllowedError(), RateLimitError() - 13 error codes, domain-specific constructors 5. Benchmark Tests - handlers/benchmarks_test.go (11 benchmarks) - middleware/benchmarks_test.go (12 benchmarks) - Sequential benchmarks for handlers, middleware, request parsing - Parallel benchmarks for concurrent load testing - Response creation benchmarks - Helper function benchmarks Benefits: - Type Safety: Validation tags and structured types - Developer Experience: 13 context helpers reduce boilerplate - Error Handling: Domain-specific errors with codes - Performance Monitoring: 23 benchmarks for regression detection - API Consistency: Standardized response formats - Maintainability: Self-documenting validation and errors Testing: - All unit tests pass - All benchmarks working - Build succeeds - No breaking changes
This commit is contained in:
@@ -2,6 +2,7 @@ package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
@@ -141,3 +142,136 @@ func DataLoadError(err error, dataType string) *AppError {
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
// ==============================================================================
|
||||
// TYPED ERRORS
|
||||
// Domain-specific error types for better error handling
|
||||
// ==============================================================================
|
||||
|
||||
// ErrorCode represents a specific error condition
|
||||
type ErrorCode string
|
||||
|
||||
const (
|
||||
ErrCodeInvalidLanguage ErrorCode = "INVALID_LANGUAGE"
|
||||
ErrCodeInvalidLength ErrorCode = "INVALID_LENGTH"
|
||||
ErrCodeInvalidIcons ErrorCode = "INVALID_ICONS"
|
||||
ErrCodeInvalidTheme ErrorCode = "INVALID_THEME"
|
||||
ErrCodeInvalidVersion ErrorCode = "INVALID_VERSION"
|
||||
ErrCodeTemplateNotFound ErrorCode = "TEMPLATE_NOT_FOUND"
|
||||
ErrCodeTemplateRender ErrorCode = "TEMPLATE_RENDER"
|
||||
ErrCodeDataLoad ErrorCode = "DATA_LOAD"
|
||||
ErrCodePDFGeneration ErrorCode = "PDF_GENERATION"
|
||||
ErrCodeMethodNotAllowed ErrorCode = "METHOD_NOT_ALLOWED"
|
||||
ErrCodeUnauthorized ErrorCode = "UNAUTHORIZED"
|
||||
ErrCodeForbidden ErrorCode = "FORBIDDEN"
|
||||
ErrCodeRateLimitExceeded ErrorCode = "RATE_LIMIT_EXCEEDED"
|
||||
)
|
||||
|
||||
// DomainError represents a domain-specific error
|
||||
type DomainError struct {
|
||||
Code ErrorCode
|
||||
Message string
|
||||
Err error
|
||||
StatusCode int
|
||||
Field string // Optional field that caused the error
|
||||
}
|
||||
|
||||
// Error implements the error interface
|
||||
func (e *DomainError) Error() string {
|
||||
if e.Err != nil {
|
||||
return fmt.Sprintf("%s: %v", e.Code, e.Err)
|
||||
}
|
||||
return fmt.Sprintf("%s: %s", e.Code, e.Message)
|
||||
}
|
||||
|
||||
// Unwrap returns the underlying error
|
||||
func (e *DomainError) Unwrap() error {
|
||||
return e.Err
|
||||
}
|
||||
|
||||
// NewDomainError creates a new domain error
|
||||
func NewDomainError(code ErrorCode, message string, statusCode int) *DomainError {
|
||||
return &DomainError{
|
||||
Code: code,
|
||||
Message: message,
|
||||
StatusCode: statusCode,
|
||||
}
|
||||
}
|
||||
|
||||
// WithError adds an underlying error
|
||||
func (e *DomainError) WithError(err error) *DomainError {
|
||||
e.Err = err
|
||||
return e
|
||||
}
|
||||
|
||||
// WithField adds field information
|
||||
func (e *DomainError) WithField(field string) *DomainError {
|
||||
e.Field = field
|
||||
return e
|
||||
}
|
||||
|
||||
// Common domain error constructors
|
||||
|
||||
func InvalidLanguageError(lang string) *DomainError {
|
||||
return NewDomainError(
|
||||
ErrCodeInvalidLanguage,
|
||||
fmt.Sprintf("Unsupported language: %s (use 'en' or 'es')", lang),
|
||||
http.StatusBadRequest,
|
||||
).WithField("lang")
|
||||
}
|
||||
|
||||
func InvalidLengthError(length string) *DomainError {
|
||||
return NewDomainError(
|
||||
ErrCodeInvalidLength,
|
||||
fmt.Sprintf("Unsupported length: %s (use 'short' or 'long')", length),
|
||||
http.StatusBadRequest,
|
||||
).WithField("length")
|
||||
}
|
||||
|
||||
func InvalidIconsError(icons string) *DomainError {
|
||||
return NewDomainError(
|
||||
ErrCodeInvalidIcons,
|
||||
fmt.Sprintf("Unsupported icons option: %s (use 'show' or 'hide')", icons),
|
||||
http.StatusBadRequest,
|
||||
).WithField("icons")
|
||||
}
|
||||
|
||||
func InvalidThemeError(theme string) *DomainError {
|
||||
return NewDomainError(
|
||||
ErrCodeInvalidTheme,
|
||||
fmt.Sprintf("Unsupported theme: %s (use 'default' or 'clean')", theme),
|
||||
http.StatusBadRequest,
|
||||
).WithField("theme")
|
||||
}
|
||||
|
||||
func InvalidVersionError(version string) *DomainError {
|
||||
return NewDomainError(
|
||||
ErrCodeInvalidVersion,
|
||||
fmt.Sprintf("Unsupported version: %s (use 'with_skills' or 'clean')", version),
|
||||
http.StatusBadRequest,
|
||||
).WithField("version")
|
||||
}
|
||||
|
||||
func PDFGenerationError(err error) *DomainError {
|
||||
return NewDomainError(
|
||||
ErrCodePDFGeneration,
|
||||
"Failed to generate PDF",
|
||||
http.StatusInternalServerError,
|
||||
).WithError(err)
|
||||
}
|
||||
|
||||
func MethodNotAllowedError(method string) *DomainError {
|
||||
return NewDomainError(
|
||||
ErrCodeMethodNotAllowed,
|
||||
fmt.Sprintf("Method %s not allowed", method),
|
||||
http.StatusMethodNotAllowed,
|
||||
)
|
||||
}
|
||||
|
||||
func RateLimitError() *DomainError {
|
||||
return NewDomainError(
|
||||
ErrCodeRateLimitExceeded,
|
||||
"Rate limit exceeded. Please try again later.",
|
||||
http.StatusTooManyRequests,
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user