Swagger/OpenAPI Generation for Rust: Utoipa and Aide
In modern web development with Rust, creating a documented API is not a luxury, but a necessity. Swagger (OpenAPI) allows you to automatically generate interactive documentation, client SDKs, and test consoles. In the Rust ecosystem, two tools hold leading positions: Utoipa (macro-based) and Aide (with an emphasis on typing and manual control). Let's examine them in detail.
Utoipa — Macros for OpenAPI 3.0
Utoipa is a library that uses attribute macros (#[utoipa::path]) to automatically generate OpenAPI specifications. It integrates tightly with popular frameworks: Actix-web, Axum, Rocket, Warp.
Features:
- Full support for OpenAPI 3.0 (paths, components, security schemes).
- Automatic type extraction from Rust structs (using
#[derive(ToSchema)]). - Generation of JSON/YAML files for Swagger UI.
- Built-in support for
actix-web(viautoipa-swagger-ui). - Low entry barrier — just add macros.
Example with Actix-web:
use actix_web::{get, web, App, HttpResponse, HttpServer, Responder};use serde::{Deserialize, Serialize};use utoipa::{OpenApi, ToSchema};use utoipa_swagger_ui::SwaggerUi;
#[derive(Serialize, Deserialize, ToSchema)]struct User { id: u32, name: String,}
#[utoipa::path( get, path = "/users/{id}", responses( (status = 200, description = "User found", body = User), (status = 404, description = "User not found") ), params( ("id" = u32, Path, description = "User ID") ))]#[get("/users/{id}")]async fn get_user(id: web::Path<u32>) -> impl Responder { HttpResponse::Ok().json(User { id: id.into_inner(), name: "Alice".into(), })}
#[derive(OpenApi)]#[openapi(paths(get_user), components(schemas(User)))]struct ApiDoc;
#[actix_web::main]async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() .service(get_user) .service( SwaggerUi::new("/swagger-ui/{_:.*}") .url("/api-doc/openapi.json", ApiDoc::openapi()), ) }) .bind("127.0.0.1:8080")? .run() .await}Aide — Control and Typing
Aide (formerly paperclip) offers a different approach: you explicitly describe the specification through structs and functions, rather than through macros. This gives more control but requires more code. The library supports Axum and Actix-web.
Features:
- Clean typing — all descriptions are defined via enums and structs.
- Built-in schema validation during generation.
- Support for nested models and compositions (allOf, oneOf).
- Flexibility: you can override any aspect of the specification.
Example with Axum:
use aide::{axum::ApiRouter, openapi::OpenApi};use axum::{routing::get, Json, Router};use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]struct Pet { id: u32, name: String,}
async fn list_pets() -> Json<Vec<Pet>> { Json(vec![Pet { id: 1, name: "Rex".into(), }])}
async fn api_docs() -> Json<OpenApi> { let mut api = OpenApi::default(); api.info.title = "Pet Store API".into(); api.info.version = "1.0.0".into(); Json(api)}
#[tokio::main]async fn main() { let app = ApiRouter::new() .route("/pets", get(list_pets)) .route("/api-doc", get(api_docs));
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap()) .serve(app.into_make_service()) .await .unwrap();} Also in library
Swift Task Scheduler: GCD, OperationQueue and Timer on iOS/macOS
Working with XML in Rust: quick-xml, roxmltree, and serde_xml_rs libraries
Geolocation and Maps in TypeScript: Libraries, API, and Code Examples