OpenAPI & Swagger UI Guide

Generate OpenAPI 3.0 specifications and serve interactive API documentation with Swagger UI.

Table of Contents

Overview

The armature-openapi module provides tools for generating OpenAPI 3.0 specifications and serving interactive API documentation. It allows you to document your APIs programmatically and serve beautiful, interactive documentation via Swagger UI.

Why OpenAPI?

  • Industry Standard: OpenAPI (formerly Swagger) is the de facto standard for REST API documentation
  • Interactive: Test your API directly from the documentation
  • Code Generation: Generate client SDKs in multiple languages
  • Validation: Validate requests/responses against the specification
  • Discoverability: Make your API easy to understand and use

Features

โœ… Programmatic Builder

  • Fluent API for building OpenAPI specs
  • Type-safe specification construction
  • Helper functions for common patterns

โœ… Swagger UI Integration

  • Beautiful, interactive API documentation
  • Test endpoints directly from the browser
  • No configuration required

โœ… Multiple Export Formats

  • JSON export
  • YAML export
  • HTML (Swagger UI)

โœ… Full OpenAPI 3.0 Support

  • All HTTP methods (GET, POST, PUT, DELETE, PATCH)
  • Request/response schemas
  • Authentication (Bearer, API Key, OAuth2)
  • Tags and organization

Quick Start

Installation

Add to your Cargo.toml:

[dependencies]
armature-framework = { version = "0.1", features = ["openapi"] }

Basic Example

use armature_framework::prelude::*;
use armature_framework::armature_openapi::*;

// Build OpenAPI specification
let spec = OpenApiBuilder::new("My API", "1.0.0")
    .description("A wonderful API")
    .server("http://localhost:3000", None)
    .tag("users", Some("User endpoints".to_string()))
    .build();

// Create Swagger UI config
let config = SwaggerConfig::new("/api-docs", spec);

// Serve Swagger UI
#[get("/api-docs")]
async fn swagger_ui(config: SwaggerConfig) -> Result<HttpResponse, Error> {
    swagger_ui_response(&config)
}

Programmatic Builder

Creating a Specification

use armature_openapi::*;

let spec = OpenApiBuilder::new("User API", "1.0.0")
    // Basic info
    .description("A comprehensive user management API")
    .terms_of_service("https://example.com/terms")

    // Contact
    .contact(
        Some("API Support".to_string()),
        Some("https://example.com/support".to_string()),
        Some("support@example.com".to_string()),
    )

    // License
    .license("MIT", Some("https://opensource.org/licenses/MIT".to_string()))

    // Servers
    .server("https://api.example.com", Some("Production".to_string()))
    .server("http://localhost:3000", Some("Development".to_string()))

    // Tags
    .tag("users", Some("User management".to_string()))
    .tag("auth", Some("Authentication".to_string()))

    .build();

Adding Endpoints

let spec = OpenApiBuilder::new("API", "1.0.0")
    .path(
        "/users",
        PathItemBuilder::new()
            .get(
                OperationBuilder::new()
                    .summary("List all users")
                    .description("Returns a paginated list of users")
                    .tag("users")
                    .operation_id("listUsers")
                    .parameter(Parameter {
                        name: "page".to_string(),
                        location: ParameterLocation::Query,
                        description: Some("Page number".to_string()),
                        required: Some(false),
                        schema: Some(integer_schema()),
                    })
                    .response(
                        "200",
                        Response {
                            description: "Successful response".to_string(),
                            content: Some({
                                let mut content = HashMap::new();
                                content.insert(
                                    "application/json".to_string(),
                                    MediaType {
                                        schema: Some(array_schema(ref_schema("User"))),
                                    },
                                );
                                content
                            }),
                        },
                    )
                    .build(),
            )
            .post(
                OperationBuilder::new()
                    .summary("Create a user")
                    .tag("users")
                    .operation_id("createUser")
                    .request_body(RequestBody {
                        description: Some("User to create".to_string()),
                        content: {
                            let mut content = HashMap::new();
                            content.insert(
                                "application/json".to_string(),
                                MediaType {
                                    schema: Some(ref_schema("User")),
                                },
                            );
                            content
                        },
                        required: Some(true),
                    })
                    .response(
                        "201",
                        Response {
                            description: "User created".to_string(),
                            content: Some({
                                let mut content = HashMap::new();
                                content.insert(
                                    "application/json".to_string(),
                                    MediaType {
                                        schema: Some(ref_schema("User")),
                                    },
                                );
                                content
                            }),
                        },
                    )
                    .build(),
            )
            .build(),
    )
    .build();

Path Parameters

.path(
    "/users/{id}",
    PathItemBuilder::new()
        .get(
            OperationBuilder::new()
                .summary("Get user by ID")
                .tag("users")
                .parameter(Parameter {
                    name: "id".to_string(),
                    location: ParameterLocation::Path,
                    description: Some("User ID".to_string()),
                    required: Some(true),
                    schema: Some(integer_schema()),
                })
                .response(
                    "200",
                    Response {
                        description: "User found".to_string(),
                        content: Some({
                            let mut content = HashMap::new();
                            content.insert(
                                "application/json".to_string(),
                                MediaType {
                                    schema: Some(ref_schema("User")),
                                },
                            );
                            content
                        }),
                    },
                )
                .response(
                    "404",
                    Response {
                        description: "User not found".to_string(),
                        content: None,
                    },
                )
                .build(),
        )
        .build(),
)

Schema Definition

Primitive Types

use armature_openapi::*;

// String
let name_schema = string_schema();

// Integer
let id_schema = integer_schema();

// Number (floating point)
let price_schema = number_schema();

// Boolean
let active_schema = boolean_schema();

Arrays

// Array of strings
let tags_schema = array_schema(string_schema());

// Array of objects
let users_schema = array_schema(ref_schema("User"));

Objects

let user_schema = object_schema(
    {
        let mut props = HashMap::new();
        props.insert("id".to_string(), integer_schema());
        props.insert("name".to_string(), string_schema());
        props.insert("email".to_string(), string_schema());
        props.insert("age".to_string(), integer_schema());
        props.insert("active".to_string(), boolean_schema());
        props
    },
    vec![
        "id".to_string(),
        "name".to_string(),
        "email".to_string(),
    ],
);

Nested Objects

let address_schema = object_schema(
    {
        let mut props = HashMap::new();
        props.insert("street".to_string(), string_schema());
        props.insert("city".to_string(), string_schema());
        props.insert("country".to_string(), string_schema());
        props
    },
    vec!["street".to_string(), "city".to_string()],
);

let user_with_address = object_schema(
    {
        let mut props = HashMap::new();
        props.insert("id".to_string(), integer_schema());
        props.insert("name".to_string(), string_schema());
        props.insert("address".to_string(), address_schema);
        props
    },
    vec!["id".to_string(), "name".to_string()],
);

Reusable Schemas

let spec = OpenApiBuilder::new("API", "1.0.0")
    // Define schema once
    .schema("User", object_schema(
        {
            let mut props = HashMap::new();
            props.insert("id".to_string(), integer_schema());
            props.insert("name".to_string(), string_schema());
            props.insert("email".to_string(), string_schema());
            props
        },
        vec!["id".to_string(), "name".to_string(), "email".to_string()],
    ))
    .schema("Address", object_schema(
        {
            let mut props = HashMap::new();
            props.insert("street".to_string(), string_schema());
            props.insert("city".to_string(), string_schema());
            props
        },
        vec!["street".to_string(), "city".to_string()],
    ))
    // Reference schemas in endpoints
    .path(
        "/users",
        PathItemBuilder::new()
            .get(
                OperationBuilder::new()
                    .summary("List users")
                    .response(
                        "200",
                        Response {
                            description: "Success".to_string(),
                            content: Some({
                                let mut content = HashMap::new();
                                content.insert(
                                    "application/json".to_string(),
                                    MediaType {
                                        schema: Some(array_schema(ref_schema("User"))),
                                    },
                                );
                                content
                            }),
                        },
                    )
                    .build(),
            )
            .build(),
    )
    .build();

Swagger UI Integration

Basic Setup

use armature_framework::prelude::*;
use armature_framework::armature_openapi::*;

#[controller("/api-docs")]
struct ApiDocsController {
    config: SwaggerConfig,
}

impl ApiDocsController {
    #[get("/")]
    async fn swagger_ui(&self) -> Result<HttpResponse, Error> {
        swagger_ui_response(&self.config)
    }

    #[get("/openapi.json")]
    async fn openapi_json(&self) -> Result<HttpResponse, Error> {
        spec_json_response(&self.config)
    }

    #[get("/openapi.yaml")]
    async fn openapi_yaml(&self) -> Result<HttpResponse, Error> {
        spec_yaml_response(&self.config)
    }
}

Custom Title

let config = SwaggerConfig::new("/api-docs", spec)
    .with_title("My Amazing API Documentation");

Multiple Versions

// Version 1
let spec_v1 = OpenApiBuilder::new("My API", "1.0.0")
    .server("https://api.example.com/v1", None)
    .build();

let config_v1 = SwaggerConfig::new("/api-docs/v1", spec_v1);

// Version 2
let spec_v2 = OpenApiBuilder::new("My API", "2.0.0")
    .server("https://api.example.com/v2", None)
    .build();

let config_v2 = SwaggerConfig::new("/api-docs/v2", spec_v2);

Security

Bearer Authentication (JWT)

let spec = OpenApiBuilder::new("API", "1.0.0")
    // Add security scheme
    .add_bearer_auth("bearer")

    // Apply to specific endpoint
    .path(
        "/users",
        PathItemBuilder::new()
            .get(
                OperationBuilder::new()
                    .summary("List users")
                    .security({
                        let mut req = HashMap::new();
                        req.insert("bearer".to_string(), vec![]);
                        req
                    })
                    .response("200", Response {
                        description: "Success".to_string(),
                        content: None,
                    })
                    .build(),
            )
            .build(),
    )
    .build();

API Key Authentication

let spec = OpenApiBuilder::new("API", "1.0.0")
    .add_api_key_auth(
        "api_key",
        "X-API-Key",
        ApiKeyLocation::Header,
    )
    .path(
        "/users",
        PathItemBuilder::new()
            .get(
                OperationBuilder::new()
                    .summary("List users")
                    .security({
                        let mut req = HashMap::new();
                        req.insert("api_key".to_string(), vec![]);
                        req
                    })
                    .response("200", Response {
                        description: "Success".to_string(),
                        content: None,
                    })
                    .build(),
            )
            .build(),
    )
    .build();

OAuth2

let spec = OpenApiBuilder::new("API", "1.0.0")
    .security_scheme(
        "oauth2",
        SecurityScheme::OAuth2 {
            flows: OAuthFlows {
                authorization_code: Some(OAuthFlow {
                    authorization_url: Some("https://example.com/oauth/authorize".to_string()),
                    token_url: Some("https://example.com/oauth/token".to_string()),
                    refresh_url: None,
                    scopes: {
                        let mut scopes = HashMap::new();
                        scopes.insert("read".to_string(), "Read access".to_string());
                        scopes.insert("write".to_string(), "Write access".to_string());
                        scopes
                    },
                }),
                ..Default::default()
            },
        },
    )
    .build();

Global Security

// Apply authentication to all endpoints by default
let spec = OpenApiBuilder::new("API", "1.0.0")
    .add_bearer_auth("bearer")
    .security({
        let mut req = HashMap::new();
        req.insert("bearer".to_string(), vec![]);
        req
    })
    .build();

Best Practices

1. Use Descriptive Names

// โœ… Good
.operation_id("getUserById")
.summary("Get a user by their unique identifier")

// โŒ Bad
.operation_id("get1")
.summary("Get")

2. Provide Examples

// Add examples to schemas
let user_schema = object_schema(
    {
        let mut props = HashMap::new();
        props.insert("id".to_string(), integer_schema());
        props.insert("name".to_string(), string_schema());
        props
    },
    vec!["id".to_string(), "name".to_string()],
);

3. Document Error Responses

.response("200", Response {
    description: "Successful response".to_string(),
    content: Some(/* ... */),
})
.response("400", Response {
    description: "Invalid request parameters".to_string(),
    content: None,
})
.response("401", Response {
    description: "Authentication required".to_string(),
    content: None,
})
.response("403", Response {
    description: "Insufficient permissions".to_string(),
    content: None,
})
.response("404", Response {
    description: "Resource not found".to_string(),
    content: None,
})
.response("500", Response {
    description: "Internal server error".to_string(),
    content: None,
})

4. Use Tags for Organization

let spec = OpenApiBuilder::new("E-commerce API", "1.0.0")
    .tag("products", Some("Product catalog".to_string()))
    .tag("orders", Some("Order management".to_string()))
    .tag("users", Some("User accounts".to_string()))
    .tag("auth", Some("Authentication".to_string()))
    .build();

5. Version Your API

// Include version in URL
.server("https://api.example.com/v1", Some("Version 1".to_string()))
.server("https://api.example.com/v2", Some("Version 2".to_string()))

6. Keep Schemas DRY

// Define common schemas once
let spec = OpenApiBuilder::new("API", "1.0.0")
    .schema("Error", object_schema(/* ... */))
    .schema("User", object_schema(/* ... */))
    .schema("Product", object_schema(/* ... */))
    // Then reference them
    .path("/users", /* use ref_schema("User") */)
    .build();

Examples

Complete REST API

use armature_openapi::*;

let spec = OpenApiBuilder::new("Task Manager API", "1.0.0")
    .description("A simple task management API")
    .server("http://localhost:3000", Some("Development".to_string()))

    // Tags
    .tag("tasks", Some("Task operations".to_string()))
    .tag("users", Some("User operations".to_string()))

    // Auth
    .add_bearer_auth("bearer")

    // Schemas
    .schema("Task", object_schema(
        {
            let mut props = HashMap::new();
            props.insert("id".to_string(), integer_schema());
            props.insert("title".to_string(), string_schema());
            props.insert("completed".to_string(), boolean_schema());
            props
        },
        vec!["id".to_string(), "title".to_string()],
    ))

    // Endpoints
    .path(
        "/tasks",
        PathItemBuilder::new()
            .get(
                OperationBuilder::new()
                    .summary("List tasks")
                    .tag("tasks")
                    .security({
                        let mut req = HashMap::new();
                        req.insert("bearer".to_string(), vec![]);
                        req
                    })
                    .response("200", Response {
                        description: "Success".to_string(),
                        content: Some({
                            let mut content = HashMap::new();
                            content.insert(
                                "application/json".to_string(),
                                MediaType {
                                    schema: Some(array_schema(ref_schema("Task"))),
                                },
                            );
                            content
                        }),
                    })
                    .build(),
            )
            .post(
                OperationBuilder::new()
                    .summary("Create task")
                    .tag("tasks")
                    .security({
                        let mut req = HashMap::new();
                        req.insert("bearer".to_string(), vec![]);
                        req
                    })
                    .request_body(RequestBody {
                        description: Some("Task to create".to_string()),
                        content: {
                            let mut content = HashMap::new();
                            content.insert(
                                "application/json".to_string(),
                                MediaType {
                                    schema: Some(ref_schema("Task")),
                                },
                            );
                            content
                        },
                        required: Some(true),
                    })
                    .response("201", Response {
                        description: "Created".to_string(),
                        content: Some({
                            let mut content = HashMap::new();
                            content.insert(
                                "application/json".to_string(),
                                MediaType {
                                    schema: Some(ref_schema("Task")),
                                },
                            );
                            content
                        }),
                    })
                    .build(),
            )
            .build(),
    )

    .build();

Summary

Key Features:

  • โœ… Programmatic OpenAPI 3.0 spec generation
  • โœ… Interactive Swagger UI documentation
  • โœ… JSON/YAML export
  • โœ… Full security scheme support
  • โœ… Type-safe builders

When to Use:

  • Documenting REST APIs
  • Generating client SDKs
  • API contract validation
  • Developer onboarding
  • API discovery

Next Steps:

  1. Define your API schemas
  2. Document each endpoint
  3. Add security schemes
  4. Serve Swagger UI
  5. Export OpenAPI spec

Happy documenting! ๐Ÿ“šโœจ