Mariner/ docs← marinerhttp.dev

02 · GUIAS

Contratos

Derive um JSON Schema de uma resposta que já funciona e todas as respostas seguintes são medidas contra essa forma — no painel de resposta, no runner de coleções e por MCP.

Capture, não escreva

Ninguém escreve um JSON Schema à mão, e é por isso que contract testing quase nunca começa. O Mariner inverte o gesto:

  1. Envie a requisição enquanto a API está se comportando.
  2. Abra a aba Contract do editor de requisições — entre Variables e Post-Response — e aperte Derive from response.
  3. Esse é o contrato. Todas as respostas dali em diante são medidas contra ele.

A aba leva um • sempre que a requisição promete uma forma, então você sabe antes de gastar um envio. Ela abre em Forma: uma linha por campo com o nome, um chip de tipo, uma marca OBR quando o schema exige, e o resto do que ele exigir — format: date, os valores de um enum, uma faixa. Vinte linhas de chaves viram oito linhas que se leem em dois segundos. JSON fica a um segmento de distância com o documento cru, e avisa assim que o que você digita deixa de ser JSON válido.

Re-derive substitui o contrato quando a API muda de forma legítima; Clear tira. Sem assinatura, o cabeçalho mostra um chip Somente leitura: o schema continua na tela para você ler, e editá-lo espera até você assinar.

O schema é um documento JSON Schema (draft 2020-12), com o título da requisição, e continua editável embaixo — cole um traduzido da sua definição de Zod, Pydantic ou OpenAPI e funciona igual.

A coleção de boas-vindas já traz um montado: Contract → GET · Uma resposta sob contrato. Envie e o veredito aparece; troque /json por /uuid na URL e veja quebrar.

O que «derivado» quer dizer

Derivar lê a resposta que você tem, não a API que você gostaria de ter:

  • Só é exigido o que estava lá de verdade. Em um array, uma chave presente em todos os elementos amostrados é obrigatória; uma que aparece em alguns é descrita, mas fica opcional.
  • Campos a mais nunca quebram. additionalProperties não é emitido, então uma API que adiciona um campo continua passando — o contrato pega o que sumiu ou mudou de tipo, que é o que de fato quebra um cliente.
  • Um campo que às vezes é nulo sai como ["string", "null"] em vez de sair como mentira.
  • Um campo cujas amostras discordaram sai como schema vazio: ali vale qualquer coisa. Chutar rejeitaria payloads que a API realmente manda.
  • Os arrays são amostrados até 200 elementos e o aninhamento até 8 níveis.

O veredito, onde você já está olhando

  • Um chip CONTRACT fica ao lado do status: verde quando a resposta cumpre, âmbar com uma contagem quando há campos desviados, vermelho quando o próprio schema não é JSON válido. Clique para pular para a aba.
  • A aba Contract do painel de resposta leva um badge âmbar com o número de violações — aceso mesmo quando você está em outra aba, porque o trabalho dele é avisar justamente da aba que você não está olhando.
  • Essa aba é uma tabela, não uma lista de reclamações: cada campo que o contrato descreve com o valor que realmente chegou ao lado, e os desviados no topo com o motivo na própria linha. Somente diferenças esconde a maioria que cumpre, que é o que torna legível um contrato de quarenta campos. Clique com o botão direito numa linha para copiar o caminho.
  • Cada violação é listada com seu caminho ($.slideshow.author) e o que houve: Expected string, got integer, Required field id is missing, e assim por diante. A lista para em 100.
  • Sem contrato, ou com uma resposta que não é JSON? Nada é mostrado. Um chip permanente de «sem contrato» sobre os 90 % das requisições que nunca vão ter um é exatamente o que ensina a parar de ler a faixa.

Um contrato quebrado nunca faz a requisição falhar. O servidor respondeu; o status é o que é. A validação é informativa e diz isso: aponta qual campo se mexeu e deixa o veredito do envio com o HTTP.

O que o validador cobre

Estrutura (type, properties, required, items, additionalProperties), valores (enum, const, minimum / maximum, minLength / maxLength, pattern, minItems / maxItems, uniqueItems, multipleOf) e composição (anyOf, allOf, oneOf, not, mais $ref local dentro de $defs).

Sem implementar, de propósito: $dynamicRef, unevaluatedProperties e a resolução de $ref remotos — aparecem nas suítes de conformidade, não em um schema derivado do seu próprio endpoint. format é tratado como a anotação que a especificação diz que é: um "date-time" que não parseia não é falha, porque metade das APIs do mundo quebraria o próprio contrato só por isso.

No runner de coleções

Execute uma coleção e cada requisição com contrato é conferida no caminho (Runner):

  • O resumo acrescenta Off contract e No contract ao lado de Passed / Failed, mas só quando pelo menos uma requisição da corrida tem contrato. Zero desvios sobre nove requisições que ninguém verifica não é boa notícia, então os dois números andam sempre juntos.
  • Cada linha ganha sua célula de contrato: selo verde, contagem âmbar, marca vermelha para um schema inservível, ou — quando não há o que conferir.
  • O relatório exportado também leva. O CSV tem uma coluna contract e o JSON um campo contract, os dois com passed, failed(n), invalid_schema ou not_json — as mesmas palavras que um agente recebe por MCP, para que um CI e um agente falem da mesma corrida sem traduzir.

A partir de um agente

O servidor MCP expõe o mesmo veredito, calculado pelo mesmo código:

Ferramenta Propósito
get_contract Ler o documento de schema que uma requisição promete
set_contract Guardar um schema que você construiu, ou passar derive_from_response: true para enviar uma vez e derivá-lo. Devolve o schema anterior
check_contract Enviar e conferir. variants: "all" testa cada variante de body salva por vez e deixa ativa a que estava
check_collection_contracts «Essa API mudou?» para uma coleção inteira; as requisições sem contrato são informadas como não verificadas, nunca puladas em silêncio

send_request e run_collection também incluem o veredito, então um agente que estava em outra coisa fica sabendo do mesmo jeito que a forma se mexeu.

Onde o schema vive

O contrato pertence à requisição, não a uma resposta: é parte da definição dela, então viaja junto na exportação e importação, na sincronização com o iCloud, e é o que o get_contract lê por MCP. Passe um pacote .mariner para um colega e os contratos vão junto. Fica guardado como documento cru — um schema que você colou do seu próprio repositório sai byte a byte igual, sem reformatação.

Uma requisição sem contrato não escreve chave nenhuma, então o pacote de quem nunca mexeu no recurso mantém exatamente a forma que tinha.

Ver em Markdown ↗