Craft-Funktion

Craft ermöglicht Entwicklern die automatische Generierung von Handler-Funktionen und Endpunkten durch einfache Annotationen, während es nahtlos mit der OpenAPI-Dokumentationsgenerierung integriert wird.

Anwendungsfälle

Die Craft-Funktion ist besonders nützlich in folgenden Szenarien:

  • Wenn Sie schnell Routen-Handler-Funktionen aus Struct-Methoden erstellen müssen
  • Wenn Sie Boilerplate-Code für manuelle Parameter-Extraktion und Fehlerbehandlung reduzieren möchten
  • Wenn Sie automatisch OpenAPI-Dokumentation für Ihre API generieren müssen
  • Wenn Sie Geschäftslogik vom Web-Framework entkoppeln möchten

Grundlegende Verwendung

Um die Craft-Funktion zu verwenden, müssen Sie folgende Module importieren:

use salvo::oapi::extract::*;
use salvo::prelude::*;

Erstellen eines Service-Structs

Versehen Sie Ihren impl-Block mit der #[craft]-Makro-Annotation, um Struct-Methoden in Handler-Funktionen oder Endpunkte umzuwandeln.

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

#[craft]
impl Opts {
    // Konstruktor
    fn new(state: i64) -> Self {
        Self { state }
    }
    
    // Weitere Methoden...
}

Erstellen von Handler-Funktionen

Verwenden Sie #[craft(handler)], um eine Methode in eine Handler-Funktion umzuwandeln:

#[craft(handler)]
fn add1(&self, left: QueryParam<i64>, right: QueryParam<i64>) -> String {
    (self.state + *left + *right).to_string()
}

Diese Methode wird zu einer Handler-Funktion, die:

  • Automatisch left- und right-Werte aus Query-Parametern extrahiert
  • Auf state vom Struct zugreift
  • Das Berechnungsergebnis als String-Antwort zurückgibt

Erstellen von Endpunkten

Verwenden Sie #[craft(endpoint)], um eine Methode in einen Endpunkt umzuwandeln:

#[craft(endpoint)]
pub(crate) fn add2(
    self: ::std::sync::Arc<Self>,
    left: QueryParam<i64>,
    right: QueryParam<i64>,
) -> String {
    (self.state + *left + *right).to_string()
}

Endpunkte können Arc zur Statusfreigabe nutzen, was besonders bei der Verarbeitung gleichzeitiger Anfragen nützlich ist.

Statische Endpunkte

Sie können auch statische Endpunkte erstellen, die nicht vom Instanzstatus abhängen:

#[craft(endpoint(responses((status_code = 400, description = "Falsche Anfrageparameter."))))]
pub fn add3(left: QueryParam<i64>, right: QueryParam<i64>) -> String {
    (*left + *right).to_string()
}

In diesem Beispiel wird eine benutzerdefinierte Fehlerantwortbeschreibung hinzugefügt, die in der generierten OpenAPI-Dokumentation widergespiegelt wird.

Parameter-Extraktoren

Das oapi::extract-Modul von Salvo bietet verschiedene Parameter-Extraktoren, wobei die häufigsten sind:

  • QueryParam<T>: Extrahiert Parameter aus Query-Strings
  • PathParam<T>: Extrahiert Parameter aus URL-Pfaden
  • FormData<T>: Extrahiert Parameter aus Formulardaten
  • JsonBody<T>: Extrahiert Parameter aus JSON-Anfragekörpern

Diese Extraktoren behandeln automatisch Parameter-Parsing und Typkonvertierung, was das Schreiben von Handler-Funktionen erheblich vereinfacht.

Integration mit OpenAPI

Die Craft-Funktion kann automatisch API-Dokumentation generieren, die der OpenAPI-Spezifikation entspricht. Im Beispiel:

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()));

// OpenAPI-Dokumentation generieren
let doc = OpenApi::new("Beispiel-API", "0.0.1").merge_router(&router);

// OpenAPI-Dokumentation und Swagger-UI-Routen hinzufügen
let router = router
    .push(doc.into_router("/api-doc/openapi.json"))
    .push(SwaggerUi::new("/api-doc/openapi.json").into_router("swagger-ui"));

Mit dieser Konfiguration ist die API-Dokumentation unter dem Endpunkt /api-doc/openapi.json verfügbar und Swagger UI unter dem Pfad /swagger-ui erreichbar.

Vollständiges Beispiel

Nachfolgend ein vollständiges Beispiel, das zeigt, wie die Craft-Funktion verwendet wird, um drei verschiedene Typen von Endpunkten zu erstellen:

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;
}