Pular para o conteúdo

Negociação de conteúdo

JSON ou CSV no mesmo endereço: o cabeçalho Accept, o q, o curinga — e quando responder 406.

O mesmo recurso pode ter mais de uma representação: a lista de pedidos em JSON para um programa, em CSV para uma planilha. O cliente diz o que aceita no Accept, com um peso q de 0 a 1, e o servidor escolhe. Kiln.negociar faz a escolha como a RFC 9110 manda.

dataforge
adopt Arcane.Kiln as Kiln

pedidos := [{"id": 1, "total": 50}, {"id": 2, "total": 70}]
app := Kiln.app()

action listar(req):
    tipo := Kiln.negociar(req, ["application/json", "text/csv"])
    given tipo is void:
        yield Kiln.problema(406, "Formato não disponível",
            "esta rota responde application/json ou text/csv")
    given tipo is "text/csv":
        linhas := ["id,total"] + [$"{p["id"]},{p["total"]}" cycle p in pedidos]
        yield Kiln.text(linhas.join("\n"), 200, {"Content-Type": "text/csv; charset=utf-8", "Vary": "Accept"})
    yield Kiln.json(pedidos, 200, {"Vary": "Accept"})

Kiln.get(app, "/pedidos", listar)

assert Kiln.test(app, "GET", "/pedidos")["body"][0]["id"] is 1
csv := Kiln.test(app, "GET", "/pedidos", void, {"Accept": "text/csv"})
assert csv["body"].starts_with("id,total")
assert Kiln.test(app, "GET", "/pedidos", void, {"Accept": "image/png"})["status"] is 406

As regras, na ordem em que decidem#

AcceptEscolhePor quê
(nenhum)o primeiro oferecidoo cliente não pediu nada
text/csv;q=0.5, application/jsonJSONq maior vence
text/*o primeiro text/… oferecidocuringa de subtipo
*/*, text/csv;q=0nunca CSVq=0 é recusa explícita, e a faixa mais específica decide
image/pngvoid → 406nada que você oferece serve