Módulo 3 · Diseño de APIs

Lección 15 — Validación y contratos con OpenAPI

Validación de entrada y un contrato que el cliente puede leer sin preguntarte.

Publicada
En esta lección
  1. Ejercicio 1 — Serializer
  2. Ejercicio 2 — Capas
  3. Ejercicio 3 — Contrato
  4. Ejercicio 4 — Test de contrato
  5. Resumen del profesor

Ejercicio 1 — Serializer

python
class SeatRefSerializer(serializers.Serializer):
    sector = serializers.CharField(max_length=50)
    row = serializers.CharField(max_length=10)
    number = serializers.IntegerField(min_value=1)

class ReserveInputSerializer(serializers.Serializer):
    event = serializers.UUIDField()
    seats = SeatRefSerializer(many=True, min_length=1, max_length=8)  # límites
    promo_code = serializers.CharField(max_length=32, required=False, write_only=True)

    def validate_event(self, value):
        if not Event.objects.filter(uuid=value).exists():   # referencia trivial
            raise serializers.ValidationError("El evento no existe")
        return value
  1. Mensajes por caso: listas vacías/grade → "Este campo debe tener entre 1 y 8 elementos"; formato → error por índice con campo concreto; uuid → "El evento no existe" (mensaje de negocio, no "Invalid pk \"...\"").
  2. "Evento pasado" consulta estado mutable y podría cambiar entre validate y action (raza): lo decide el servicio con el evento bloqueado (10). El serializer es forma; el servicio, mundo.

Ejercicio 2 — Capas

  1. JSON roto → 400 (parser DRF, capa sintaxis); tipos mal → 400 con errors por campo (capa forma); asiento ocupado → 409 con type: seat-already-reserved (capa negocio, desde DomainError de la 10/13).
  2. "Asiento libre" en el serializer reintroduce check-then-act sin bloqueo: dos requests validan ok y ambos crean — exactamente la carrera de la 03/10 que la constraint convierte en 409 tarde y con mala UX. La validación de negocio mutable va bajo bloqueo, en el servicio.

Ejercicio 3 — Contrato

  1. Lo típico que adivina mal: viewsets con múltiples serializers (list vs detail), FileField/binary, errores 4xx no documentados. El YAML te lo señala.
  2. @extend_schema(request=ReserveInputSerializer, responses={201: ReservationOut, 409: ProblemDetail, 422: ProblemDetail}, summary="Crear reserva") — y el webhook con request=GatewayWebhookIn, responses={200: None}.
  3. --fail-on-warn obliga a documentar lo que el AutoSchema no deduce: cada warning arreglado es un endpoint que el frontend consume sin preguntarte.

Ejercicio 4 — Test de contrato

python
def test_reserva_cumple_contrato(self, client, ...):
    resp = client.post("/api/reservations/", payload)
    self.assertEqual(resp.status_code, 201)
    self.assertEqual(set(resp.data), set(ReservationOut.Meta.fields))  # claves del schema
  1. Con los examples extraídos y jsonschema.validate(resp.data, example_schema): las docs mienten cuando el código evoluciona sin actualizarlas — el test hace que mentir rompa CI.

Resumen del profesor

  • Capas: sintaxis → forma → negocio; el negocio bajo bloqueo, no en el serializer.
  • Serializer con límites explícitos, fields explícitos y mensajes de dominio.
  • OpenAPI generado del código + extend_schema donde adivina; --fail-on-warn en CI.
  • Un test de contrato por endpoint crítico: las docs dejan de mentir.

Después: Lección 16 — gRPC, GraphQL, WebSockets/SSE.