Funcionalidade Craft

O Craft permite que desenvolvedores gerem automaticamente funções de manipulação e endpoints através de anotações simples, integrando-se perfeitamente com a geração de documentação OpenAPI.

Casos de Uso

A funcionalidade Craft é particularmente útil nos seguintes cenários:

  • Quando você precisa criar rapidamente funções de manipulação de rotas a partir de métodos de structs
  • Quando deseja reduzir código repetitivo para extração manual de parâmetros e tratamento de erros
  • Quando precisa gerar automaticamente documentação OpenAPI para sua API
  • Quando quer desacoplar a lógica de negócio do framework web

Uso Básico

Para usar a funcionalidade Craft, você precisa importar os seguintes módulos:

use salvo::oapi::extract::*;
use salvo::prelude::*;

Criando uma Struct de Serviço

Anote seu bloco impl com a macro #[craft] para converter métodos da struct em funções de manipulação ou endpoints.

#[derive(Clone)]
pub struct Opts {
    state: i64,
}

#[craft]
impl Opts {
    // Construtor
    fn new(state: i64) -> Self {
        Self { state }
    }
    
    // Mais métodos...
}

Criando Funções de Manipulação

Use #[craft(handler)] para converter um método em uma função de manipulação:

#[craft(handler)]
fn add1(&self, left: QueryParam<i64>, right: QueryParam<i64>) -> String {
    (self.state + *left + *right).to_string()
}

Este método se tornará uma função de manipulação que:

  • Extrai automaticamente os valores left e right dos parâmetros de consulta
  • Acessa o state da struct
  • Retorna o resultado do cálculo como uma resposta em string

Criando Endpoints

Use #[craft(endpoint)] para converter um método em um endpoint:

#[craft(endpoint)]
pub(crate) fn add2(
    self: ::std::sync::Arc<Self>,
    left: QueryParam<i64>,
    right: QueryParam<i64>,
) -> String {
    (self.state + *left + *right).to_string()
}

Endpoints podem utilizar Arc para compartilhar estado, o que é particularmente útil ao lidar com requisições concorrentes.

Endpoints Estáticos

Você também pode criar endpoints estáticos que não dependem do estado da instância:

#[craft(endpoint(responses((status_code = 400, description = "Parâmetros de requisição incorretos."))))]
pub fn add3(left: QueryParam<i64>, right: QueryParam<i64>) -> String {
    (*left + *right).to_string()
}

Neste exemplo, uma descrição personalizada de resposta de erro é adicionada, que será refletida na documentação OpenAPI gerada.

Extratores de Parâmetros

O módulo oapi::extract do Salvo fornece vários extratores de parâmetros, sendo os mais comuns:

  • QueryParam<T>: Extrai parâmetros de strings de consulta
  • PathParam<T>: Extrai parâmetros de caminhos URL
  • FormData<T>: Extrai parâmetros de dados de formulário
  • JsonBody<T>: Extrai parâmetros de corpos de requisição JSON

Esses extratores lidam automaticamente com análise de parâmetros e conversão de tipos, simplificando muito a escrita de funções de manipulação.

Integração com OpenAPI

A funcionalidade Craft pode gerar automaticamente documentação de API compatível com a especificação OpenAPI. No exemplo:

let router = Router::new()
    .push(Router::with_path("add1").get(opts.add1()))
    .push(Router::with_path("add2").get(opts.add2()))
    .push(Router::with_path("add3").get(Opts::add3()));

// Gerar documentação OpenAPI
let doc = OpenApi::new("API de Exemplo", "0.0.1").merge_router(&router);

// Adicionar rotas de documentação OpenAPI e Swagger UI
let router = router
    .push(doc.into_router("/api-doc/openapi.json"))
    .push(SwaggerUi::new("/api-doc/openapi.json").into_router("swagger-ui"));

Com esta configuração, a documentação da API estará disponível no endpoint /api-doc/openapi.json, e o Swagger UI será acessível no caminho /swagger-ui.

Exemplo Completo

Abaixo está um exemplo completo demonstrando como usar a funcionalidade Craft para criar três tipos diferentes de endpoints:

main.rs
Cargo.toml
use salvo::oapi::extract::*;
use salvo::prelude::*;
use std::sync::Arc;

// Options struct holding a state value for calculations
#[derive(Clone)]
pub struct Opts {
    state: i64,
}

// Implement methods for Opts using the craft macro for API generation
#[craft]
impl Opts {
    // Constructor for Opts
    fn new(state: i64) -> Self {
        Self { state }
    }

    // Handler method that adds state value to two query parameters
    #[craft(handler)]
    fn add1(&self, left: QueryParam<i64>, right: QueryParam<i64>) -> String {
        (self.state + *left + *right).to_string()
    }

    // Endpoint method using Arc for shared state
    #[craft(endpoint)]
    pub(crate) fn add2(
        self: ::std::sync::Arc<Self>,
        left: QueryParam<i64>,
        right: QueryParam<i64>,
    ) -> String {
        (self.state + *left + *right).to_string()
    }

    // Static endpoint method with custom error response
    #[craft(endpoint(responses((status_code = 400, description = "Wrong request parameters."))))]
    pub fn add3(left: QueryParam<i64>, right: QueryParam<i64>) -> String {
        (*left + *right).to_string()
    }
}

#[tokio::main]
async fn main() {
    // Create shared state with initial value 1
    let opts = Arc::new(Opts::new(1));

    // Configure router with three endpoints:
    // - /add1: Uses instance method with state
    // - /add2: Uses Arc-wrapped instance method
    // - /add3: Uses static method without state
    let router = Router::new()
        .push(Router::with_path("add1").get(opts.add1()))
        .push(Router::with_path("add2").get(opts.add2()))
        .push(Router::with_path("add3").get(Opts::add3()));

    // Generate OpenAPI documentation
    let doc = OpenApi::new("Example API", "0.0.1").merge_router(&router);

    // Add OpenAPI documentation and Swagger UI routes
    let router = router
        .push(doc.into_router("/api-doc/openapi.json"))
        .push(SwaggerUi::new("/api-doc/openapi.json").into_router("swagger-ui"));

    // Start server on localhost:8698
    let acceptor = TcpListener::new("127.0.0.1:8698").bind().await;
    Server::new(acceptor).serve(router).await;
}