28 · Servidor HTTP
Formulários de Action Tipados
Envie campos escalares para uma @action com HTML nativo e conversão no servidor pela assinatura.
Nesta página
Um formulário progressivo funciona antes de iniciar o JavaScript. Associe
action a uma função @action e dê a cada controle o nome do parâmetro
correspondente. O compilador gera o endpoint POST e um campo oculto com o
caminho local de retorno.
Renderize um formulário cuja ação recebe um inteiro e um booleano de checkbox.
// Feature: progressive forms take their field types from an @action signature.
// Run with: zolo run examples/features/36-web/33-typed-action-form.zolo --no-cache
use std::html::{View, label, input, button, render}
@action
fn save(age: int, newsletter: bool) -> Result<str, str> {
if age < 18 {
return Result::Err("You must be at least 18")
}
return Result::Ok("Age: {age}; newsletter: {newsletter}")
}
fn preferences() -> View {
<form action={save} return_to="/saved" id="preferences">
<label for="age">Age</label>
<input id="age" name="age" type="number" required />
<label for="newsletter">Receive the newsletter</label>
<input id="newsletter" name="newsletter" type="checkbox" />
<button type="submit">Save</button>
</form>
}
print(render(preferences()))
O formulário renderizado usa HTML normal. Seu id é preservado, age é
convertido para int, e newsletter desmarcado vira false.
Não precisamos de conversão manual, ilha ou script de cliente.
| Campos enviados | Comportamento do servidor |
|---|---|
age=25&newsletter=on |
Chama save(25, true). |
age=25 |
Chama save(25, false). |
age=abc, idade vazia ou ausente |
HTTP 422 por campo; a ação não executa. |
age=25&age=26 |
HTTP 422 por campo duplicado; a ação não executa. |
age=17 |
Chama a ação; a regra de negócio devolve Result::Err. |
Os tipos suportados são str, int, float e bool.
Texto precisa estar presente, mas pode ser vazio. Um inteiro precisa caber no
intervalo exato de 64 bits com sinal; um float precisa ser finito.
Booleanos aceitam on/true/1 ou off/false/0; omitir significa
false. A conversão acontece no servidor mesmo quando alguém ignora required
ou as verificações de tipo do navegador. Valores padrão dos parâmetros não
preenchem campos omitidos.
Para testar o fluxo completo, rode este servidor na CLI local e abra
http://localhost:3099. Ele continua executando até você pará-lo:
Uma página de formulário, uma ação tipada no servidor e uma página de sucesso.
// Feature: a typed progressive form works through a normal browser POST.
// Run with: zolo run examples/features/36-web/34-typed-action-form-server.zolo --no-cache
// Open http://localhost:3099. No client script or island is needed.
use std::http
use std::html::{View, document, head, title, body, main, h1, p, label, input, button, a}
@action
fn save(age: int, newsletter: bool) -> Result<str, str> {
if age < 18 {
return Result::Err("You must be at least 18")
}
print("Age: {age}; newsletter: {newsletter}")
return Result::Ok("Saved")
}
fn home(_req) -> View {
<document lang="en">
<head><title>Typed progressive form</title></head>
<body><main>
<h1>Preferences</h1>
<form action={save} return_to="/saved">
<label for="age">Age</label>
<input id="age" name="age" type="number" required />
<label for="newsletter">Receive the newsletter</label>
<input id="newsletter" name="newsletter" type="checkbox" />
<button type="submit">Save</button>
</form>
</main></body>
</document>
}
fn saved(_req) -> View {
<document lang="en">
<head><title>Saved</title></head>
<body><main>
<h1>Saved</h1>
<p>The action accepted the submitted values.</p>
<a href="/">Back to preferences</a>
</main></body>
</document>
}
let app = http.router()
|> http.get("/", home)
|> http.get("/saved", saved)
http.serve(3099, app)
Requer a CLI/host do Zolo — abra no playground ou rode localmente.
Envie idade 25 com o checkbox desmarcado. O handler imprime
Age: 25; newsletter: false; o navegador segue o HTTP 303 até /saved.
Uma requisição direta usa o mesmo caminho:
curl -i --data-urlencode "age=25" \
--data-urlencode "__zolo_return_to=/saved" \
http://localhost:3099/_zolo/action/features.36-web.34-typed-action-form-server.save
Esse endpoint pertence ao arquivo deste exemplo: o caminho inclui a identidade
do módulo e o nome da ação. No markup, passe apenas a referência da função com
action={save}. O compilador escreve a URL correta no atributo action do
formulário renderizado; não a reconstrua apenas pelo nome da função.
Falhas de conversão e resultados de negócio Result::Err devolvem HTTP 422.
Esse contrato não exibe erros automaticamente ao lado dos campos nem adiciona
estado de envio. Uploads, arrays, objetos, valores opcionais, parâmetros
variádicos e sem tipo precisam de outro endpoint ou de um contrato posterior.
return_to usa / por padrão e aceita apenas caminhos locais absolutos.
method="post" explícito é permitido; outros métodos são rejeitados.
action="/subscribe" com URL mantém o comportamento HTML normal.
O helper anterior web.action_form(save, return_to: "/saved") { ... } continua
disponível com a mesma conversão tipada.