Fonctionnalité Craft

Craft permet aux développeurs de générer automatiquement des fonctions de traitement 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 situations suivantes :

  • Lorsque vous avez besoin de créer rapidement des fonctions de gestion de routes à partir de méthodes de structure
  • Lorsque vous souhaitez réduire le code passe-partout lié à l'extraction de paramètres et à la gestion d'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 traitement ou en points de terminaison.

#[derive(Clone)]
pub struct Opts {
    state: i64,
}

#[craft]
impl Opts {
    // Constructeur
    fn new(state: i64) -> Self {
        Self { state }
    }
    
    // Plus de méthodes...
}

Création d'une fonction de traitement

Utilisez #[craft(handler)] pour convertir une méthode en fonction de traitement :

#[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 traitement qui :

  • Extrait automatiquement les valeurs left et right des paramètres de requête
  • Accède à l'état state dans la structure
  • Retourne le résultat du calcul sous forme de chaîne de réponse

Création d'un point 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 pour traiter des requêtes concurrentes.

Point de terminaison statique

Vous pouvez également créer des points de terminaison statiques qui ne dépendent pas de l'état d'une 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 personnalisée de réponse d'erreur 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 plusieurs extracteurs de paramètres, les plus courants étant :

  • QueryParam<T> : Extrait un paramètre de la chaîne de requête
  • PathParam<T> : Extrait un paramètre du chemin URL
  • FormData<T> : Extrait un paramètre des données de formulaire
  • JsonBody<T> : Extrait un paramètre du corps de requête JSON

Ces extracteurs effectuent automatiquement l'analyse et la conversion des paramètres, simplifiant grandement l'écriture des fonctions de traitement.

Intégration avec OpenAPI

La fonctionnalité Craft peut générer automatiquement une documentation d'API conforme aux spécifications 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ère la documentation OpenAPI
let doc = OpenApi::new("Example API", "0.0.1").merge_router(&router);

// Ajoute les routes pour la documentation OpenAPI et l'interface 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 de l'API sera disponible à l'adresse /api-doc/openapi.json, et l'interface Swagger UI sera accessible via le chemin /swagger-ui.

Exemple complet

Voici un exemple complet illustrant l'utilisation de 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;

#[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 = "Wrong request parameters."))))]
    pub fn add3(left: QueryParam<i64>, right: QueryParam<i64>) -> String {
        (*left + *right).to_string()
    }
}

#[tokio::main]
async fn main() {
    // Crée un état partagé avec une valeur initiale de 1
    let opts = Arc::new(Opts::new(1));

    // Configure les routes pour les trois points de terminaison
    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ère la documentation OpenAPI
    let doc = OpenApi::new("Example API", "0.0.1").merge_router(&router);
    
    // Ajoute les routes pour la documentation OpenAPI et l'interface Swagger UI
    let router = router
        .push(doc.into_router("/api-doc/openapi.json"))
        .push(SwaggerUi::new("/api-doc/openapi.json").into_router("swagger-ui"));

    // Démarre le serveur sur localhost:5800
    let acceptor = TcpListener::new("127.0.0.1:5800").bind().await;
    Server::new(acceptor).serve(router).await;
}