Mariner/ docs← marinerhttp.dev

02 · GUÍAS

Contratos

Deriva un JSON Schema de una respuesta que ya funciona y todas las respuestas posteriores se miden contra esa forma — en el panel de respuesta, en el runner de colecciones y por MCP.

Captúralo, no lo escribas

Nadie escribe un JSON Schema a mano, y por eso el contract testing casi nunca empieza. Mariner invierte el gesto:

  1. Envía la petición mientras la API se porta bien.
  2. Abre la pestaña Contract del editor de peticiones — entre Variables y Post-Response — y pulsa Derive from response.
  3. Ese es el contrato. Todas las respuestas a partir de ahora se miden contra él.

La pestaña lleva un • siempre que la petición promete una forma, así que lo sabes antes de gastar un envío. Abre en Forma: una fila por campo con su nombre, un chip de tipo, una marca REQ cuando el schema lo exige, y lo demás que exija — format: date, los valores de un enum, un rango. Veinte líneas de llaves se convierten en ocho filas que lees en dos segundos. JSON está a un segmento de distancia con el documento en crudo, y avisa en cuanto lo que escribes deja de ser JSON válido.

Re-derive reemplaza el contrato cuando la API cambia de forma legítima; Clear lo quita. Sin suscripción la cabecera muestra un chip Solo lectura: el schema sigue en pantalla para leerlo, y editarlo espera a que te suscribas.

El schema es un documento JSON Schema (draft 2020-12), titulado con el nombre de la petición, y sigue siendo editable debajo — pega uno traducido de tu definición de Zod, Pydantic u OpenAPI y funciona igual.

La colección de bienvenida trae uno ya montado: Contract → GET · Una respuesta bajo contrato. Envíala y aparece el veredicto; cambia /json por /uuid en la URL y míralo romperse.

Qué significa «derivado»

Derivar lee la respuesta que tienes, no la API que te gustaría tener:

  • Solo se exige lo que estaba de verdad. En un array, una clave presente en todos los elementos muestreados es obligatoria; una que aparece en algunos se describe pero queda opcional.
  • Los campos de más nunca lo rompen. No se emite additionalProperties, así que una API que añade un campo sigue cumpliendo — el contrato caza lo que desapareció o cambió de tipo, que es lo que de verdad rompe un cliente.
  • Un campo que a veces es nulo sale como ["string", "null"] en vez de como una mentira.
  • Un campo cuyas muestras no se pusieron de acuerdo sale como schema vacío: ahí valida cualquier cosa. Adivinar rechazaría payloads que la API sí envía.
  • Los arrays se muestrean hasta 200 elementos y el anidado hasta 8 niveles.

El veredicto, donde ya estás mirando

  • Un chip CONTRACT se sitúa junto al estado: verde cuando la respuesta cumple, ámbar con un recuento cuando hay campos desviados, rojo cuando el propio schema no es JSON válido. Púlsalo para saltar a la pestaña.
  • La pestaña Contract del panel de respuesta lleva un badge ámbar con el número de incumplimientos — encendido incluso cuando estás en otra pestaña, porque su trabajo es avisarte justo desde la pestaña que no estás mirando.
  • Esa pestaña es una tabla, no una lista de quejas: cada campo que describe el contrato con el valor que llegó de verdad al lado, y los desviados ordenados arriba con el motivo en su propia línea. Solo diferencias esconde a la mayoría que cumple, que es lo que hace legible un contrato de cuarenta campos. Clic derecho en una fila para copiar su ruta.
  • Cada incumplimiento se lista con su ruta ($.slideshow.author) y qué pasó: Expected string, got integer, Required field id is missing, etc. La lista se corta en 100.
  • ¿Sin contrato, o una respuesta que no es JSON? No se muestra nada. Un chip permanente de «sin contrato» sobre el 90 % de las peticiones que nunca van a tener uno es exactamente lo que enseña a dejar de leer el strip.

Un contrato roto nunca hace fallar la petición. El servidor contestó; el estado es el que es. La validación es informativa y lo dice: señala qué campo se movió y deja el veredicto del envío en manos del HTTP.

Qué cubre el validador

Estructura (type, properties, required, items, additionalProperties), valores (enum, const, minimum / maximum, minLength / maxLength, pattern, minItems / maxItems, uniqueItems, multipleOf) y composición (anyOf, allOf, oneOf, not, más $ref local dentro de $defs).

Sin implementar, a propósito: $dynamicRef, unevaluatedProperties y la resolución de $ref remotos — aparecen en las suites de conformidad, no en un schema derivado de tu propio endpoint. format se trata como la anotación que la especificación dice que es: un "date-time" que no parsea no es un fallo, porque medio internet incumpliría su propio contrato solo por eso.

En el runner de colecciones

Ejecuta una colección y cada petición con contrato se comprueba sobre la marcha (Runner):

  • El resumen añade Off contract y No contract junto a Passed / Failed, pero solo cuando al menos una petición de la corrida tiene contrato. Cero desvíos sobre nueve peticiones que nadie comprueba no es una buena noticia, así que las dos cifras van siempre juntas.
  • Cada fila tiene su celda de contrato: sello verde, recuento ámbar, marca roja para un schema inservible, o — cuando no hay nada que comprobar.
  • El informe exportado también lo lleva. El CSV tiene una columna contract y el JSON un campo contract, ambos con passed, failed(n), invalid_schema o not_json — las mismas palabras que recibe un agente por MCP, para que un CI y un agente puedan hablar de la misma corrida sin traducir.

Desde un agente

El servidor MCP expone el mismo veredicto, calculado por el mismo código:

Herramienta Propósito
get_contract Leer el documento de schema que promete una petición
set_contract Guardar un schema que construiste, o pasar derive_from_response: true para enviar una vez y derivarlo. Devuelve el schema anterior
check_contract Enviar y comprobar. variants: "all" prueba cada variante de body guardada por turno y deja activa la que lo estaba
check_collection_contracts «¿Cambió esta API?» para una colección entera; las peticiones sin contrato se informan como no comprobadas, nunca se saltan en silencio

send_request y run_collection incluyen el veredicto también, así que un agente que estaba en otra cosa se entera igual de que la forma se movió.

Dónde vive el schema

El contrato pertenece a la petición, no a una respuesta: es parte de su definición, así que viaja con ella en la exportación e importación, en la sincronización con iCloud, y es lo que lee get_contract por MCP. Si le pasas un paquete .mariner a un compañero, los contratos van dentro. Se guarda como documento en crudo — un schema que pegaste desde tu propio repo sale byte a byte igual, sin reformatear.

Una petición sin contrato no escribe ninguna clave, así que el paquete de quien nunca tocó la función conserva exactamente la forma que tenía.

Ver en Markdown ↗