Rust整合openapi、swagger-ui

641 阅读3分钟

前后端分离开发中,接口需要规范化、标准化、文档化,这里使用utoipa

OpenAPI是什么

OpenAPI 是一个规范,用于描述、生产、消费和可视化 RESTful 风格的 Web 服务。它提供了一种标准的、与编程语言无关的方式来定义 API 的接口,包括路径(endpoints)、操作(如 GET、POST 等 HTTP 方法)、请求和响应的格式、参数、安全机制等细节

添加crate

tokio = { version = "1", features = ["full"] }
axum = "0.7.7"
# openapi
utoipa = { version = "5.1.1", features = ["axum_extras", "debug"] }
# openapi 文档
utoipa-swagger-ui = { version = "8.0.0", features = ["axum"] }
utoipa-axum = { version = "0", features = ["debug"] }
serde = "1"
serde_json = "1"

配置

定义Api文档结构体

// 定义 API 文档结构体
#[derive(OpenApi)]
#[openapi(
    tags(
        //  指定标签信息,后续用标签区给api分组
        (name = CUSTOMER_TAG, description = "Customer API endpoints"),
        (name = ORDER_TAG, description = "Order API endpoints")
    )
)]
struct ApiDoc;

定义健康检查内部路由

/// 获取健康检查
#[utoipa::path(
    method(get, head), // 方法
    path = "/api/health", // 路径
    responses(
        // 响应状态、描述、返回内容、类型
        (status = OK, description = "Success", body = str, content_type = "text/plain")
    )
)]
async fn health() -> &'static str {
    "ok"
}

定义内部处理器(非必须),这里的路由是swagger的内部路由

mod inner {
    pub mod secret_handlers {
        /// swagger内部处理器
        #[utoipa::path(get, path = "/api/inner/secret", responses((status = OK, body = str)))]
        pub async fn get_secret() -> &'static str {
            "secret"
        }

        /// swagger内部处理器
        #[utoipa::path(
            post,
            path = "/api/inner/secret",
            responses((status = OK, description = "OK"))
        )]
        pub async fn post_secret() {
            println!("You posted a secret")
        }
    }
}

对应界面

image.png

添加swagger-ui外部路由

#[tokio::main]
async fn main() -> Result<(), io::Error> {
    let (router, api) = OpenApiRouter::with_openapi(ApiDoc::openapi())
        .routes(routes!(health))
        .nest("/api/customer", customer::router())
        .nest("/api/order", order::router())
        .routes(routes!(inner::secret_handlers::get_secret, inner::secret_handlers::post_secret))
        .split_for_parts();
    // 合并路由和swagger ui路由
    let router = router.merge(SwaggerUi::new("/swagger-ui").url("/apidoc/openapi.json", api));

    let listener = TcpListener::bind((Ipv4Addr::LOCALHOST, 8080)).await?;
    axum::serve(listener, router).await
}

使用

访问:http://localhost:8080/swagger-ui/

1、utoipa可以添加方法定义和描述,可以看到方法上的文档注释///变成了swagger的方法描述,rust doc支持markdown,你可以在swagger中使用markdown语法

image.png

2、api分组,指定tag字段

src/customer.rs

use axum::Json;
use serde::Serialize;
use utoipa::ToSchema;
use utoipa_axum::router::OpenApiRouter;
use utoipa_axum::routes;

/// 客户定义
#[derive(ToSchema, Serialize)]
struct Customer {
    name: String,
}

/// 向父模块公开路由
pub fn router() -> OpenApiRouter {
    OpenApiRouter::new().routes(routes!(get_customer))
}

/// 获取客户
///
/// 描述: 获取客户信息
#[utoipa::path(
    get,
    path = "",
    responses((status = OK, body = Customer)),
    tag = super::CUSTOMER_TAG
)]
async fn get_customer() -> Json<Customer> {
    Json(Customer {
        name: String::from("Bill Book"),
    })
}

image.png

3、测试接口

src/order.rs

use axum::Json;
use serde::{ Deserialize, Serialize };
use utoipa::ToSchema;
use utoipa_axum::router::OpenApiRouter;
use utoipa_axum::routes;

/// 订单定义
#[derive(ToSchema, Serialize)]
struct Order {
    id: i32,
    name: String,
}
/// 订单请求定义
#[derive(ToSchema, Deserialize, Serialize)]
struct OrderRequest {
    name: String,
}

/// 向父模块公开路由
pub fn router() -> OpenApiRouter {
    OpenApiRouter::new().routes(routes!(get_order, create_order))
}

/// 获取订单
///
/// 获取id100的订单
#[utoipa::path(get, path = "", responses((status = OK, body = Order)), tag = super::ORDER_TAG)]
async fn get_order() -> Json<Order> {
    Json(Order {
        id: 100,
        name: String::from("Bill Book"),
    })
}

/// 创建订单
///
/// 这里用于描述方法的详细信息
#[utoipa::path(
    post,
    path = "",
    responses((status = OK, body = Order)),
    params(("name" = String, Path, description = "订单名")),
    tag = super::ORDER_TAG
)]
async fn create_order(Json(order): Json<OrderRequest>) -> Json<Order> {
    Json(Order {
        id: 120,
        name: order.name,
    })
}

image.png

4、使用ToSchema可以将结构体映射到swagger-ui

image.png