Dominar Esta Arte

Por Que Construir Este Framework

Como iniciante, enfrentei dificuldades para compreender frameworks existentes como actix-web e Rocket. Ao tentar reescrever um serviço web anterior em Go usando Rust, cada framework parecia mais complexo que os disponíveis em Go. Dada a já íngreme curva de aprendizado do Rust, por que complicar ainda mais os frameworks web?

Quando o Tokio introduziu o framework Axum, fiquei entusiasmado, pensando que finalmente poderia parar de manter meu próprio framework web. No entanto, logo percebi que, embora o Axum parecesse simples, ele exigia extensas ginásticas de tipos e definições genéricas. Criar até mesmo um middleware básico demandava profundo conhecimento de Rust e a escrita de grandes quantidades de código boilerplate obscuro.

Assim, decidi continuar mantendo meu framework web único — um que seja intuitivo, rico em recursos e amigável para iniciantes.

O Salvo É Para Você?

O Salvo é simples, mas abrangente e poderoso, possivelmente o mais forte no ecossistema Rust. Apesar de suas capacidades, ele permanece fácil de aprender e usar, poupando você de quaisquer dores de "auto-castração".

  • Ideal para iniciantes em Rust: Operações CRUD são comuns e, com o Salvo, tais tarefas parecem tão diretas quanto com frameworks em outras linguagens (por exemplo, Express, Koa, Gin, Flask). Em alguns aspectos, o Salvo é ainda mais abstrato e conciso.

  • Adequado para produção: Se você pretende implantar Rust em produção para servidores robustos e de alta velocidade, o Salvo atende perfeitamente. Embora ainda não esteja na versão 1.0, seus recursos principais passaram por anos de iteração, garantindo estabilidade e resolução oportuna de problemas.

  • Perfeito para você, especialmente se seus cabelos estão afinando e caindo diariamente.

Alcançando a Simplicidade

O Hyper lida com muitas implementações de baixo nível, tornando-o uma base confiável para necessidades gerais. O Salvo segue essa abordagem, oferecendo um sistema de roteamento poderoso e flexível, juntamente com recursos essenciais como Acme, OpenAPI e autenticação JWT.

O Salvo unifica Handlers e Middleware: Middleware é um Handler. Ambos são anexados ao Router via hoop. Essencialmente, eles processam Request e podem escrever dados em Response. Um Handler recebe três parâmetros: Request, Depot (para dados temporários durante o processamento da requisição) e Response.

Para conveniência, você pode omitir parâmetros desnecessários ou ignorar sua ordem.

use salvo::prelude::*;

#[handler]
async fn hello_world(_req: &mut Request, _depot: &mut Depot, res: &mut Response) {
    res.render("Hello world");
}
#[handler]
async fn hello_world(res: &mut Response) {
    res.render("Hello world");
}

A API de roteamento é excepcionalmente simples, mas poderosa. Para casos de uso típicos, você só precisa se concentrar no tipo Router.

Além disso, se uma struct implementar traits relevantes, o Salvo pode gerar automaticamente documentação OpenAPI, extrair parâmetros, lidar com erros de forma elegante e retornar mensagens amigáveis ao usuário. Isso torna a escrita de handlers tão intuitiva quanto escrever funções comuns. Exploraremos esses recursos em detalhes mais tarde. Aqui está um exemplo:

#[endpoint(tags("message_logs"))]
pub async fn create_message_log_handler(
    input: JsonBody<CreateOrUpdateMessageLog>,
    depot: &mut Depot,
) -> APPResult<Json<MessageLog>> {
    let db = utils::get_db(depot)?;
    let log = create_message_log(&input, db).await?;
    Ok(Json(log))
}

Neste exemplo, JsonBody<CreateOrUpdateMessageLog> analisa automaticamente JSON do corpo da requisição no tipo CreateOrUpdateMessageLog (suportando múltiplas fontes de dados e tipos aninhados). A macro #[endpoint] gera documentação OpenAPI para este endpoint, simplificando a extração de parâmetros e o tratamento de erros.

Sistema de Roteamento

Acredito que o sistema de roteamento do Salvo se destaca. Roteadores podem ser planos ou em árvore, distinguindo entre árvores de lógica de negócio e árvores de diretório de acesso. A árvore de lógica de negócio organiza roteadores com base nas necessidades do negócio, que podem não coincidir com a árvore de diretório de acesso.

Normalmente, as rotas são escritas assim:

Router::new().path("articles").get(list_articles).post(create_article);
Router::new()
    .path("articles/{id}")
    .get(show_article)
    .patch(edit_article)
    .delete(delete_article);

Frequentemente, visualizar artigos e listá-los não requer login do usuário, mas criar, editar ou excluir artigos sim. O sistema de roteamento aninhado do Salvo lida com isso elegantemente. Podemos agrupar rotas públicas juntas:

Router::new()
    .path("articles")
    .get(list_articles)
    .push(Router::new().path("{id}").get(show_article));

Em seguida, agrupar rotas protegidas com middleware para autenticação:

Router::new()
    .path("articles")
    .hoop(auth_check)
    .post(create_article)
    .push(Router::new().path("{id}").patch(edit_article).delete(delete_article));

Embora ambos os roteadores compartilhem o mesmo path("articles"), eles podem ser adicionados ao mesmo roteador pai, resultando em:

Router::new()
    .push(
        Router::new()
            .path("articles")
            .get(list_articles)
            .push(Router::new().path("{id}").get(show_article)),
    )
    .push(
        Router::new()
            .path("articles")
            .hoop(auth_check)
            .post(create_article)
            .push(Router::new().path("{id}").patch(edit_article).delete(delete_article)),
    );

{id} corresponde a um segmento de caminho. Normalmente, um id de artigo é numérico, então podemos restringir a correspondência com uma regex: r"{id:/\d+/}".