Características do Craft
O Craft permite que os desenvolvedores gerem automaticamente funções de processamento e endpoints através de simples anotações, integrando-se perfeitamente com a geração de documentação OpenAPI.
Cenários de Uso
As características do Craft são especialmente úteis nos seguintes cenários:
- Quando você precisa criar rapidamente funções de roteamento a partir de métodos de estruturas
- Quando deseja reduzir código repetitivo para extração de parâmetros e tratamento de erros
- Quando precisa gerar automaticamente documentação OpenAPI para sua API
- Quando deseja desacoplar a lógica de negócios do framework web
Uso Básico
Para usar as características do Craft, é necessário importar os seguintes módulos:
use salvo::oapi::extract::*;
use salvo::prelude::*;
Criando uma Estrutura de Serviço
Use a macro #[craft]
para anotar seu bloco impl, convertendo métodos da estrutura em funções de processamento 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 Processamento
Use #[craft(handler)]
para converter um método em uma função de processamento:
#[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 processamento que:
- Extrai automaticamente os valores
left
e right
dos parâmetros de consulta
- Acessa o estado
state
da estrutura
- 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 é especialmente ú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, também foi adicionada uma descrição personalizada para respostas de erro, que será refletida na documentação OpenAPI gerada.
O módulo oapi::extract
do Salvo fornece vários extratores de parâmetros, os mais comuns incluem:
QueryParam<T>
: Extrai parâmetros da string de consulta
PathParam<T>
: Extrai parâmetros do caminho da URL
FormData<T>
: Extrai parâmetros dos dados do formulário
JsonBody<T>
: Extrai parâmetros do corpo da requisição JSON
Esses extratores realizam automaticamente a análise e conversão de tipos, simplificando muito a escrita de funções de processamento.
Integração com OpenAPI
As características do Craft podem gerar automaticamente documentação de API compatível com as especificações 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 para 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 a interface Swagger UI estará acessível no caminho /swagger-ui
.
Exemplo Completo
A seguir, um exemplo completo demonstrando como usar as características do Craft para criar três tipos diferentes de endpoints:
use salvo::oapi::extract::*;
use salvo::prelude::*;
use std::sync::Arc;
#[derive(Clone)]
pub struct Opts {
state: i64,
}
#[craft]
impl Opts {
fn new(state: i64) -> Self {
Self { state }
}
#[craft(handler)]
fn add1(&self, left: QueryParam<i64>, right: QueryParam<i64>) -> String {
(self.state + *left + *right).to_string()
}
#[craft(endpoint)]
pub(crate) fn add2(
self: ::std::sync::Arc<Self>,
left: QueryParam<i64>,
right: QueryParam<i64>,
) -> String {
(self.state + *left + *right).to_string()
}
#[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()
}
}
#[tokio::main]
async fn main() {
// Criar estado compartilhado, valor inicial 1
let opts = Arc::new(Opts::new(1));
// Configurar rotas para os três endpoints
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 para 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"));
// Iniciar servidor em localhost:5800
let acceptor = TcpListener::new("127.0.0.1:5800").bind().await;
Server::new(acceptor).serve(router).await;
}