Das Swagger UI als Rust-Modul neu verpacken

created: Dienstag, Mai 21, 2024

Alle unsere Services benötigen eine Swagger UI, um dem Benutzer die aktuelle OpenAPI-Datei zu präsentieren. Da unser Backend komplett mit Rust gebaut ist, wird das Hosting dieser Swagger UI ebenfalls durch einen Rust-Prozess durchgeführt.

Um die Swagger UI also zu einem erstklassigen Bestandteil des Rust-Ökosystems zu machen, haben wir beschlossen, die Swagger-UI als Rust-Modul neu zu verpacken. So kann dependabot Updates für das zugrundeliegende Projekt erkennen und diese Abhängigkeiten in unsere Projekte einpflegen.

Dies hat außerdem den Vorteil, dass immer wenn diese Abhängigkeit aktualisiert wird, wir unsere CI/CD-Pipeline ausführen, um zu prüfen, ob die UI für uns weiterhin funktioniert.

Wie es gebaut wird

Die Swagger UI wird über github verpackt. Die aktuelle Version zu bestimmen ist also einfach ein weiterer Aufruf gegen die Github API. So können wir entscheiden, ob ein Update notwendig ist oder nicht. Wenn eine neue Version veröffentlicht wurde, laden wir die minifizierten JS- und CSS-Dateien herunter und aktualisieren unser lokales Rust-Repo mit den neuen Abhängigkeiten.

Nachdem diese Dateien aktualisiert wurden, erhöhen wir auch die Modulversion auf dieselbe Version wie das Github-Release. So stimmt die Cargo-Version immer mit der Github-Version überein.

Der gesamte für dieses Verfahren benötigte Code befindet sich in der Definition der Github Action.

Wie man es verwendet

Die Verwendung dieses Crates ist recht einfach. Die statischen Ressourcen werden als axum-Routen bereitgestellt und können mit einer bestehenden Routendefinition zusammengeführt werden. Um diese Routen zu implementieren, muss man einen Präfix für die Routen sowie eine API-Definition angeben. Die API-Definition kann entweder eine Inline-YAML-Datei oder ein externer Link zur API-Definition sein.

Hier ein Beispiel, wie das Crate mit einer Inline-OpenAPI-Definition verwendet wird.

use axum::Router;
use swagger_ui_dist::{ApiDefinition, OpenApiSource};

#[tokio::main]
async fn main() {
    let api_def = ApiDefinition {
        uri_prefix: "/api",
        api_definition: OpenApiSource::Inline(include_str!("petstore.yaml")),
        title: Some("My Super Duper API"),
    };
    let app = Router::new().merge(swagger_ui_dist::generate_routes(api_def));
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    println!("listening on http://localhost:3000/api");
    axum::serve(listener, app).await.unwrap();
}

crate.io Link:
https://crates.io/crates/swagger-ui-dist

github Repo:
https://github.com/apimeister/swagger-ui-dist-rs/