OpenAPI with Aide

If the open-api feature is enabled, an OpenAPI schema can be built for the app's Axum API by registering API routes using Aide. The schema will then be generated and served at the /api/_docs/api.json route by default, and is also accessible via CLI commands or the HttpService#open_api_schema method.

Register routes

OpenAPI routes are registered with Aide's ApiRouter, which has a similar API to Axum's Router.

const BASE: &str = "/api";

pub fn http_service(state: &AppContext) -> HttpServiceBuilder<AppContext> {
    HttpServiceBuilder::new(Some(BASE), state)
        // Create your routes as an `ApiRouter` in order to include it in the OpenAPI schema.
        .api_router(
            ApiRouter::new()
                // Register a `GET` route on the `ApiRouter`
                .api_route(
                    &build_path(BASE, "/example"),
                    get_with(example_get, example_get_docs),
                ),
        )
}

#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct ExampleResponse {}

#[instrument(skip_all)]
pub async fn example_get(
    State(_state): State<AppContext>,
) -> RoadsterResult<Json<ExampleResponse>> {
    Ok(Json(ExampleResponse {}))
}

pub fn example_get_docs(op: TransformOperation) -> TransformOperation {
    op.description("Example API.")
        .tag("Example")
        .response_with::<200, Json<ExampleResponse>, _>(|res| res.example(ExampleResponse {}))
}

Get schema via API route

By default, the generated schema will be served at /api/_docs/api.json. This route can be configured via the service.http.default-routes.api-schema.route config field.

# First, run your app
cargo run

# In a separate shell or browser, navigate to the API, e.g.
curl localhost:3000/api/_docs/api.json

Get schema via CLI

The schema can also be generated via a CLI command

cargo run -- roadster open-api -o $HOME/open-api.json

Get schema from the HttpService

The schema can also be generated programmatically using the HttpService directly.

type App = RoadsterApp<AppContext>;

async fn open_api() -> RoadsterResult<()> {
    // Build the app
    let app: App = RoadsterApp::builder()
        .state_provider(|context| Ok(context))
        .add_service_provider(move |registry, state| {
            Box::pin(async move {
                registry
                    .register_builder(crate::http::http_service(state))
                    .await?;
                Ok(())
            })
        })
        .build();

    // Prepare the app
    let prepared = prepare(app, PrepareOptions::builder().build()).await?;

    // Get the `HttpService`
    let http_service = prepared.service_registry.get::<HttpService>()?;

    // Get the OpenAPI schema
    let schema = http_service.open_api_schema(&OpenApiArgs::builder().build())?;

    println!("{schema}");

    Ok(())
}