Característica Craft
Craft permite a los desarrolladores generar automáticamente funciones manejadoras y endpoints mediante anotaciones simples, integrando además de forma transparente con la generación de documentación OpenAPI.
Casos de Uso
La característica Craft es especialmente útil en los siguientes escenarios:
- Cuando necesitas crear rápidamente funciones manejadoras de rutas a partir de métodos de estructuras
- Cuando deseas reducir el código repetitivo para la extracción manual de parámetros y manejo de errores
- Cuando necesitas generar automáticamente documentación OpenAPI para tu API
- Cuando quieres desacoplar la lógica de negocio del framework web
Uso Básico
Para utilizar la característica Craft, necesitas importar los siguientes módulos:
use salvo::oapi::extract::*;
use salvo::prelude::*;
Creación de una Estructura de Servicio
Anota tu bloque impl con la macro #[craft] para convertir los métodos de la estructura en funciones manejadoras o endpoints.
#[derive(Clone)]
pub struct Opts {
state: i64,
}
#[craft]
impl Opts {
// Constructor
fn new(state: i64) -> Self {
Self { state }
}
// Más métodos...
}
Creación de Funciones Manejadoras
Usa #[craft(handler)] para convertir un método en una función manejadora:
#[craft(handler)]
fn add1(&self, left: QueryParam<i64>, right: QueryParam<i64>) -> String {
(self.state + *left + *right).to_string()
}
Este método se convertirá en una función manejadora que:
- Extrae automáticamente los valores
left y right de los parámetros de consulta
- Accede al
state de la estructura
- Devuelve el resultado del cálculo como una respuesta de cadena
Creación de Endpoints
Usa #[craft(endpoint)] para convertir un método en un 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()
}
Los endpoints pueden utilizar Arc para compartir estado, lo cual es especialmente útil al manejar solicitudes concurrentes.
Endpoints Estáticos
También puedes crear endpoints estáticos que no dependen del estado de la instancia:
#[craft(endpoint(responses((status_code = 400, description = "Parámetros de solicitud incorrectos."))))]
pub fn add3(left: QueryParam<i64>, right: QueryParam<i64>) -> String {
(*left + *right).to_string()
}
En este ejemplo, se añade una descripción personalizada de respuesta de error, que se reflejará en la documentación OpenAPI generada.
El módulo oapi::extract de Salvo proporciona varios extractores de parámetros, siendo los más comunes:
QueryParam<T>: Extrae parámetros de cadenas de consulta
PathParam<T>: Extrae parámetros de rutas URL
FormData<T>: Extrae parámetros de datos de formulario
JsonBody<T>: Extrae parámetros de cuerpos de solicitud JSON
Estos extractores manejan automáticamente el análisis de parámetros y la conversión de tipos, simplificando enormemente la escritura de funciones manejadoras.
Integración con OpenAPI
La característica Craft puede generar automáticamente documentación de API conforme a la especificación OpenAPI. En el ejemplo:
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()));
// Generar documentación OpenAPI
let doc = OpenApi::new("API de Ejemplo", "0.0.1").merge_router(&router);
// Añadir rutas de documentación OpenAPI y Swagger UI
let router = router
.push(doc.into_router("/api-doc/openapi.json"))
.push(SwaggerUi::new("/api-doc/openapi.json").into_router("swagger-ui"));
Con esta configuración, la documentación de la API estará disponible en el endpoint /api-doc/openapi.json, y Swagger UI será accesible en la ruta /swagger-ui.
Ejemplo Completo
A continuación se muestra un ejemplo completo que demuestra cómo usar la característica Craft para crear tres 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