Concepto clave
En APIs de alto rendimiento, la validación de datos y el manejo de errores no son solo cuestiones de seguridad, sino de estabilidad del sistema. Imagina un restaurante de lujo donde los comensales envían pedidos: si no validas que un pedido de "filete" incluya el punto de cocción, el chef podría prepararlo incorrectamente y el cliente quedaría insatisfecho. En Go, validar datos antes de procesarlos evita que errores simples propaguen fallos en cascada.
La validación debe ocurrir lo más cerca posible del punto de entrada, idealmente en el handler HTTP. Esto sigue el principio de "fail fast": detectar problemas temprano reduce el uso innecesario de recursos. Un error de validación bien manejado devuelve una respuesta HTTP clara (como 400 Bad Request) con detalles específicos, mientras que un error no manejado puede causar un panic que derribe el microservicio.
Cómo funciona en la práctica
Veamos el flujo típico en un handler de gorilla/mux:
- Recibir la solicitud HTTP (POST/PUT) con datos JSON en el cuerpo
- Decodificar el JSON a una estructura Go usando json.Unmarshal
- Validar cada campo según reglas de negocio (requerido, formato, rangos)
- Si hay errores, construir una respuesta de error estructurada
- Si es válido, proceder con la lógica de negocio
La clave está en separar claramente la validación de entrada de la lógica del dominio. Esto permite reutilizar validaciones y hacer el código más testeable.
Código en acción
Antes: Validación básica sin estructura clara
func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
var user User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
}
// Validación dispersa en el handler
if user.Name == "" {
http.Error(w, "Name is required", http.StatusBadRequest)
return
}
if user.Age < 18 {
http.Error(w, "Age must be 18+", http.StatusBadRequest)
return
}
// ... más lógica
}Después: Validación estructurada con funciones dedicadas
type UserRequest struct {
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
}
func (u *UserRequest) Validate() []string {
var errors []string
if strings.TrimSpace(u.Name) == "" {
errors = append(errors, "name is required")
}
if !isValidEmail(u.Email) {
errors = append(errors, "email format invalid")
}
if u.Age < 18 || u.Age > 120 {
errors = append(errors, "age must be between 18 and 120")
}
return errors
}
func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
var req UserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
respondWithError(w, http.StatusBadRequest, "Invalid request format")
return
}
if validationErrors := req.Validate(); len(validationErrors) > 0 {
respondWithValidationErrors(w, validationErrors)
return
}
// Lógica de negocio con datos validados
// ...
}Errores comunes
- Validar solo en el cliente: Nunca confíes en la validación del frontend. Siempre valida en el backend, donde controlas las reglas de negocio.
- Devolver errores genéricos: "Error de validación" no ayuda al cliente. Especifica qué campo falló y por qué (ej: "email: formato inválido").
- Panic por datos inválidos: Usar panic() para errores de validación es excesivo. Maneja los errores gracefulmente con respuestas HTTP apropiadas.
- Validar después de procesar: Si validas datos después de iniciar operaciones costosas (como consultas a BD), desperdicias recursos. Valida primero.
- Olvidar sanitización: Validar no es lo mismo que sanitizar. Un email puede tener formato válido pero contener SQL injection. Usa parámetros preparados en consultas.
Checklist de dominio
- ¿Validas TODOS los datos de entrada, incluyendo parámetros de query y headers cuando son críticos?
- ¿Tus respuestas de error incluyen mensajes específicos y códigos HTTP apropiados (400, 422, etc.)?
- ¿Separaste la lógica de validación de la lógica de negocio para facilitar testing?
- ¿Usas estructuras dedicadas para requests en lugar de estructuras de dominio para validación?
- ¿Implementaste límites de tamaño para request bodies para prevenir DoS?
- ¿Tus validaciones incluyen reglas de negocio (ej: "saldo mínimo: 10") además de formato?
- ¿Documentaste las reglas de validación para que otros desarrolladores las entiendan?
Refactorizar handler con validación estructurada
En este ejercicio, mejorarás un handler existente que tiene problemas de validación. Sigue estos pasos:
- Descarga el código base desde el repositorio del curso (archivo:
buggy_handler.go) - Analiza el handler
ProcessOrderHandlerque actualmente valida datos de forma inconsistente - Crea una estructura
OrderRequestcon los campos: ProductID (string), Quantity (int), Priority (string) - Implementa el método
Validate() []stringque verifique:- ProductID no vacío y máximo 50 caracteres
- Quantity entre 1 y 1000
- Priority solo acepte valores: "low", "medium", "high"
- Refactoriza el handler para usar la nueva estructura y método Validate
- Implementa la función
respondWithValidationErrorsque devuelva JSON con formato:{"errors": ["error1", "error2"]}y status 422 - Escribe al menos 2 tests unitarios para el método Validate
Entrega: Archivo Go refactorizado y archivo de tests.
Pistas- Usa strings.TrimSpace() para validar campos string no vacíos
- Considera usar un map[string]bool para validar valores permitidos en Priority
- En los tests, prueba casos borde como Quantity = 0 y Quantity = 1001
Evalua tu comprension
Completa el quiz interactivo de arriba para ganar XP.