- Added go-playground/validator dependency - Created pkg/validation/ package with custom validator wrapper - Implemented request validation for v2 greet endpoint - Added structured validation error responses - Extended BDD tests to cover validation scenarios - Updated AGENTS.md with v2 API documentation - Created ADR 0011-validation-library-selection.md - Simplified server handler creation code - Updated CHANGELOG with implementation details
111 lines
2.9 KiB
Go
111 lines
2.9 KiB
Go
package greet
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"strconv"
|
|
"io"
|
|
"net/http"
|
|
|
|
"DanceLessonsCoach/pkg/validation"
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
type GreeterV2 interface {
|
|
GreetV2(ctx context.Context, name string) string
|
|
}
|
|
|
|
type ApiV2Greet interface {
|
|
RegisterRoutes(router chi.Router)
|
|
}
|
|
|
|
type apiV2GreetHandler struct {
|
|
greeter GreeterV2
|
|
validator *validation.Validator
|
|
}
|
|
|
|
func NewApiV2GreetHandler(greeter GreeterV2, validator *validation.Validator) ApiV2Greet {
|
|
return &apiV2GreetHandler{greeter: greeter, validator: validator}
|
|
}
|
|
|
|
func (h *apiV2GreetHandler) RegisterRoutes(router chi.Router) {
|
|
log.Trace().Msg("Registering v2 greet routes")
|
|
router.Post("/", h.handleGreetPost)
|
|
log.Trace().Msg("v2 Greet routes registered")
|
|
}
|
|
|
|
type greetRequest struct {
|
|
Name string `json:"name" validate:"max=100"`
|
|
}
|
|
|
|
type greetResponse struct {
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
func (h *apiV2GreetHandler) handleGreetPost(w http.ResponseWriter, r *http.Request) {
|
|
// Read request body
|
|
body, err := io.ReadAll(r.Body)
|
|
if err != nil {
|
|
http.Error(w, `{"error":"failed to read request body"}`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Parse JSON
|
|
var req greetRequest
|
|
if err := json.Unmarshal(body, &req); err != nil {
|
|
http.Error(w, `{"error":"invalid JSON format"}`, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Validate request if validator is available
|
|
if h.validator != nil {
|
|
log.Trace().Str("name", req.Name).Msg("Validating request")
|
|
if err := h.validator.Validate(req); err != nil {
|
|
log.Trace().Err(err).Msg("Validation failed")
|
|
h.handleValidationError(w, err)
|
|
return
|
|
}
|
|
log.Trace().Msg("Validation passed")
|
|
}
|
|
|
|
// Call service
|
|
message := h.greeter.GreetV2(r.Context(), req.Name)
|
|
|
|
// Write response
|
|
h.writeJSONResponse(w, message)
|
|
}
|
|
|
|
func (h *apiV2GreetHandler) handleValidationError(w http.ResponseWriter, err error) {
|
|
var validationErr *validation.ValidationError
|
|
if errors.As(err, &validationErr) {
|
|
// Create structured validation error response
|
|
response := map[string]interface{}{
|
|
"error": "validation_failed",
|
|
"message": "Invalid request data",
|
|
"details": make([]map[string]string, 0, len(validationErr.Messages)),
|
|
}
|
|
|
|
// Parse validation messages into structured format
|
|
for _, msg := range validationErr.Messages {
|
|
// Simple parsing - in production, use proper parsing
|
|
detail := map[string]string{
|
|
"message": msg,
|
|
}
|
|
response["details"] = append(response["details"].([]map[string]string), detail)
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
json.NewEncoder(w).Encode(response)
|
|
} else {
|
|
// Fallback for other types of errors
|
|
http.Error(w, `{"error":"validation_error","message":`+strconv.Quote(err.Error())+`}`, http.StatusBadRequest)
|
|
}
|
|
}
|
|
|
|
func (h *apiV2GreetHandler) writeJSONResponse(w http.ResponseWriter, message string) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(greetResponse{Message: message})
|
|
} |