Validação e mapeamento
request: o que a rota aceita
request declara o que uma rota aceita — e isso já é aplicado automaticamente pelo framework,
sem nenhum step: um header ou parâmetro de query obrigatório ausente já responde 400 antes mesmo
do pipeline começar a rodar.
- id: createWidget
method: POST
path: /widgets
request:
headers:
- { name: X-Tenant-Id, required: true }
body:
schema: schemas/widget-create.json
contentType: application/json
request.body.schema alimenta a documentação OpenAPI gerada, mas a validação de corpo em runtime
continua sendo responsabilidade do step validate, explicitamente declarado no pipeline — as duas
coisas são independentes por design.
validate: recusando o que não serve
- type: validate
mode: collect # collect: reporta todos os erros de uma vez; failFast: para no primeiro
rules:
- { expr: "$count(input.body.items) > 0", code: EMPTY_ORDER, path: /items, message: "pedido sem itens" }
- { expr: "input.body.total > 0", code: INVALID_TOTAL, path: /total, message: "total inválido" }
validate aceita dois blocos, que podem ser usados juntos ou separadamente:
schema— um JSON Schema, valida a forma do payload (tipos, campos obrigatórios, formatos).rules— uma lista de regras em JSONata, cada uma comexpr(a condição que precisa ser verdadeira),code(o código de erro),path(opcional, aponta para o campo problemático) emessage.
A divisão de responsabilidade é intencional: JSON Schema valida a forma, JSONata valida a regra de negócio. Misturar as duas no mesmo lugar tende a produzir schemas difíceis de ler; manter a separação deixa cada uma fazer o que faz melhor.
Uma falha em validate produz automaticamente code: validation_failed — o bloco errors: da
rota (veja Referência de configuração YAML) só decide o
status/title HTTP que esse código vira:
errors:
- { code: validation_failed, status: 400, title: "Widget inválido" }
map: transformando dados
- type: map
id: order
to: vars.order
expr: |
{
"id": $uuid(),
"customerId": input.body.customerId,
"total": $sum(input.body.items.(price * quantity)),
"createdAt": $now()
}
map avalia uma expressão JSONata sobre o estado atual do pipeline e grava o resultado em
to: — tipicamente um caminho sob vars. É assim que você transforma o payload recebido no
formato que o resto do pipeline (persistência, resposta, evento publicado) realmente precisa.
Juntando as duas coisas
- id: createWidget
method: POST
path: /widgets
request:
headers:
- { name: X-Tenant-Id, required: true }
pipeline:
- type: validate
mode: collect
rules:
- { expr: "input.body.name != null", code: MISSING_NAME, path: /name, message: "name é obrigatório" }
- type: map
to: vars.widget
expr: "{ 'id': $uuid(), 'name': input.body.name }"
- type: return
response:
status: 201
body: "vars.widget"
errors:
- { code: validation_failed, status: 400, title: "Widget inválido" }
Repare que não é preciso passar output: explícito em return quando response.body já avalia a
mesma expressão — normalmente basta type: return puro e deixar response.body fazer o trabalho.
Para onde ir daqui
- Persistência — o próximo passo natural depois de validar e mapear: gravar o dado.
- Controle de fluxo — decidir o que fazer quando a validação falha de
formas mais elaboradas do que um
errors:simples permite.