Funzionalità Craft
Craft consente agli sviluppatori di generare automaticamente funzioni handler ed endpoint tramite semplici annotazioni, integrandosi perfettamente con la generazione di documentazione OpenAPI.
Casi d'uso
La funzionalità Craft risulta particolarmente utile nei seguenti scenari:
- Quando è necessario creare rapidamente funzioni handler per le rotte a partire da metodi di struct
- Quando si desidera ridurre il codice boilerplate per l'estrazione manuale dei parametri e la gestione degli errori
- Quando occorre generare automaticamente documentazione OpenAPI per le proprie API
- Quando si vuole disaccoppiare la logica di business dal framework web
Utilizzo di base
Per utilizzare la funzionalità Craft, è necessario importare i seguenti moduli:
use salvo::oapi::extract::*;
use salvo::prelude::*;
Creazione di una Struct di Servizio
Annotare il blocco impl con la macro #[craft] per convertire i metodi della struct in funzioni handler o endpoint.
#[derive(Clone)]
pub struct Opts {
state: i64,
}
#[craft]
impl Opts {
// Costruttore
fn new(state: i64) -> Self {
Self { state }
}
// Altri metodi...
}
Creazione di Funzioni Handler
Utilizzare #[craft(handler)] per convertire un metodo in una funzione handler:
#[craft(handler)]
fn add1(&self, left: QueryParam<i64>, right: QueryParam<i64>) -> String {
(self.state + *left + *right).to_string()
}
Questo metodo diventerà una funzione handler che:
- Estrae automaticamente i valori
left e right dai parametri di query
- Accede allo
state dalla struct
- Restituisce il risultato del calcolo come risposta stringa
Creazione di Endpoint
Utilizzare #[craft(endpoint)] per convertire un metodo in 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()
}
Gli endpoint possono utilizzare Arc per condividere lo stato, particolarmente utile nella gestione di richieste concorrenti.
Endpoint Statici
È possibile creare anche endpoint statici che non dipendono dallo stato dell'istanza:
#[craft(endpoint(responses((status_code = 400, description = "Parametri della richiesta errati."))))]
pub fn add3(left: QueryParam<i64>, right: QueryParam<i64>) -> String {
(*left + *right).to_string()
}
In questo esempio viene aggiunta una descrizione personalizzata per la risposta di errore, che verrà riflessa nella documentazione OpenAPI generata.
Estrattori di Parametri
Il modulo oapi::extract di Salvo fornisce vari estrattori di parametri, tra i più comuni:
QueryParam<T>: Estrae parametri dalle query string
PathParam<T>: Estrae parametri dai percorsi URL
FormData<T>: Estrae parametri dai dati di form
JsonBody<T>: Estrae parametri dal corpo JSON delle richieste
Questi estrattori gestiscono automaticamente il parsing dei parametri e la conversione dei tipi, semplificando notevolmente la scrittura delle funzioni handler.
Integrazione con OpenAPI
La funzionalità Craft può generare automaticamente documentazione API conforme alla specifica OpenAPI. Nell'esempio:
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()));
// Generazione documentazione OpenAPI
let doc = OpenApi::new("API di Esempio", "0.0.1").merge_router(&router);
// Aggiunta rotte per documentazione 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"));
Con questa configurazione, la documentazione API sarà disponibile all'endpoint /api-doc/openapi.json, mentre Swagger UI sarà accessibile al percorso /swagger-ui.
Esempio Completo
Di seguito un esempio completo che dimostra come utilizzare la funzionalità Craft per creare tre tipi diversi di endpoint:
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