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.
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.
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:
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;
}
[package]
name = "example-craft"
version.workspace = true
edition.workspace = true
publish.workspace = true
rust-version.workspace = true
[dependencies]
salvo = { workspace = true, features = ["craft", "oapi"] }
tokio = { workspace = true, features = ["macros"] }
tracing.workspace = true
tracing-subscriber.workspace = true