Concepto clave
La validación de datos de entrada es el proceso de verificar que la información recibida por una API cumple con reglas específicas antes de procesarla. Imagina un formulario de registro web: sin validación, usuarios podrían enviar emails inválidos o contraseñas demasiado cortas, causando errores en el sistema. En Go, podemos usar struct tags para definir estas reglas directamente en nuestras estructuras de datos, combinando la definición del modelo con sus restricciones de validación.
Los struct tags son metadatos que agregamos a los campos de una estructura usando backticks. Paquetes como go-playground/validator leen estos tags para aplicar validaciones automáticamente. Esto es más eficiente que validar manualmente cada campo, especialmente en APIs con múltiples endpoints. La validación temprana evita que datos incorrectos fluyan por el sistema, reduciendo bugs y mejorando la seguridad.
Cómo funciona en la práctica
Primero, definimos una estructura que represente los datos de entrada, como un JSON para crear un usuario. Agregamos tags de validación usando el formato validate:"regla". Por ejemplo, Email string `json:"email" validate:"required,email"` indica que el campo email es obligatorio y debe tener formato de email válido.
Luego, en el handler HTTP, usamos el validador para comprobar los datos. Si hay errores, respondemos con un código 400 Bad Request y detalles de los campos inválidos. Esto proporciona feedback claro al cliente. La validación ocurre después de decodificar el JSON pero antes de cualquier lógica de negocio, asegurando que solo procesamos datos correctos.
Codigo en accion
Antes: Validación manual en el handler, propensa a errores y repetitiva.
func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
var user struct {
Email string
Password string
Age int
}
json.NewDecoder(r.Body).Decode(&user)
// Validación manual
if user.Email == "" {
http.Error(w, "Email es requerido", http.StatusBadRequest)
return
}
if !strings.Contains(user.Email, "@") {
http.Error(w, "Email inválido", http.StatusBadRequest)
return
}
if len(user.Password) < 8 {
http.Error(w, "Password debe tener al menos 8 caracteres", http.StatusBadRequest)
return
}
// Más validaciones...
}Después: Usando struct tags y validador, más limpio y mantenible.
import "github.com/go-playground/validator/v10"
type CreateUserRequest struct {
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=8"`
Age int `json:"age" validate:"gte=18"`
}
var validate = validator.New()
func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
var req CreateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, "JSON inválido", http.StatusBadRequest)
return
}
if err := validate.Struct(req); err != nil {
errors := err.(validator.ValidationErrors)
response := map[string]string{
"error": "Validación fallida",
"details": errors.Error(),
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
json.NewEncoder(w).Encode(response)
return
}
// Procesar usuario válido
fmt.Fprintf(w, "Usuario creado: %s", req.Email)
}Errores comunes
- No validar todos los campos: Olvidar tags en campos opcionales que aún necesitan reglas, como máximo longitud. Solución: Revisar cada campo del struct y asignar tags apropiados.
- Tags mal escritos: Errores tipográficos en reglas, como
min=8escrito comomin8. Solución: Usar constantes o revisar documentación del validador. - Validar solo en el handler: No reutilizar la validación en otros lugares, como tests o servicios. Solución: Crear una función separada para validar que pueda ser usada en múltiples capas.
- Mensajes de error genéricos: Responder solo "Validación fallida" sin detalles. Solución: Incluir información específica sobre qué campo falló y por qué.
- Ignorar validación de tipos: Asumir que el JSON siempre tendrá los tipos correctos. Solución: Usar tags como
validate:"number"para campos numéricos.
Checklist de dominio
- Definir structs con tags
jsonyvalidatepara todos los campos de entrada. - Instalar y configurar un validador como
go-playground/validatoren el proyecto. - Validar datos inmediatamente después de decodificar JSON en los handlers.
- Proporcionar respuestas de error claras con detalles de validación fallida.
- Escribir tests unitarios que verifiquen casos válidos e inválidos.
- Usar reglas comunes: required, email, min, max, gte (greater than or equal), lte.
- Documentar las reglas de validación en la especificación de la API.
Implementar validación en un endpoint de producto
En este ejercicio, mejorarás un endpoint existente para crear productos en una API de e-commerce. Sigue estos pasos:
- Descarga el código base que incluye un handler básico sin validación.
- Define un struct
CreateProductRequestcon campos:Name(string),Price(float64),Stock(int),Category(string). - Agrega tags
jsonpara mapear a claves JSON y tagsvalidatecon estas reglas:- Name: requerido, mínimo 3 caracteres, máximo 100.
- Price: requerido, mayor que 0.
- Stock: requerido, mínimo 0.
- Category: requerido, debe ser uno de: "electronics", "clothing", "books".
- Integra el validador en el handler para validar el struct después de decodificar el JSON.
- Si la validación falla, responde con status 400 y un JSON que liste los errores por campo.
- Si es exitosa, responde con status 201 y un mensaje de confirmación.
- Prueba con datos válidos e inválidos usando curl o Postman.
- Usa la regla 'oneof' para validar que Category esté en la lista permitida.
- Recuerda inicializar el validador una vez y reutilizarlo, no en cada request.
- Para Price mayor que 0, la regla es 'gt=0' (greater than).
Evalua tu comprension
Completa el quiz interactivo de arriba para ganar XP.