Pular para o conteúdo

Resolvedores

Quem responde por cada campo: contexto, autorização, erros, middleware e a ordem das coisas.

Um resolvedor é a ação que responde por um campo. Ele recebe o objeto pai, os argumentos e o contexto — e devolve o valor.

dataforge
action pedidos_de(usuario, args, ctx):
    yield Banco.pedidos(usuario.id, limite := args["limite"])

Lavra.campo(esq, "Usuario", "pedidos", "[Pedido!]!",
    args := {"limite": {"tipo": "Integer", "padrao": 10}},
    resolve := pedidos_de)

A ordem em que as coisas acontecem#

text
texto da consulta
    ↓  ler          — vira árvore; erro aqui traz linha e coluna
    ↓  validar      — TODOS os problemas de uma vez, sem resolver nada
    ↓  limites      — profundidade e custo, antes de qualquer resolvedor
    ↓  resolver     — campo a campo, do topo para as folhas
    ↓  lote         — a fila vira uma consulta só
    ↓  coagir       — o valor vira o tipo declarado
resposta: dados, erros, extensoes
Do texto ao resultado. Cada etapa entrega a seguinte. O analisador é o único opcional — e é o que fala antes de qualquer coisa rodar.

A validação vem antes de executar de propósito. Executar e descobrir no meio que o campo não existe já custou tudo o que veio antes — inclusive escritas, numa mudanca.

O contexto#

O contexto é o que atravessa a consulta inteira: quem pediu, a conexão do banco, o rastro. Ele é montado por pedido e morre com ele.

dataforge
action contexto_de(req):
    token := req["headers"]["authorization"] ?? ""
    yield {
        "usuario": autenticar(token),
        "banco": conexao,
        "ip": req["ip"],
    }

Lavra.montar(api, esq, "/lavra", contexto_de := contexto_de)
dataforge
action meu_perfil(raiz, args, ctx):
    given ctx["usuario"] is void:
        Lavra.recusar("entre para ver o seu perfil")
    yield ctx["usuario"]

Erros#

Um erro num campo não derruba a resposta inteira. O campo vira void, o erro entra na lista com o caminho até ele, e o resto da consulta continua.

dataforge
action risco_de(cliente, args, ctx):
    resposta := Malha.de(ctx["malha"], "risco").obter($"/risco/{cliente.id}")
    given resposta["status"] isnt 200:
        Lavra.erro("o serviço de risco não respondeu", codigo := "dependencia")
    yield resposta["corpo"]["nota"]
json
{
  "dados": {"cliente": {"nome": "Ana", "risco": void}},
  "erros": [{"mensagem": "o serviço de risco não respondeu",
             "caminho": ["cliente", "risco"],
             "codigo": "dependencia"}]
}

Quem pediu dez campos e teve um problema num recebe nove — não zero. É a diferença entre uma tela com um aviso e uma tela vazia.

A exceção é o campo !: ele prometeu nunca ser void, então um erro ali sobe para o pai, e daí para cima, até achar alguém que admita void.

Autorização#

Há três lugares, e eles resolvem coisas diferentes:

OndePara quê
o campo não está no esquemaninguém pode pedir, nunca
Lavra.recusar no resolvedoreste usuário não pode ver este dado
uma diretiva @admina regra vale para muitos campos, e aparece na consulta
dataforge
action cpf_de(usuario, args, ctx):
    given ctx["usuario"]?.id isnt usuario.id and ctx["papel"] isnt "admin":
        Lavra.recusar("o CPF é do próprio dono")
    yield usuario.cpf

Middleware#

Para o que vale para toda consulta — registro, rastro, tempo —, use o middleware do próprio Kiln, que roda antes da rota:

dataforge
action rastrear(req):
    Malha.propagar(req, "api")

server api on 8080:
    middleware rastrear
    middleware Kiln.rate_limit(60, 60)

Lavra.montar(api, esq, "/lavra")

Para o que vale por campo, o lugar é a diretiva própria — ela aparece na consulta, e é auditável lendo o texto.

Mudanças#

dataforge
action criar_pedido(raiz, args, ctx):
    dados := args["dados"]
    given len(dados["itens"]) is 0:
        Lavra.erro("um pedido precisa de ao menos um item", codigo := "validacao")
    yield Banco.transacao(ctx["banco"], lambda => gravar(dados))

Uma mudança que toca duas tabelas precisa de transação — e é Arcane.Database quem a dá. O Lavra não inventa uma: a venda gravada com o estoque não baixado é o mesmo problema em qualquer camada, e ele já está resolvido embaixo.