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.

Extracteurs de paramètres

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 :

main.rs
Cargo.toml
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;
}