Alle unsere Services benötigen eine Swagger UI, um dem Benutzer die aktuelle OpenAPI-Datei zu präsentieren. Da unser Backend vollständig mit Rust gebaut ist, wird das Hosting dieser Swagger UI ebenfalls durch einen Rust-Prozess realisiert.
Um die Swagger UI im Rust-Ökosystem zu einem First-Class Citizen zu machen, haben wir beschlossen, die Swagger-UI als Rust-Modul neu zu verpacken. So kann dependabot Updates des zugrunde liegenden Projekts erfassen und diese Abhängigkeiten in unsere Projekte ziehen.
Dies hat zudem den Vorteil, dass bei jedem Update dieser Abhängigkeit unsere CI/CD-Pipeline ausgeführt wird, um zu prüfen, ob die UI für uns noch funktioniert.
Die Swagger UI wird über GitHub gebaut und verpackt. Die Ermittlung der aktuellen Version erfolgt also über einen weiteren Aufruf der 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 stets mit der GitHub-Version überein.
Der gesamte für diesen Vorgang erforderliche Code befindet sich in der GitHub Action Definition.
Die Nutzung dieses Crates ist recht einfach. Die statischen Ressourcen werden als Axum-Routen bereitgestellt und können mit einer bestehenden Routen-Definition zusammengeführt werden. Um diese Routen zu implementieren, muss ein Präfix für die Routen sowie eine API-Definition angegeben werden. 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/