oasgen
is a library to generate OpenAPI 3.0 specs from Rust server code (or any async functions). It supports:
actix
- actix-webaxum
- axum- No framework - if you just want to register Rust functions to generate an OpenAPI spec file.
Contributions to support other web frameworks are welcome!
// Actix-web example
use actix_web::web::Json;
use actix_web::{App, HttpServer};
use oasgen::{oasgen, OaSchema, Server};
use serde::{Deserialize, Serialize};
#[derive(OaSchema, Deserialize)]
pub struct SendCode {
pub mobile: String,
}
#[derive(Serialize, OaSchema, Debug)]
pub struct SendCodeResponse {
pub found_account: bool,
}
#[oasgen]
async fn send_code(_body: Json<SendCode>) -> Json<SendCodeResponse> {
Json(SendCodeResponse {
found_account: false,
})
}
#[tokio::main]
async fn main() {
let server = Server::actix().post("/send-code", send_code).freeze();
HttpServer::new(move || App::new().service(server.clone().into_service()))
.bind(("127.0.0.1", 5000))
.unwrap()
.run()
.await
.unwrap()
}
// axum example
use oasgen::{OaSchema, Server, oasgen};
use axum::{Json, routing};
use serde::{Deserialize, Serialize};
#[derive(OaSchema, Deserialize)]
pub struct SendCode {
pub mobile: String,
}
#[derive(Serialize, OaSchema, Debug)]
pub struct SendCodeResponse {
pub found_account: bool,
}
#[oasgen]
async fn send_code(_body: Json<SendCode>) -> Json<SendCodeResponse> {
Json(SendCodeResponse { found_account: false })
}
#[tokio::main]
async fn main() {
let server = Server::axum()
.post("/send-code", send_code)
.freeze();
let router = axum::Router::new()
.route("/healthcheck", routing::get(|| async { "OK" }))
.merge(server.into_router());
axum::Server::bind(&"0.0.0.0:5000".parse().unwrap())
.serve(router.into_make_service())
.await
.unwrap();
}
To compile the axum example, use the following dependencies:
[dependencies]
axum = ".."
oasgen = { version = "..", features = ["axum"] }
serde = { version = "..", features = ["derive"] }
tokio = { version = "..", features = ["full"] }
[dependencies]
# At minimum, you probably want a server feature installed (axum, actix) to support that framework
oasgen = { version = "..", features = []}
There are several features for activating other libraries:
actix
- actix-webaxum
- axumswagger-ui
- swagger uiuuid
- uuidchrono
- chronotime
- timesqlx
- sqlx
You can customize the generated spec in many ways.
You have direct access to the OpenAPI struct, so you can customize it however you want.
let mut server = Server::new();
server.openapi.info.title = "My API".to_string();
server.openapi.components.schemas.insert("MySchema".to_string(), Schema::new_object());
server
.get("/my-route", my_handler)
.freeze();
Note that you must make any changes before calling .freeze()
(which moves the OpenAPI struct into an Arc to be shared between threads).
You can hand-write an implementation of OaSchema instead of using derive to customize any Schema. If you do this, call
register_schema
after the struct definition to add it to the spec.
use oasgen::{OaSchema, Schema, register_schema};
pub struct User {
pub id: i32,
pub name: String,
}
impl OaSchema for User {
fn schema() -> Schema {
let mut schema = Schema::new_object();
schema.properties_mut().insert("id", Schema::new_integer());
schema.properties_mut().insert("name", Schema::new_string());
schema
}
}
register_schema!("User", &|| User::schema());
Technically speaking, you don't need to implement OaSchema at all.
You can pass any arbitrary closure that returns a Schema
to the register_schema macro.
You can also customize an operation:
async fn my_server_handler() {
// ...
}
// You must use the fully qualified path to the function.
// You can simplify this slightly by passing in `concat!(module_path!(), "::my_server_handler")`
register_operation!("my_server_crate::path::to::my_server_handler", &|| {
let mut operation = Operation::default();
operation.summary = Some("My summary".to_string());
// ...
operation
});
oasgen
defines its own attributes, and also respects serde
attributes. It also uses docstrings as descriptions.
You can see all attributes in macro/src/attr.rs
. Look at those structs for relevant documentation, and see the examples below.
#[derive(OaSchema)]
pub struct User {
pub id: i32,
pub name: String,
// Because oasgen respects serde attributes, this will not appear in the spec.
#[serde(skip)]
pub password_hash: String,
// This will be in the response (because there's no serde(skip), but it will not show up in the OpenAPI spec.
#[oasgen(skip)]
pub internal_id: i32,
}
#[oasgen(
tags("auth", "users"),
summary = "This is a short summary"),
deprecated = true,
operation_id = "my_operation_id",
description = "This is a long description and will override the docstring of the function",
)]
async fn my_server_handler() {
// ...
}
You have direct access to the OpenAPI
struct. You can use serde
to write it to a file, stdout, and more.
We provide a helper function write_and_exit_if_env_var_set
that integrates well with a basic build process:
let server = Server::new()
// your routes
.write_and_exit_if_env_var_set("./openapi.yaml")
// .freeze() here, if you're mounting to a server.
If OASGEN_WRITE_SPEC=1
, it will write the spec to the path, then exit.
In your build process, build the executable, run it once with the env var set to output the spec, then run it again without the env var to start the server normally.
Note
Requires the swagger-ui
feature
There are built-in functions to create routes that display the raw spec, or display a Swagger UI page for the spec.
let mut server = oasgen::Server::axum()
.post("/auth/register_password", auth::register_password) // example route
.route_yaml_spec("/openapi.yaml") // the spec will be available at /openapi.yaml
.route_json_spec("/openapi.json") // the spec will be available at /openapi.json
.swagger_ui("/openapi/"); // the swagger UI will be available at /openapi/.
// NOTE: The trailing slash is required, as is calling either `route_yaml_spec()` or `route_json_spec()` before `swagger_ui()`.
If you need to customize these routes, you have directly use a clone of the OpenAPI struct. It's in an Arc, so it's cheap to clone.
let mut server = oasgen::Server::axum()
.post("/auth/register_password", auth::register_password) // example route
.freeze();
let spec = server.openapi.clone();
let router = axum::Router::new()
.merge(server.into_router())
.route("/alt/route/to/openapi.yaml", get(|| {
let spec = spec.clone();
async {
serde_yaml::to_string(spec).unwrap()
}
}))
;