Fonctionnalité Craft
Craft permet aux développeurs de générer automatiquement des fonctions de gestionnaire et des points de terminaison grâce à de simples annotations, tout en s'intégrant parfaitement à la génération de documentation OpenAPI.
Cas d'utilisation
La fonctionnalité Craft est particulièrement utile dans les scénarios suivants :
- Lorsque vous devez créer rapidement des fonctions de gestionnaire de route à partir de méthodes de structure
- Lorsque vous souhaitez réduire le code passe-partout pour l'extraction manuelle des paramètres et la gestion des erreurs
- Lorsque vous devez générer automatiquement une documentation OpenAPI pour votre API
- Lorsque vous voulez découpler la logique métier du framework web
Utilisation de base
Pour utiliser la fonctionnalité Craft, vous devez importer les modules suivants :
use salvo::oapi::extract::*;
use salvo::prelude::*;
Création d'une structure de service
Annotez votre bloc impl avec la macro #[craft] pour convertir les méthodes de structure en fonctions de gestionnaire ou en points de terminaison.
#[derive(Clone)]
pub struct Opts {
state: i64,
}
#[craft]
impl Opts {
// Constructeur
fn new(state: i64) -> Self {
Self { state }
}
// Autres méthodes...
}
Création de fonctions de gestionnaire
Utilisez #[craft(handler)] pour convertir une méthode en fonction de gestionnaire :
#[craft(handler)]
fn add1(&self, left: QueryParam<i64>, right: QueryParam<i64>) -> String {
(self.state + *left + *right).to_string()
}
Cette méthode deviendra une fonction de gestionnaire qui :
- Extrait automatiquement les valeurs
left et right des paramètres de requête
- Accède à
state depuis la structure
- Retourne le résultat du calcul sous forme de réponse chaîne de caractères
Création de points de terminaison
Utilisez #[craft(endpoint)] pour convertir une méthode en point de terminaison :
#[craft(endpoint)]
pub(crate) fn add2(
self: ::std::sync::Arc<Self>,
left: QueryParam<i64>,
right: QueryParam<i64>,
) -> String {
(self.state + *left + *right).to_string()
}
Les points de terminaison peuvent utiliser Arc pour partager l'état, ce qui est particulièrement utile lors de la gestion de requêtes concurrentes.
Points de terminaison statiques
Vous pouvez également créer des points de terminaison statiques qui ne dépendent pas de l'état de l'instance :
#[craft(endpoint(responses((status_code = 400, description = "Wrong request parameters."))))]
pub fn add3(left: QueryParam<i64>, right: QueryParam<i64>) -> String {
(*left + *right).to_string()
}
Dans cet exemple, une description d'erreur personnalisée est ajoutée, qui sera reflétée dans la documentation OpenAPI générée.
Le module oapi::extract de Salvo fournit divers extracteurs de paramètres, les plus courants incluant :
QueryParam<T> : Extrait les paramètres des chaînes de requête
PathParam<T> : Extrait les paramètres des chemins d'URL
FormData<T> : Extrait les paramètres des données de formulaire
JsonBody<T> : Extrait les paramètres des corps de requête JSON
Ces extracteurs gèrent automatiquement l'analyse des paramètres et la conversion de type, simplifiant grandement l'écriture des fonctions de gestionnaire.
Intégration avec OpenAPI
La fonctionnalité Craft peut générer automatiquement une documentation API conforme à la spécification OpenAPI. Dans l'exemple :
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()));
// Générer la documentation OpenAPI
let doc = OpenApi::new("Example API", "0.0.1").merge_router(&router);
// Ajouter les routes de documentation OpenAPI et Swagger UI
let router = router
.push(doc.into_router("/api-doc/openapi.json"))
.push(SwaggerUi::new("/api-doc/openapi.json").into_router("swagger-ui"));
Avec cette configuration, la documentation API sera disponible au point de terminaison /api-doc/openapi.json, et Swagger UI sera accessible au chemin /swagger-ui.
Exemple complet
Voici un exemple complet démontrant comment utiliser la fonctionnalité Craft pour créer trois types différents de points de terminaison :
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