oasgen

repository·master·Indexed 42 days ago

https://github.com/kurtbuilds/oasgen

A Rust library for automatically generating OpenAPI 3.0 specifications from server code or async functions. It integrates with frameworks such as Axum and Actix-web using attributes and macros to keep API documentation in sync with implementation. Features include support for Swagger UI, custom schema implementation via the OaSchema trait, and the ability to export specifications in JSON and YAML formats.

Tokens
9.8K
Snippets
40
Records
41
Agent score
52%

What's inside oasgen

  1. Write the OpenAPI spec to a file via environment variable

    master

    To automate spec generation in a build process, use the write_and_exit_if_env_var_set(path) method.

    If the environment variable OASGEN_WRITE_SPEC=1 is set, the library will write the spec to the provided path and then exit the process. This allows you to generate the file during a build step before running the actual server.

    let server = Server::new()
        // your routes
        .write_and_exit_if_env_var_set("./openapi.yaml")
        // .freeze() here, if you're mounting to a server.
  2. Install oasgen

    master

    To use oasgen, add it to your Cargo.toml. You should enable features for the web framework you are using (e.g., axum or actix) to allow the generator to integrate with your server routes.

    Available features:

    • actix: support for actix-web
    • axum: support for axum
    • swagger-ui: support for Swagger UI routes
    • uuid: support for UUID types
    • chrono: support for Chrono types
    • time: support for Time types
    • sqlx: support for SQLx types
    [dependencies]
    oasgen = { version = "..", features = ["axum"] }
  3. Automatic OaParameter support for Result types

    master

    The OaParameter trait is automatically implemented for Result<T, E> where T implements OaParameter. This allows you to define parameter logic directly on your success types, and the implementation will propagate the schema and parameter definitions through the Result wrapper.

    // If T implements OaParameter, Result<T, E> also implements it automatically
    impl<T, E> OaParameter for Result<T, E>
    where
        T: OaParameter,
    {
        fn body_schema() -> Option<RefOr<Schema>> {
            T::body_schema()
        }
        // ...
    }
  4. Integrate oasgen with Axum

    master

    To use oasgen with the Axum web framework, use the axum() method on the Server struct. This initializes a server instance configured with an Axum Router. You can then register HTTP routes which will simultaneously be added to your Axum router and your OpenAPI specification.

    // Assuming Server and OpenAPI are in scope
    let server = Server::axum()
        .get("/hello", hello_handler)
        .post("/data", post_handler);
    
    let app = server.into_router();
  5. Integrate oasgen with Actix-web

    master

    To use oasgen with an Actix-web server, initialize a server using Server::actix(), register your routes using HTTP method helpers, and then convert the server into an Actix Scope using .into_service(). This Scope can be registered directly within your Actix App.

    // Example of initializing and integrating with Actix
    let server = Server::actix()
        .get("/hello", hello_handler)
        .post("/data", data_handler)
        .route_json_spec("/openapi.json")
        .freeze();
    
    App::new()
        .service(server.into_service())
  6. Use oasgen with Axum

    master

    To integrate oasgen with an axum server, use Server::axum(). You define routes using methods like .post(), .get(), etc., and then call .freeze() to convert the server into an axum::Router via .into_router().

    Note: You must use the #[oasgen] attribute on your handler functions to ensure they are registered in the OpenAPI spec.

    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();
    }
  7. Use oasgen with Actix-web

    master

    To integrate oasgen with an actix-web server, use Server::actix(). After defining routes, call .freeze() to get a service that can be used within an actix_web::App via .service(server.clone().into_service()).

    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()
    }
  8. Manually implement or register a Schema

    master

    If #[derive(OaSchema)] is insufficient, you can manually implement the OaSchema trait for a type. Alternatively, you can use the register_schema! macro to pass a closure that returns a Schema object.

    To ensure the schema is included in the generated spec, you must call register_schema! after the struct definition.

    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());
  9. Expose the OpenAPI spec and Swagger UI via routes

    master

    If you have the swagger-ui feature enabled, you can add built-in routes to your server to serve the spec or the Swagger UI.

    Requirements for Swagger UI:

    1. You must call route_yaml_spec() or route_json_spec() before calling swagger_ui().
    2. The swagger_ui() path must have a trailing slash (e.g., "/openapi/").

    Available methods:

    • route_yaml_spec(path): Serves the YAML spec at the given path.
    • route_json_spec(path): Serves the JSON spec at the given path.
    • swagger_ui(path): Serves the Swagger UI at the given path.
    let mut server = oasgen::Server::axum()
        .post("/auth/register_password", auth::register_password)
        .route_yaml_spec("/openapi.yaml")
        .route_json_spec("/openapi.json")
        .swagger_ui("/openapi/");
  10. Customize the OpenAPI struct directly

    master

    You can modify the underlying OpenAPI struct before calling .freeze(). This allows you to set the API title or manually insert schemas into the components section.

    Important: All modifications must be made before calling .freeze(), as .freeze() moves the struct into an Arc for thread-safe sharing.

    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();
  11. Use oasgen attributes on handlers

    master

    Use the #[oasgen(...)] attribute on async functions to customize how they appear in the OpenAPI spec. Supported options include:

    • tags(...): A list of tags for the operation.
    • summary = "...": A short summary.
    • description = "...": A long description (overrides the function's docstring).
    • deprecated = true: Marks the operation as deprecated.
    • operation_id = "...": Sets a specific operation ID.

    oasgen also respects #[serde(skip)] to exclude fields from the spec, and provides #[oasgen(skip)] to exclude fields from the OpenAPI spec while keeping them in the actual JSON response.

    #[derive(OaSchema)]
    pub struct User {
        pub id: i32,
        pub name: String,
        #[serde(skip)]
        pub password_hash: String,
        #[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() {
        // ...
    }
  12. Manually customize an Operation

    master

    You can override operation details (like summary, description, etc.) using the register_operation! macro. This requires the fully qualified path to the handler function. You can use concat!(module_path!(), "::my_handler") to simplify the path string.

    // You must use the fully qualified path to the function.
    register_operation!("my_server_crate::path::to::my_server_handler", &|| {
        let mut operation = Operation::default();
        operation.summary = Some("My summary".to_string());
        // ...
        operation
    });