Craft-Funktionen

Craft ermöglicht Entwicklern die automatische Generierung von Handler-Funktionen und Endpunkten durch einfache Annotationen, während eine nahtlose Integration mit der OpenAPI-Dokumentationsgenerierung gewährleistet wird.

Anwendungsfälle

Die Craft-Funktionen sind besonders nützlich in folgenden Szenarien:

  • Wenn Sie schnell Routen-Handler aus Strukturmethoden erstellen müssen
  • Wenn Sie den manuellen Aufwand für 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-Funktionen zu nutzen, müssen folgende Module importiert werden:

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

Erstellen einer Service-Struktur

Mit dem #[craft]-Makro können Sie Ihren impl-Block annotieren, um Strukturmethoden 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 einer Handler-Funktion

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 die Werte left und right aus den Query-Parametern extrahiert
  • Auf den state-Zustand in der Struktur zugreift
  • Das Berechnungsergebnis als String-Antwort zurückgibt

Erstellen eines Endpunkts

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 nutzen, um Zustände zu teilen, was besonders bei der Verarbeitung gleichzeitiger Anfragen hilfreich ist.

Statische Endpunkte

Sie können auch statische Endpunkte erstellen, die nicht vom Instanzzustand 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 wurde zusätzlich eine benutzerdefinierte Fehlerantwort hinzugefügt, die in der generierten OpenAPI-Dokumentation erscheint.

Parameter-Extraktoren

Das oapi::extract-Modul von Salvo bietet verschiedene Parameter-Extraktoren, die gängigsten sind:

  • QueryParam<T>: Extrahiert Parameter aus der Query-Zeichenkette
  • PathParam<T>: Extrahiert Parameter aus dem URL-Pfad
  • FormData<T>: Extrahiert Parameter aus Formulardaten
  • JsonBody<T>: Extrahiert Parameter aus dem JSON-Request-Body

Diese Extraktoren übernehmen automatisch die Parameteranalyse und Typumwandlung, was das Schreiben von Handler-Funktionen erheblich vereinfacht.

Integration mit OpenAPI

Die Craft-Funktionen können automatisch OpenAPI-konforme API-Dokumentation generieren. 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("Example 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 wird die API-Dokumentation unter dem Endpunkt /api-doc/openapi.json bereitgestellt, und die Swagger-UI ist unter /swagger-ui verfügbar.

Vollständiges Beispiel

Hier ist ein vollständiges Beispiel, das zeigt, wie Craft-Funktionen verwendet werden, um drei verschiedene Arten von Endpunkten zu erstellen:

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

#[tokio::main]
async fn main() {
    // Gemeinsamen Zustand erstellen, Startwert ist 1
    let opts = Arc::new(Opts::new(1));

    // Routen für drei Endpunkte konfigurieren
    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("Example 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"));

    // Server auf localhost:5800 starten
    let acceptor = TcpListener::new("127.0.0.1:5800").bind().await;
    Server::new(acceptor).serve(router).await;
}