4528e04bad
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
168 lines
5.1 KiB
Go
168 lines
5.1 KiB
Go
package handlers
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
)
|
|
|
|
// ==============================================================================
|
|
// REQUEST/RESPONSE TYPES
|
|
// Structured types for common request patterns with validation
|
|
// ==============================================================================
|
|
|
|
// LanguageRequest represents a request with language parameter
|
|
type LanguageRequest struct {
|
|
Lang string `validate:"required,oneof=en es"`
|
|
}
|
|
|
|
// ParseLanguageRequest parses and validates language from query parameters
|
|
func ParseLanguageRequest(r *http.Request) (*LanguageRequest, error) {
|
|
lang := r.URL.Query().Get("lang")
|
|
if lang == "" {
|
|
lang = "en"
|
|
}
|
|
|
|
// Validate language
|
|
if lang != "en" && lang != "es" {
|
|
return nil, fmt.Errorf("unsupported language: %s (use 'en' or 'es')", lang)
|
|
}
|
|
|
|
return &LanguageRequest{Lang: lang}, nil
|
|
}
|
|
|
|
// PDFExportRequest represents all parameters for PDF export
|
|
type PDFExportRequest struct {
|
|
Lang string `validate:"required,oneof=en es"` // Language: "en" or "es"
|
|
Length string `validate:"required,oneof=short long"` // Length: "short" or "long"
|
|
Icons string `validate:"required,oneof=show hide"` // Icons: "show" or "hide"
|
|
Version string `validate:"required,oneof=with_skills clean"` // Version: "with_skills" or "clean"
|
|
}
|
|
|
|
// ParsePDFExportRequest parses and validates PDF export parameters
|
|
func ParsePDFExportRequest(r *http.Request) (*PDFExportRequest, error) {
|
|
req := &PDFExportRequest{
|
|
Lang: r.URL.Query().Get("lang"),
|
|
Length: r.URL.Query().Get("length"),
|
|
Icons: r.URL.Query().Get("icons"),
|
|
Version: r.URL.Query().Get("version"),
|
|
}
|
|
|
|
// Set defaults
|
|
if req.Lang == "" {
|
|
req.Lang = "en"
|
|
}
|
|
if req.Length == "" {
|
|
req.Length = "short"
|
|
}
|
|
if req.Icons == "" {
|
|
req.Icons = "show"
|
|
}
|
|
if req.Version == "" {
|
|
req.Version = "with_skills"
|
|
}
|
|
|
|
// Validate language
|
|
if req.Lang != "en" && req.Lang != "es" {
|
|
return nil, fmt.Errorf("unsupported language: %s (use 'en' or 'es')", req.Lang)
|
|
}
|
|
|
|
// Validate length
|
|
if req.Length != "short" && req.Length != "long" {
|
|
return nil, fmt.Errorf("unsupported length: %s (use 'short' or 'long')", req.Length)
|
|
}
|
|
|
|
// Validate icons
|
|
if req.Icons != "show" && req.Icons != "hide" {
|
|
return nil, fmt.Errorf("unsupported icons option: %s (use 'show' or 'hide')", req.Icons)
|
|
}
|
|
|
|
// Validate version
|
|
if req.Version != "with_skills" && req.Version != "clean" {
|
|
return nil, fmt.Errorf("unsupported version: %s (use 'with_skills' or 'clean')", req.Version)
|
|
}
|
|
|
|
return req, nil
|
|
}
|
|
|
|
// PreferenceToggleRequest represents a toggle request with language context
|
|
type PreferenceToggleRequest struct {
|
|
Lang string // Current language from query or cookie
|
|
}
|
|
|
|
// ParsePreferenceToggleRequest parses toggle request parameters
|
|
func ParsePreferenceToggleRequest(r *http.Request, defaultLang string) *PreferenceToggleRequest {
|
|
lang := r.URL.Query().Get("lang")
|
|
if lang == "" {
|
|
lang = defaultLang
|
|
}
|
|
|
|
return &PreferenceToggleRequest{Lang: lang}
|
|
}
|
|
|
|
// ==============================================================================
|
|
// RESPONSE TYPES
|
|
// Structured response types for consistent API responses
|
|
// ==============================================================================
|
|
|
|
// APIResponse is a standardized response wrapper for JSON responses
|
|
type APIResponse struct {
|
|
Success bool `json:"success"`
|
|
Data interface{} `json:"data,omitempty"`
|
|
Error *ErrorInfo `json:"error,omitempty"`
|
|
Meta *MetaInfo `json:"meta,omitempty"`
|
|
}
|
|
|
|
// ErrorInfo provides structured error information
|
|
type ErrorInfo struct {
|
|
Code string `json:"code"` // Error code (e.g., "INVALID_LANGUAGE")
|
|
Message string `json:"message"` // Human-readable error message
|
|
Field string `json:"field,omitempty"` // Field that caused the error
|
|
Details string `json:"details,omitempty"` // Additional error details
|
|
}
|
|
|
|
// MetaInfo provides metadata about the response
|
|
type MetaInfo struct {
|
|
Timestamp int64 `json:"timestamp,omitempty"` // Unix timestamp
|
|
Version string `json:"version,omitempty"` // API version
|
|
RequestID string `json:"request_id,omitempty"` // Request tracking ID
|
|
}
|
|
|
|
// SuccessResponse creates a success response
|
|
func SuccessResponse(data interface{}) *APIResponse {
|
|
return &APIResponse{
|
|
Success: true,
|
|
Data: data,
|
|
}
|
|
}
|
|
|
|
// NewErrorResponse creates an error response
|
|
func NewErrorResponse(code, message string) *APIResponse {
|
|
return &APIResponse{
|
|
Success: false,
|
|
Error: &ErrorInfo{
|
|
Code: code,
|
|
Message: message,
|
|
},
|
|
}
|
|
}
|
|
|
|
// ErrorResponseWithField creates an error response with field information
|
|
func ErrorResponseWithField(code, message, field string) *APIResponse {
|
|
return &APIResponse{
|
|
Success: false,
|
|
Error: &ErrorInfo{
|
|
Code: code,
|
|
Message: message,
|
|
Field: field,
|
|
},
|
|
}
|
|
}
|
|
|
|
// HealthCheckResponse represents health check endpoint response
|
|
type HealthCheckResponse struct {
|
|
Status string `json:"status"` // "healthy" or "unhealthy"
|
|
Version string `json:"version"` // Application version
|
|
Uptime int64 `json:"uptime,omitempty"` // Uptime in seconds
|
|
Checks map[string]bool `json:"checks,omitempty"` // Component health checks
|
|
}
|